@taiga-ai/mcp-server 0.26.0-dev.486 → 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.
Files changed (2) hide show
  1. package/dist/index.js +262 -182
  2. 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
  *
@@ -1385,6 +1388,49 @@ z.object({
1385
1388
  ...e.failingChecks === void 0 ? {} : { failingChecks: e.failingChecks },
1386
1389
  ...e.blockers === void 0 ? {} : { blockers: e.blockers }
1387
1390
  }));
1391
+ /**
1392
+ * What a run's hardening came to, coverage included (PRO-2829). `passed`
1393
+ * counted only blocking findings, so a run whose scanners never ran said
1394
+ * "passed" all the same.
1395
+ *
1396
+ * - `failed`: blocking findings remain (the gaps are still listed).
1397
+ * - `not_scanned`: no scanner that applies to the repository ran to the end.
1398
+ * - `passed_with_gaps`: nothing blocks, but some scanner did not run.
1399
+ * - `passed`: nothing blocks and every scanner that applies ran.
1400
+ */
1401
+ const HARDENING_VERDICTS = [
1402
+ "passed",
1403
+ "passed_with_gaps",
1404
+ "not_scanned",
1405
+ "failed"
1406
+ ];
1407
+ /**
1408
+ * The verdict and the gaps of a hardening result (PRO-2829), for every reader
1409
+ * (the run read, the web's run page, the MCP), from what the agent reported.
1410
+ *
1411
+ * A gap is a scanner that applies and did not run: `not_run` (the scan never
1412
+ * reached it), `skipped` (its tool was missing), or `failed` with a reason,
1413
+ * which is a scanner that could not finish. A `failed` scanner without one
1414
+ * found something (tflint fails on a MAJOR that blocks nothing, and rows from
1415
+ * before `scannerReasons` used `failed` for "had findings"), so it ran and is
1416
+ * no gap. A scanner listed in `notApplicable` is no gap; on a result from
1417
+ * before that list, a skipped tflint is read the same way, as the only reason
1418
+ * the agent ever skipped it was a repository with no Terraform.
1419
+ */
1420
+ function hardeningCoverage(result) {
1421
+ const notApplicable = new Set(result.notApplicable ?? []);
1422
+ const applies = Object.entries(result.scanners ?? {}).filter(([scanner, status]) => !notApplicable.has(scanner) && !(result.notApplicable === void 0 && scanner === "tflint" && status === "skipped"));
1423
+ const reasons = result.scannerReasons ?? {};
1424
+ const gaps = applies.filter(([scanner, status]) => status === "not_run" || status === "skipped" || status === "failed" && scanner in reasons).map(([scanner, status]) => ({
1425
+ scanner,
1426
+ status,
1427
+ reason: reasons[scanner] ?? null
1428
+ }));
1429
+ return {
1430
+ verdict: !result.passed ? "failed" : gaps.length === applies.length ? "not_scanned" : gaps.length > 0 ? "passed_with_gaps" : "passed",
1431
+ gaps
1432
+ };
1433
+ }
1388
1434
  [...BUILD_RUN_ACTIVE_STATUSES.filter((s) => s !== "reviewing")];
1389
1435
  //#endregion
1390
1436
  //#region ../../packages/types/dist/agents.js
@@ -2202,7 +2248,7 @@ const DOCUMENT_INPUTS = Object.freeze({
2202
2248
  * reads as deleted) and fields they explain would simply be missing. So it
2203
2249
  * refuses to start, and says what to do.
2204
2250
  */
2205
- function apiLevelProblem(introspection, apiUrl, required = 1, version = SERVER_VERSION) {
2251
+ function apiLevelProblem(introspection, apiUrl, required = 2, version = SERVER_VERSION) {
2206
2252
  const offered = introspection.mcpApiLevel ?? 0;
2207
2253
  if (offered >= required) return null;
2208
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.`;
@@ -2459,7 +2505,7 @@ const BY_ARGUMENT = {
2459
2505
  /** Where an argument means something else on one tool, or the tool's 404 needs its own word. */
2460
2506
  const BY_TOOL = {
2461
2507
  taiga_resolve_action: () => "taiga_list_action_required lists the items with their IDs.",
2462
- taiga_get_plan: (said) => /\bplan\b/i.test(said) ? "taiga_get_initiative says whether one is being generated (planningStatus planning) or failed, and taiga_plan_again asks for a new one." : ITEM_ID_HINT
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
2463
2509
  };
2464
2510
  /**
2465
2511
  * What the API calls the thing each ID names, as its 404 says it ("Environment
@@ -3942,6 +3988,34 @@ function hardeningSaid(blocking, named) {
3942
3988
  const recorded = named.length > shown.length ? `, ${named.length} recorded in taiga_get_run` : "";
3943
3989
  return `${count}: ${shown.join("; ")}${rest > 0 ? `; and ${rest} more${recorded}` : ""}.`;
3944
3990
  }
3991
+ /** How each hardening verdict reads (PRO-2829): "passed" only when every scanner that applies ran. */
3992
+ const VERDICT_SAID = {
3993
+ passed: "Hardening passed: every scanner that applies ran and nothing blocks",
3994
+ passed_with_gaps: "Hardening passed, but not every scanner ran, so its checks are missing from the result",
3995
+ not_scanned: "No security scanner ran in hardening: \"passed\" only means no blocking finding was recorded",
3996
+ failed: "Hardening failed: blocking findings remain"
3997
+ };
3998
+ /**
3999
+ * What a run's hardening came to, coverage included (PRO-2829), from the
4000
+ * verdict the API reads off it, or read here from an API from before that.
4001
+ * Null without a hardening result.
4002
+ */
4003
+ function scanSaid(hardening) {
4004
+ if (!hardening || typeof hardening !== "object") return null;
4005
+ const h = hardening;
4006
+ const coverage = typeof h.verdict === "string" && HARDENING_VERDICTS.includes(h.verdict) && Array.isArray(h.gaps) ? {
4007
+ verdict: h.verdict,
4008
+ gaps: h.gaps
4009
+ } : typeof h.passed === "boolean" && h.scanners ? hardeningCoverage(h) : null;
4010
+ if (!coverage) return null;
4011
+ const gaps = coverage.gaps.map((gap) => `${gap.scanner}${gap.status === "failed" ? " did not finish" : ""}${gap.reason ? ` (${gap.reason})` : ""}`);
4012
+ const missing = gaps.length > 0 ? `; did not run: ${gaps.join(", ")}` : "";
4013
+ return {
4014
+ verdict: coverage.verdict,
4015
+ gaps: gaps.length,
4016
+ said: `${VERDICT_SAID[coverage.verdict]}${missing}.`
4017
+ };
4018
+ }
3945
4019
  /**
3946
4020
  * What each failedReason means for the next move (PRO-2800): reviewers met dispatch_rejected and agent_reported as
3947
4021
  * bare enums. As @taiga/types documents them.
@@ -3962,20 +4036,23 @@ const FAILED_REASON_MEANS = {
3962
4036
  * text out: its first sentence is in the outcome, and taiga_get_run has it all.
3963
4037
  */
3964
4038
  function readableRun(run, opts = {}) {
3965
- const { prDraft, error, reviewEscalation, runtime: _runtime, ...rest } = run;
4039
+ const { prDraft, error, reviewEscalation, runtime: _runtime, origin, ...rest } = run;
3966
4040
  const said = runOutcome(run);
3967
4041
  const means = FAILED_REASON_MEANS[String(run["failedReason"])];
3968
4042
  const hardening = run["hardeningResult"];
3969
4043
  const blocking = typeof hardening?.blockingFindings === "number" ? hardening.blockingFindings : 0;
3970
4044
  const named = Array.isArray(hardening?.blocking) ? hardening.blocking : [];
4045
+ const scan = scanSaid(hardening);
3971
4046
  return {
3972
4047
  ...rest,
4048
+ ...origin === void 0 ? {} : { origin: origin === "backlog" ? "initiative" : origin },
3973
4049
  ...means ? { failedReasonMeans: means } : {},
4050
+ ...scan && scan.gaps + (scan.verdict === "not_scanned" ? 1 : 0) > 0 ? { scanCoverage: scan.said } : {},
3974
4051
  ...blocking > 0 ? { hardeningNote: hardeningSaid(blocking, named) } : {},
3975
4052
  ...opts.row || error === void 0 ? {} : { error },
3976
4053
  ...opts.row || reviewEscalation === void 0 ? {} : { reviewEscalation },
3977
4054
  ...typeof rest["prNumber"] === "number" || rest["prUrl"] ? { prDraft } : {},
3978
- ...said ?? {}
4055
+ ...said ? { outcome: said.outcome } : {}
3979
4056
  };
3980
4057
  }
3981
4058
  /** Distinct initiatives a run list reads to say which moved on; past this a waiting row says it was not checked. */
@@ -4027,7 +4104,8 @@ const INITIATIVE_ENDED = {
4027
4104
  /** A readable run row that also says when its initiative ended while its pull request waited. */
4028
4105
  function withInitiativeState(row, states) {
4029
4106
  const state = states.get(String(row["backlogItemId"]));
4030
- if (!["gated", "unmerged"].includes(String(row["disposition"]))) return row;
4107
+ const status = String(row["status"]);
4108
+ if (!(BUILD_RUN_STATUSES.includes(status) && ["gated", "unmerged"].includes(landedDisposition(status, row["prMerged"] === true)))) return row;
4031
4109
  if (state === "unchecked") return {
4032
4110
  ...row,
4033
4111
  outcome: `${String(row["outcome"])} Whether its initiative still wants it was not checked.`
@@ -4038,7 +4116,6 @@ function withInitiativeState(row, states) {
4038
4116
  const open = row["prState"] === "open" ? ` ${pr} is still open on the forge.` : "";
4039
4117
  return {
4040
4118
  ...row,
4041
- disposition: "abandoned",
4042
4119
  actions: [],
4043
4120
  outcome: `Its initiative ${ended}, so nobody is waiting on this run.${open}`
4044
4121
  };
@@ -4147,19 +4224,20 @@ function registerBuildTools(server, client) {
4147
4224
  if (!client.hasCapability("products:read")) return;
4148
4225
  server.registerTool("taiga_list_runs", {
4149
4226
  title: "List runs",
4150
- description: "List coding agent runs, newest first: branch, pull request, step progress, outcome (one sentence), actions and disposition: in_flight, gated (a person owes a review and merge), succeeded (merged), failed, stopped, rejected (pull request closed unmerged), unmerged, or abandoned (its initiative ended: done, canceled, archived or deleted). 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).",
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).",
4151
4228
  inputSchema: z.object({
4152
4229
  productId: productId.optional(),
4153
- 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; needs productId"),
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"),
4154
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"),
4155
4232
  ...pagination
4156
4233
  }),
4157
4234
  annotations: READ_ONLY
4158
4235
  }, (params) => runTool(async () => {
4159
- if (!params.productId && params.initiativeId) return toolError("VALIDATION_ERROR", "initiativeId narrows the runs of one product: pass its productId as well (the initiative carries it).");
4160
- if (params.productId && params.active) return toolError("VALIDATION_ERROR", "active narrows the org-wide list only: omit productId, or read the product's runs and judge each by its status.");
4161
- if (params.productId) {
4162
- const result = await client.listBuildRuns(params.productId, {
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, {
4163
4241
  backlogItemId: params.initiativeId,
4164
4242
  limit: params.limit,
4165
4243
  cursor: params.cursor
@@ -4172,7 +4250,7 @@ function registerBuildTools(server, client) {
4172
4250
  }));
4173
4251
  server.registerTool("taiga_get_run", {
4174
4252
  title: "Get run",
4175
- description: "Read one run with its steps: outcome, disposition, pull request, hardening, 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.",
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.",
4176
4254
  inputSchema: z.object({
4177
4255
  runId,
4178
4256
  full: z.boolean().optional().describe("Carry each step's complete instructions and report (default false: a summary)")
@@ -4299,9 +4377,7 @@ async function queueView(client, product, options = { name: "all" }) {
4299
4377
  if (item) {
4300
4378
  Object.assign(row, {
4301
4379
  title: item.title,
4302
- state: item.state,
4303
- planningStatus: item.planningStatus,
4304
- needsHumanCause: item.needsHumanCause,
4380
+ ...standing(item),
4305
4381
  criteria: criteriaCounts(item),
4306
4382
  lifecycle: lifecycle ? {
4307
4383
  turn: lifecycle.turn,
@@ -4333,7 +4409,7 @@ async function queueView(client, product, options = { name: "all" }) {
4333
4409
  const line = typeof paused === "string" ? `The line is paused (since ${paused}): nothing is planned or built until a person resumes it.` : null;
4334
4410
  return {
4335
4411
  linePaused: paused === null ? null : typeof paused === "string",
4336
- entries: rows,
4412
+ entries: rows.map(({ lifecycle: _lifecycle, runId: _runId, ...row }) => row),
4337
4413
  waitingOn: line ?? waitingOn(rows),
4338
4414
  ...note ? { note } : {}
4339
4415
  };
@@ -4379,18 +4455,21 @@ function registerQueueTools(server, client) {
4379
4455
  }
4380
4456
  //#endregion
4381
4457
  //#region src/tools/backlogs.ts
4382
- /** What a list row needs to say for the model to pick an item to read in full. */
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
+ */
4383
4466
  const BACKLOG_SUMMARY_FIELDS = [
4384
4467
  "id",
4385
4468
  "title",
4386
4469
  "category",
4387
- "status",
4388
- "state",
4389
- "planningStatus",
4390
4470
  "priority",
4391
4471
  "estimate",
4392
4472
  "origin",
4393
- "needsHumanCause",
4394
4473
  "archivedAt",
4395
4474
  "createdAt",
4396
4475
  "updatedAt"
@@ -4404,21 +4483,44 @@ const ITEM_OUTCOME_FIELDS = [
4404
4483
  "id",
4405
4484
  "productId",
4406
4485
  "title",
4407
- "status",
4408
- "state",
4409
- "planningStatus",
4410
- "needsHumanCause",
4411
- "needsHumanDetail",
4412
4486
  "autoBuild",
4413
- "archivedAt",
4414
- "lifecycle"
4487
+ "archivedAt"
4415
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"
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
+ }
4416
4518
  /**
4417
4519
  * What the lifecycle on an initiative says, and the call each next step is.
4418
4520
  * needsHumanCause is only the line's flag; a plan that failed outside the line
4419
4521
  * leaves it null, and the lifecycle is what says why and what moves it on.
4420
4522
  */
4421
- const LIFECYCLE_MEANING = "situation says in a sentence where it stands and what moves it on. lifecycle.cta names the next call: start_plan taiga_queue_initiative, retry_plan taiga_plan_again (planFailureCause says why), start_build taiga_approve_plan, resume_build taiga_resume_initiative, retry_build a fresh run (web only; off the line, taiga_queue_initiative instead, as situation says), review_pr and view_pr the pull request. needsHumanCause is set only when the line stopped on it.";
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).";
4422
4524
  /** The move each call to action is, in the tools' words. */
4423
4525
  const CTA_MOVE = {
4424
4526
  start_plan: "taiga_queue_initiative puts it on the line to be planned and built (paid work: ask first)",
@@ -4431,8 +4533,8 @@ const CTA_MOVE = {
4431
4533
  };
4432
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). */
4433
4535
  const OFF_THE_LINE_MOVE = "taiga_queue_initiative puts it back on the line (paid work: ask first)";
4434
- /** What happens to a queued initiative whose last run did not land: the line plans it again (PRO-2840). */
4435
- const REQUEUED_MOVE = "the line plans it again when its turn comes";
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";
4436
4538
  /** How a run stands, as the end of "its latest run ...", by disposition. */
4437
4539
  const RUN_STANDS = {
4438
4540
  in_flight: "is running",
@@ -4469,7 +4571,10 @@ function situationOf(item) {
4469
4571
  const column = typeof item["state"] === "string" ? item["state"] : null;
4470
4572
  const offTheLine = column !== null && !LINE_STATES.includes(column);
4471
4573
  const stalled = life.stage === "building" && life.turn !== "agent";
4472
- const requeued = stalled && column === "queue";
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";
4473
4578
  if (stalled) stage = requeued ? "Queued" : offTheLine ? "Off the line" : column === null ? "Its build stopped" : "On the line, but its build stopped";
4474
4579
  const build = life.latestBuild;
4475
4580
  if (build && typeof build.status === "string" && life.stage !== "done" && life.stage !== "canceled") {
@@ -4490,7 +4595,7 @@ function situationOf(item) {
4490
4595
  }
4491
4596
  const turn = life.turn === "you" ? "A person's turn" : life.turn === "agent" ? "The agent's turn" : null;
4492
4597
  const handedBack = handbackSaid(build?.reviewEscalation);
4493
- 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;
4494
4599
  if (requeued) parts.push(`Nothing to do: ${REQUEUED_MOVE}.`);
4495
4600
  else if (life.cta === "view_pr") parts.push(`Nothing to do: ${move}.`);
4496
4601
  else if (turn && move) parts.push(`${turn}: ${move}.`);
@@ -4518,15 +4623,14 @@ function endingSaid(item) {
4518
4623
  if (next && typeof next.id === "string") said.push(`; superseded by "${String(next.title)}" (${next.id})`);
4519
4624
  return said.join("");
4520
4625
  }
4521
- /** A row or item with its situation added when it has a lifecycle. */
4626
+ /** A row or item with where it stands added (`standing`). */
4522
4627
  function withSituation(item, row = item) {
4523
- const situation = situationOf(item);
4524
- return situation ? {
4628
+ return {
4525
4629
  ...row,
4526
- situation
4527
- } : row;
4630
+ ...standing(item)
4631
+ };
4528
4632
  }
4529
- /** List rows, each with its situation. */
4633
+ /** List rows, each with where it stands. */
4530
4634
  function situatedRows(items) {
4531
4635
  return itemRows(items, BACKLOG_SUMMARY_FIELDS).map((row, index) => withSituation(items[index], row));
4532
4636
  }
@@ -4603,36 +4707,6 @@ const outcomeOf = (item) => withSituation(item, {
4603
4707
  criteria: criteriaCounts(item)
4604
4708
  });
4605
4709
  const outcome = (item) => toolResult(outcomeOf(item));
4606
- /** The statuses an initiative in each board column can have: the column decides it (statusForState, PRO-2828). */
4607
- const COLUMN_STATUSES = {
4608
- backlog: [
4609
- "all",
4610
- "open",
4611
- "backlog"
4612
- ],
4613
- todo: [
4614
- "all",
4615
- "open",
4616
- "backlog"
4617
- ],
4618
- queue: [
4619
- "all",
4620
- "open",
4621
- "backlog"
4622
- ],
4623
- plan: [
4624
- "all",
4625
- "open",
4626
- "backlog"
4627
- ],
4628
- build: [
4629
- "all",
4630
- "open",
4631
- "in_progress"
4632
- ],
4633
- done: ["all", "done"],
4634
- canceled: ["all", "canceled"]
4635
- };
4636
4710
  /**
4637
4711
  * Each tool is registered under the capability its route requires, so a
4638
4712
  * key sees exactly what it may call: reads on backlog:read; the board move on
@@ -4665,25 +4739,15 @@ function registerBacklogTools(server, client) {
4665
4739
  function registerBacklogReads(server, client) {
4666
4740
  server.registerTool("taiga_list_initiatives", {
4667
4741
  title: "List initiatives",
4668
- description: "List a product's open initiatives (not done or canceled; status: 'all' for every one): title, category, status, place on the line (backlog, todo, queue, plan, build, done), situation (where it stands and what moves it on) and criteria (acceptance criteria met, confirmed with evidence or checked off by a person; null before planning, so a done initiative can show fewer met than it has). query finds one by title, state one board column; taiga_get_initiative reads it in full. Archived ones are left out unless asked for.",
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.",
4669
4743
  inputSchema: z.object({
4670
4744
  productId,
4671
4745
  query: z.string().trim().min(1).max(200).optional().describe("Part of the title, any case: only the items that contain it"),
4672
- status: z.enum([
4746
+ column: z.enum([
4673
4747
  "open",
4674
4748
  "all",
4675
- "backlog",
4676
- "in_progress",
4677
- "done",
4678
- "canceled"
4679
- ]).optional().describe("open (default without state): not done or canceled; all: every one; in_progress is the Build column, backlog every other open one"),
4680
- planningStatus: z.enum([
4681
- "unplanned",
4682
- "planning",
4683
- "planned",
4684
- "failed"
4685
- ]).optional().describe("Only items whose plan is in this state"),
4686
- 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"),
4687
4751
  category: z.enum([
4688
4752
  "feature",
4689
4753
  "bug",
@@ -4695,56 +4759,66 @@ function registerBacklogReads(server, client) {
4695
4759
  }),
4696
4760
  annotations: READ_ONLY
4697
4761
  }, (params) => runTool(async () => {
4698
- const { query, state, status = state === void 0 ? "open" : "all", ...filters } = params;
4699
- 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;
4700
4763
  const apiFilters = {
4701
4764
  ...filters,
4702
- status: status === "open" || status === "all" ? void 0 : status
4765
+ order: "newest",
4766
+ ...column === "open" ? { open: true } : column === "all" ? {} : { state: column }
4703
4767
  };
4704
- const open = status === "open";
4705
- if (query === void 0 && state === void 0 && !open) {
4706
- const result = await client.listBacklogItems(params.productId, apiFilters);
4768
+ if (query === void 0) {
4769
+ const result = await client.listBacklogItems(id, apiFilters);
4707
4770
  return toolResult(page(situatedRows(result.data), result.nextCursor));
4708
4771
  }
4709
- if (params.cursor !== void 0) return toolError("VALIDATION_ERROR", "Open items, a board column or a title search answer one page; narrow them, or pass status: 'all' (or one status) to page through every item.");
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.");
4710
4773
  let closedSeen = 0;
4711
- const needle = (query ?? "").toLowerCase();
4712
- const found = await searchPages((cursor) => client.listBacklogItems(params.productId, {
4774
+ const needle = query.toLowerCase();
4775
+ const found = await searchPages((cursor) => client.listBacklogItems(id, {
4713
4776
  ...apiFilters,
4777
+ ...column === "open" ? { open: void 0 } : {},
4714
4778
  limit: 100,
4715
4779
  cursor
4716
- }), query ?? "", (row) => [row.title], params.limit ?? 25, (row) => {
4717
- if (state !== void 0 && row.state !== state) return false;
4718
- if (!open || !["done", "canceled"].includes(row.status)) return true;
4719
- 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;
4720
4783
  return false;
4721
4784
  });
4722
- const hidden = query !== void 0 && state === void 0 && !found.more ? closedSeen : 0;
4785
+ const hidden = column === "open" && !found.more ? closedSeen : 0;
4723
4786
  return toolResult({
4724
4787
  ...page(situatedRows(found.rows), null),
4725
- ...hidden > 0 ? { closedMatches: `${hidden} done or canceled initiative${hidden === 1 ? " also matches" : "s also match"}; status: 'all' lists them.` } : {},
4726
- ...found.more ? { more: "There may be more than the limit carries: narrow it with state or query, or list one status (backlog, in_progress, done or canceled) without them, which pages." } : {}
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." } : {}
4727
4790
  });
4728
4791
  }));
4729
4792
  server.registerTool("taiga_get_initiative", {
4730
4793
  title: "Get initiative",
4731
- description: `Read one initiative in full: description, acceptance criteria (which are met), technical context, dependencies and source documents. Read it before implementing. A failed plan carries planFailure (cause, meaning, when, by whom). ${LIFECYCLE_MEANING}`,
4732
- inputSchema: z.object({ initiativeId }),
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
+ }),
4733
4799
  annotations: READ_ONLY
4734
4800
  }, (params) => runTool(async () => {
4735
4801
  const item = (await client.getBacklogItem(params.initiativeId)).data;
4736
4802
  const row = item;
4737
- if (item.planningStatus !== "failed") return toolResult(withSituation(row));
4738
- const ownCause = item.lifecycle?.planFailureCause;
4739
- const planFailure = await planFailureOf(client, params.initiativeId, ownCause);
4740
- return toolResult(withSituation(row, planFailure ? {
4741
- ...item,
4742
- planFailure
4743
- } : item));
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
+ });
4744
4818
  }));
4745
4819
  server.registerTool("taiga_get_plan", {
4746
4820
  title: "Get the active plan",
4747
- 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; planningStatus says whether one is being generated.",
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.",
4748
4822
  inputSchema: z.object({
4749
4823
  initiativeId,
4750
4824
  full: z.boolean().optional().describe("Carry each step's complete instructions (default false: a summary)")
@@ -4907,7 +4981,7 @@ function registerReviewGate(server, client) {
4907
4981
  function registerResume(server, client) {
4908
4982
  server.registerTool("taiga_resume_initiative", {
4909
4983
  title: "Resume initiative",
4910
- description: "Carry on an initiative the line stopped on (needsHumanCause is set): 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 failed plan with needsHumanCause null (lifecycle.cta retry_plan, planFailureCause says why) needs taiga_plan_again.",
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.",
4911
4985
  inputSchema: z.object({ initiativeId }),
4912
4986
  annotations: OPERATE
4913
4987
  }, (params) => runTool(async () => outcome((await client.resumeInitiative(params.initiativeId)).data)));
@@ -5332,6 +5406,28 @@ function readWith(row, backlogRead) {
5332
5406
  default: return;
5333
5407
  }
5334
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
+ }
5335
5431
  /** A summary past its first sentence: review_doc rows quoted two documents, 800 to 1,200 characters each. */
5336
5432
  const SUMMARY_LIMIT = 200;
5337
5433
  function firstSentence(text) {
@@ -5349,9 +5445,12 @@ function itemRow(row, now, full, backlogRead) {
5349
5445
  const created = typeof row["createdAt"] === "string" ? Date.parse(row["createdAt"]) : NaN;
5350
5446
  const { summary, status, blocking, ...lean } = rest;
5351
5447
  const read = readWith(row, backlogRead);
5448
+ const saysNothing = row["kind"] === "setup_manual" && String(summary).trim().toLowerCase() === "manual config";
5352
5449
  return {
5353
5450
  ...lean,
5354
- ...full ? { summary } : row["kind"] === "review_pr" ? {} : { summary: firstSentence(summary) },
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) },
5355
5454
  ...full || status !== "open" ? { status } : {},
5356
5455
  ...full || blocking === true ? { blocking } : {},
5357
5456
  ...read ? { readWith: read } : {},
@@ -5383,7 +5482,8 @@ function summaryOf(rows, stale) {
5383
5482
  if (stale.has(String(row["id"]))) entry.stale = (entry.stale ?? 0) + 1;
5384
5483
  else {
5385
5484
  entry.open += 1;
5386
- entry.byKind[String(row["kind"])] = (entry.byKind[String(row["kind"])] ?? 0) + 1;
5485
+ const kind = String(kindSaid(row["kind"]));
5486
+ entry.byKind[kind] = (entry.byKind[kind] ?? 0) + 1;
5387
5487
  }
5388
5488
  byProduct.set(key, entry);
5389
5489
  }
@@ -5439,15 +5539,10 @@ async function staleBacklogReviews(client, rows, out) {
5439
5539
  const empty = /* @__PURE__ */ new Set();
5440
5540
  await Promise.all(checked.map(async (productId) => {
5441
5541
  try {
5442
- const pages = await Promise.all(["backlog", "in_progress"].map((status) => client.listBacklogItems(productId, {
5443
- status,
5444
- limit: 100
5445
- })));
5446
- if (pages.some((page) => page.data.some((item) => item.state === "backlog" || item.state === "todo"))) return;
5447
- if (pages.some((page) => page.nextCursor)) {
5448
- unchecked += asks.filter((row) => row["productId"] === productId).length;
5449
- return;
5450
- }
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;
5451
5546
  empty.add(productId);
5452
5547
  } catch (error) {
5453
5548
  if (!(error instanceof TaigaApiError || isNetworkFailure(error))) throw error;
@@ -5530,6 +5625,7 @@ function sameKeyOnce(rows) {
5530
5625
  id: from["id"],
5531
5626
  category: from["category"],
5532
5627
  title: from["title"],
5628
+ inboxItemId: from["inboxItemId"],
5533
5629
  inboxItemStatus: from["inboxItemStatus"]
5534
5630
  });
5535
5631
  if (!isOpen(kept) && isOpen(row)) {
@@ -5629,7 +5725,7 @@ function registerInboxTools(server, client) {
5629
5725
  ...params.cursor === void 0 ? { withTotal: true } : {}
5630
5726
  });
5631
5727
  return toolResult({
5632
- ...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),
5633
5729
  ...totalOf(result)
5634
5730
  });
5635
5731
  }));
@@ -5882,42 +5978,31 @@ async function policiesPart(client) {
5882
5978
  sections: Object.keys(sectionsOf(row["data"] ?? {}) ?? {}).length
5883
5979
  }));
5884
5980
  }
5885
- /** The statuses an item is open in; the API's filter takes one at a time. */
5886
- const OPEN_BACKLOG_STATUSES = ["backlog", "in_progress"];
5887
5981
  /**
5888
- * The items still open: one status-filtered read per open status (the API
5889
- * leaves archived items out), each paged in full up to a cap the result
5890
- * reports. The list is oldest first and the open items are the newest, so a
5891
- * single page would miss exactly the ones that matter. Rows carrying a
5892
- * retired status (readable for a rolling deploy) match neither filter;
5893
- * 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.
5894
5984
  */
5895
5985
  async function backlogPart(client, productId) {
5896
- const open = /* @__PURE__ */ new Map();
5986
+ const rows = [];
5897
5987
  let truncated = false;
5898
5988
  let pageCapped = false;
5899
- for (const status of OPEN_BACKLOG_STATUSES) {
5900
- let cursor;
5901
- for (let pageIndex = 0;; pageIndex += 1) {
5902
- if (pageIndex === BACKLOG_PAGE_LIMIT) {
5903
- truncated = true;
5904
- pageCapped = true;
5905
- break;
5906
- }
5907
- const result = await client.listBacklogItems(productId, {
5908
- status,
5909
- limit: 100,
5910
- cursor
5911
- });
5912
- for (const row of result.data) if (!isSettledOrArchived({
5913
- status: String(row.status),
5914
- archivedAt: row.archivedAt ?? null
5915
- })) open.set(String(row.id), row);
5916
- if (!result.nextCursor) break;
5917
- 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;
5918
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;
5919
6005
  }
5920
- const rows = [...open.values()].sort((a, b) => String(a.id) < String(b.id) ? -1 : String(a.id) > String(b.id) ? 1 : 0);
5921
6006
  if (rows.length > BACKLOG_IN_CONTEXT) truncated = true;
5922
6007
  const byColumn = {
5923
6008
  backlog: 0,
@@ -5929,9 +6014,9 @@ async function backlogPart(client, productId) {
5929
6014
  for (const row of rows) if (row.state) byColumn[row.state] = (byColumn[row.state] ?? 0) + 1;
5930
6015
  return {
5931
6016
  byColumn,
5932
- items: situatedRows(rows.slice(-50)),
6017
+ items: situatedRows(rows.slice(0, BACKLOG_IN_CONTEXT)),
5933
6018
  truncated,
5934
- ...pageCapped ? { note: "More than 1000 open items in one status: only the oldest of them were read, so the newest may be missing here and byColumn counts only those read; taiga_list_initiatives pages the rest." } : {}
6019
+ ...pageCapped ? { note: "More than 1000 open items: byColumn counts the newest 1000; taiga_list_initiatives pages the rest." } : {}
5935
6020
  };
5936
6021
  }
5937
6022
  /**
@@ -5956,7 +6041,7 @@ const CONTEXT_RUN_SAID = [
5956
6041
  "runNumber",
5957
6042
  "backlogItemId",
5958
6043
  "targetTitle",
5959
- "disposition",
6044
+ "status",
5960
6045
  "outcome",
5961
6046
  "failedReasonMeans",
5962
6047
  "hardeningNote",
@@ -6051,15 +6136,15 @@ async function productContext(client, product, options) {
6051
6136
  }), null, 2).length;
6052
6137
  }
6053
6138
  return toolResult({
6054
- ...result,
6055
- characters
6139
+ characters,
6140
+ ...result
6056
6141
  });
6057
6142
  }
6058
6143
  function registerContextTools(server, client) {
6059
6144
  if (!client.hasCapability("products:read")) return;
6060
6145
  server.registerTool("taiga_get_product_context", {
6061
6146
  title: "Get product context",
6062
- 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 status: '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.`,
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.`,
6063
6148
  inputSchema: z.object({
6064
6149
  productId: productId.optional(),
6065
6150
  slug: slug.optional(),
@@ -22136,29 +22221,19 @@ function judgeItem(until, gate) {
22136
22221
  /** The initiative as a wait reports it: where it is, whose turn, its latest run and its criteria. */
22137
22222
  function itemReport(item) {
22138
22223
  const run = latestBuildOf(item);
22139
- const situation = situationOf(item);
22140
22224
  return {
22141
- ...situation ? { situation } : {},
22142
22225
  ...pickFields([item], [
22143
22226
  "id",
22144
22227
  "title",
22145
- "state",
22146
- "planningStatus",
22147
- "needsHumanCause",
22148
- "needsHumanDetail",
22149
22228
  "autoBuild"
22150
22229
  ])[0],
22151
- lifecycle: pickFields([lifecycleOf(item)], [
22152
- "turn",
22153
- "stage",
22154
- "cta",
22155
- "planFailureCause"
22156
- ])[0],
22157
- latestBuild: run ? pickFields([run], [
22230
+ ...standing(item),
22231
+ latestRun: run ? pickFields([run], [
22158
22232
  "id",
22159
22233
  "status",
22160
22234
  "completedSteps",
22161
22235
  "totalSteps",
22236
+ "prNumber",
22162
22237
  "prUrl",
22163
22238
  "prState",
22164
22239
  "prMerged",
@@ -22342,6 +22417,10 @@ function withRecoveryHints(tool, schema, inputs, isRead) {
22342
22417
  message: `${withStop(issue.message)} ${pointers.join(" ")}`
22343
22418
  };
22344
22419
  }
22420
+ if (issue.code === "invalid_value") return {
22421
+ ...issue,
22422
+ message: `${withStop(issue.message)} Pass one of those values exactly.`
22423
+ };
22345
22424
  const argument = issue.path?.length === 1 ? String(issue.path[0]) : void 0;
22346
22425
  const source = argument ? idSource(argument) : void 0;
22347
22426
  if (source && /received undefined/.test(issue.message)) return {
@@ -22490,7 +22569,7 @@ function registerAllTools(server, client, options = {}) {
22490
22569
  function registerWhoami(server, client, registered) {
22491
22570
  server.registerTool("taiga_whoami", {
22492
22571
  title: "Who am I",
22493
- 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.",
22494
22573
  inputSchema: z.object({}),
22495
22574
  annotations: READ_ONLY
22496
22575
  }, () => runTool(async () => {
@@ -22498,6 +22577,7 @@ function registerWhoami(server, client, registered) {
22498
22577
  const tools = [...registered.entries()].filter(([name]) => name !== "taiga_whoami");
22499
22578
  const named = (pick) => tools.filter(([, notes]) => pick(notes)).map(([name]) => name);
22500
22579
  return toolResult({
22580
+ api: client.apiHost,
22501
22581
  email: me?.email ?? null,
22502
22582
  role: me?.role ?? null,
22503
22583
  scopes: me?.scopes ?? null,
@@ -22540,8 +22620,8 @@ const SERVER_INSTRUCTIONS = [
22540
22620
  "conversation that specifies a new product. A factory owns products.",
22541
22621
  "",
22542
22622
  "**IDs** are UUIDs; the product, factory and initiative lists take query to find one by name. A NOT_FOUND names",
22543
- "the tool that lists the right ones. Lists page with nextCursor; taiga_list_initiatives lists open ones unless",
22544
- "status: 'all'. outcome, situation and nextStep are one-sentence summaries; setup writes return readiness.",
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.",
22545
22625
  "Product documents have two timelines: plan (intent) and reality (from the code).",
22546
22626
  "",
22547
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.486",
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",