@taiga-ai/mcp-server 0.28.0-dev.597 → 0.28.0-dev.599

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 +96 -16
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -22484,8 +22484,11 @@ var TaigaClient = class {
22484
22484
  return this.request("POST", `/builds/${seg(id)}/stop`);
22485
22485
  }
22486
22486
  /** Ask the coding agent for changes to the run's pull request (PRO-3035). */
22487
- async requestBuildRunChanges(id, notes) {
22488
- return this.request("POST", `/builds/${seg(id)}/request-changes`, { notes });
22487
+ async requestBuildRunChanges(id, notes, opts = {}) {
22488
+ return this.request("POST", `/builds/${seg(id)}/request-changes`, {
22489
+ notes,
22490
+ ...opts.reviewPlan !== void 0 ? { reviewPlan: opts.reviewPlan } : {}
22491
+ });
22489
22492
  }
22490
22493
  async listBuildRunSteps(id) {
22491
22494
  return this.request("GET", `/builds/${seg(id)}/steps`);
@@ -23579,14 +23582,16 @@ object({
23579
23582
  headSha: lenient(string$2()),
23580
23583
  file: lenient(string$2()),
23581
23584
  failingChecks: lenient(lenientList(storedFailingCheck)),
23582
- blockers: lenient(lenientList(_enum(READY_BLOCKERS)))
23585
+ blockers: lenient(lenientList(_enum(READY_BLOCKERS))),
23586
+ detail: lenient(string$2())
23583
23587
  }).transform((e) => ({
23584
23588
  reason: e.reason,
23585
23589
  ...e.rounds === void 0 ? {} : { rounds: e.rounds },
23586
23590
  ...e.headSha === void 0 ? {} : { headSha: e.headSha },
23587
23591
  ...e.file === void 0 ? {} : { file: e.file },
23588
23592
  ...e.failingChecks === void 0 ? {} : { failingChecks: e.failingChecks },
23589
- ...e.blockers === void 0 ? {} : { blockers: e.blockers }
23593
+ ...e.blockers === void 0 ? {} : { blockers: e.blockers },
23594
+ ...e.detail === void 0 ? {} : { detail: e.detail }
23590
23595
  }));
23591
23596
  /**
23592
23597
  * What a run's hardening came to, coverage included (PRO-2829). `passed`
@@ -27309,7 +27314,7 @@ function handbackSaid(escalation) {
27309
27314
  case "review_bot_cap": return `review bots still have open comments${rounds}`;
27310
27315
  case "ci_never_settled": return "CI did not finish in time";
27311
27316
  case "review_fix_stalled": return "Taiga could not fix some review comments; its replies say what it tried";
27312
- case "changes_not_planned": return "the planner did not plan the request for changes, so the pull request is as it was";
27317
+ case "changes_not_planned": return `the planner did not plan the request for changes${typeof e.detail === "string" && e.detail ? ` (${e.detail})` : ""}, so the pull request is as it was`;
27313
27318
  case "ready_blocked": return `CI is green, but Taiga kept the pull request a draft${blockers.length ? `: ${blockers.join("; ")}` : ""}`;
27314
27319
  case "check_weakened": return `CI went green only after a fix changed ${typeof e.file === "string" ? e.file : "a CI workflow or a test"}`;
27315
27320
  default: return typeof e.reason === "string" ? e.reason : null;
@@ -27356,16 +27361,34 @@ function runOutcome(run) {
27356
27361
  return {
27357
27362
  disposition,
27358
27363
  outcome: {
27359
- in_flight: status === "pending" ? "Queued: waiting to start." : `Running: ${RUNNING[status] ?? status}${progress}.`,
27364
+ in_flight: status === "pending" ? "Queued: waiting to start." : status === "replanning" ? steerPaused(run["failedReason"], progress) : `Running: ${RUNNING[status] ?? status}${progress}.`,
27360
27365
  gated: handback ? `Handed back to a person: ${handback}. ${pr}${draft ? `${draft},` : ""} waits for them to ${handbackNextStep(run["reviewEscalation"])}.` : `Waiting for a person to review and merge ${pr}${draft}.`,
27361
27366
  succeeded: `Merged: ${pr} is in ${typeof run["baseBranch"] === "string" ? run["baseBranch"] : "the base branch"}.`,
27362
27367
  unmerged: typeof run["prNumber"] === "number" || run["prUrl"] ? `Settled without merging: ${pr} was left unmerged.` : "Settled without a merged pull request.",
27363
27368
  failed: `Failed${why ? `: ${why}` : ""}${progress}.`,
27364
- stopped: `Stopped before it finished${progress}.`,
27369
+ stopped: replanHandedBack(run["failedReason"], why, progress) ?? `Stopped before it finished${progress}.`,
27365
27370
  rejected: `${pr} was closed without merging, so the work did not land.${handback ? ` It had been handed back: ${handback}.` : ""}`
27366
27371
  }[disposition] ?? status
27367
27372
  };
27368
27373
  }
27374
+ /**
27375
+ * A run re-planning for a request for changes (PRO-3161), in one sentence:
27376
+ * the re-plan in flight, or the revised plan waiting for review.
27377
+ */
27378
+ function steerPaused(reason, progress) {
27379
+ if (reason === "steer_review") return `Paused${progress}: the plan revised for a request for changes waits for a person to review it (taiga_review_plan).`;
27380
+ return `Paused after a step${progress}: its plan is being revised for a request for changes.`;
27381
+ }
27382
+ /**
27383
+ * A run its re-plan handed back (PRO-3165), in one sentence with the
27384
+ * planner's reason. Null for any other stop.
27385
+ */
27386
+ function replanHandedBack(reason, why, progress) {
27387
+ const said = why && why !== reason ? `: ${why}` : ".";
27388
+ if (reason === "replan_refused") return `Stopped${progress}, handed back: the planner would not plan the request for changes${said} Resume continues the original plan.`;
27389
+ if (reason === "replan_failed") return `Stopped${progress}, handed back: ${why && why !== reason ? why.replace(/\.?$/, ".") : "the plan could not be revised."} Resume continues the original plan.`;
27390
+ return null;
27391
+ }
27369
27392
  /** Words a sentence does not end at: "e.g." and the like, and a lone initial. */
27370
27393
  const ABBREVIATION = /(?:\b(?:e\.g|i\.e|etc|vs|cf|incl|approx)|(?:^|\s)[A-Za-z])\.$/i;
27371
27394
  /** A finding's first sentence, capped: it ends at . ! or ? before white space, but not at an abbreviation. */
@@ -27710,20 +27733,27 @@ function registerBuildTools(server, client) {
27710
27733
  });
27711
27734
  }));
27712
27735
  if (!client.hasCapability("products:write")) return;
27736
+ const steering = client.hasFeature("steerRuns");
27713
27737
  if (client.hasFeature("requestChanges")) server.registerTool("taiga_request_changes", {
27714
- title: "Request changes on a pull request",
27715
- description: "Tell the coding agent what should change on a run's open pull request, as a person's review would: it splits the request into separate asks, makes the changes on the same pull request, and answers each one there (done, answered, or not done and why). Only for a run whose pull request is waiting for review (the initiative is in review with its pull request open); refused otherwise, and where the organization does not have it yet. taiga_wait follows the run until it is back in review.",
27738
+ title: steering ? "Request changes on a run" : "Request changes on a pull request",
27739
+ description: steering ? "Tell Taiga what should change in a run, while it builds or once its pull request waits for review. The run stops after the step it is building (at once when it is not building one), the planner revises the steps still to build on top of the committed work (adding, editing, dropping, or reverting committed steps exactly), and the run continues with the revised steps; never a new initiative. A request while the run hardens, checks CI or is already being re-planned waits and joins the next re-plan. reviewPlan holds the run until a person has looked at the revised plan (taiga_review_plan approves it or comments to revise it again). A request the planner will not plan, a policy conflict, hands the run back with the reason, still on its original plan. The answer says what happened to it (steer). For a plan that waits for its build to be approved, comment on the plan with taiga_review_plan instead." : "Tell the coding agent what should change on a run's open pull request, as a person's review would: it splits the request into separate asks, makes the changes on the same pull request, and answers each one there (done, answered, or not done and why). Only for a run whose pull request is waiting for review (the initiative is in review with its pull request open); refused otherwise, and where the organization does not have it yet. taiga_wait follows the run until it is back in review.",
27716
27740
  inputSchema: object({
27717
27741
  runId,
27718
- notes: string$2().trim().min(1).max(CHANGE_REQUEST_MAX_CHARS).describe("What should change, in markdown: a list of changes, questions, or both")
27742
+ notes: string$2().trim().min(1).max(CHANGE_REQUEST_MAX_CHARS).describe("What should change, in markdown: a list of changes, questions, or both"),
27743
+ ...steering ? { reviewPlan: boolean().optional().describe("Hold the run until a person has reviewed the revised plan (default false)") } : {}
27719
27744
  }),
27720
27745
  annotations: OPERATE
27721
27746
  }, (params) => runTool(async () => {
27722
- const result = await client.requestBuildRunChanges(params.runId, params.notes);
27747
+ const reviewPlan = "reviewPlan" in params ? params.reviewPlan : void 0;
27748
+ const result = await client.requestBuildRunChanges(params.runId, params.notes, { reviewPlan });
27723
27749
  const row = pickFields([result.data], RUN_OUTCOME_FIELDS)[0];
27724
27750
  return toolResult({
27725
27751
  ...readableRow(row),
27726
- requestId: result.data.requestId
27752
+ requestId: result.data.requestId,
27753
+ ...result.data.steer ? {
27754
+ steer: result.data.steer,
27755
+ situation: steerSituation(result.data.steer)
27756
+ } : {}
27727
27757
  });
27728
27758
  }));
27729
27759
  server.registerTool("taiga_stop_run", {
@@ -27736,6 +27766,12 @@ function registerBuildTools(server, client) {
27736
27766
  return toolResult(readableRow(pickFields([result.data], RUN_OUTCOME_FIELDS)[0]));
27737
27767
  }));
27738
27768
  }
27769
+ /** What happened to a request that steers a run (PRO-3161), in one sentence. */
27770
+ function steerSituation(steer) {
27771
+ if (steer === "replanning") return "The run has paused and its plan is being revised for the request.";
27772
+ if (steer === "after_step") return "The run stops when the step it is building ends, then its plan is revised.";
27773
+ return "The run is hardening, checking CI or already being re-planned; the request joins the next re-plan.";
27774
+ }
27739
27775
  //#endregion
27740
27776
  //#region src/tools/queue.ts
27741
27777
  /** How many entries the read names; past this an entry keeps its initiativeId only. */
@@ -28152,6 +28188,37 @@ async function planFailureOf(client, initiativeId, itemCause) {
28152
28188
  by: typeof actor.name === "string" ? actor.name : actor.type ?? null
28153
28189
  };
28154
28190
  }
28191
+ /**
28192
+ * What a request for changes did to this plan (PRO-3161), when one revised it:
28193
+ * the steps the re-plan added, edited and dropped, and the committed steps it
28194
+ * reverts, from the `run_replanned` event that made this version. Null for a
28195
+ * plan no request revised, or when the history cannot be read.
28196
+ */
28197
+ async function planRevisionOf(client, initiativeId, planId) {
28198
+ let events;
28199
+ try {
28200
+ events = (await client.listBacklogItemActivity(initiativeId)).data;
28201
+ } catch (error) {
28202
+ if (error instanceof TaigaApiError || isNetworkFailure(error)) return null;
28203
+ throw error;
28204
+ }
28205
+ for (const event of [...events].reverse()) {
28206
+ if (event.kind !== "turn" || event.trigger !== "run_replanned") continue;
28207
+ const data = event.data ?? {};
28208
+ if (data["planId"] !== planId) continue;
28209
+ return {
28210
+ revisedFor: "a request for changes",
28211
+ previousPlanId: data["previousPlanId"] ?? null,
28212
+ added: data["added"] ?? [],
28213
+ edited: data["edited"] ?? [],
28214
+ dropped: data["dropped"] ?? [],
28215
+ reverted: data["reverted"] ?? [],
28216
+ waitsForReview: data["reviewPlan"] === true,
28217
+ at: event.occurredAt ?? null
28218
+ };
28219
+ }
28220
+ return null;
28221
+ }
28155
28222
  const planId = idInput("plan", "taiga_get_plan gives the initiative's active plan and its ID");
28156
28223
  const initiativeId = idInput("initiative", "taiga_list_initiatives (query finds one by title) gives it");
28157
28224
  /** The plan without its steps' prose: enough to approve it or say what a run will do. */
@@ -28170,7 +28237,9 @@ const PLAN_STEP_SUMMARY_FIELDS = [
28170
28237
  "dependencies",
28171
28238
  "writes",
28172
28239
  "reads",
28173
- "createsResources"
28240
+ "createsResources",
28241
+ "revertsSteps",
28242
+ "changeRequestIds"
28174
28243
  ];
28175
28244
  /** The outcome fields of an initiative, with how many of its criteria are met, and where it stands. */
28176
28245
  const outcomeOf = (item) => withSituation(item, {
@@ -28290,7 +28359,7 @@ function registerBacklogReads(server, client) {
28290
28359
  }));
28291
28360
  server.registerTool("taiga_get_plan", {
28292
28361
  title: "Get the active plan",
28293
- description: "Read an initiative's active plan, step by step: what each builds, the files it writes and reads, and what it depends on. Enough to approve or reject it; full: true adds each step's complete instructions (long). NOT_FOUND when there is no active plan; the initiative's situation says why.",
28362
+ description: "Read an initiative's active plan, step by step: what each builds, the files it writes and reads, and what it depends on. Enough to approve or reject it; full: true adds each step's complete instructions (long). A plan a request for changes revised says so in revision: the steps it added, edited and dropped, and the committed steps it reverts. NOT_FOUND when there is no active plan; the initiative's situation says why.",
28294
28363
  inputSchema: object({
28295
28364
  initiativeId,
28296
28365
  full: boolean().optional().describe("Carry each step's complete instructions (default false: a summary)")
@@ -28298,10 +28367,16 @@ function registerBacklogReads(server, client) {
28298
28367
  annotations: READ_ONLY
28299
28368
  }, (params) => runTool(async () => {
28300
28369
  const plan = (await client.getActivePlan(params.initiativeId)).data;
28301
- if (params.full) return toolResult(plan);
28370
+ const revision = await planRevisionOf(client, params.initiativeId, plan.id);
28371
+ const revised = revision ? { revision } : {};
28372
+ if (params.full) return toolResult({
28373
+ ...plan,
28374
+ ...revised
28375
+ });
28302
28376
  const steps = Array.isArray(plan.steps) ? plan.steps : [];
28303
28377
  return toolResult({
28304
28378
  ...pickFields([plan], PLAN_SUMMARY_FIELDS)[0],
28379
+ ...revised,
28305
28380
  steps: pickFields(steps, PLAN_STEP_SUMMARY_FIELDS)
28306
28381
  });
28307
28382
  }));
@@ -28465,7 +28540,7 @@ function registerResume(server, client) {
28465
28540
  function registerPlanReview(server, client) {
28466
28541
  server.registerTool("taiga_review_plan", {
28467
28542
  title: "Approve or reject a plan",
28468
- description: "Give an initiative's plan its verdict. approve starts the run that builds it; reject sends it back with a comment and the planner writes a new one from it. Read the plan first (taiga_get_plan) and pass its ID; refused if the plan was replaced since, a new one is being written, or a run is already going.",
28543
+ description: "Give an initiative's plan its verdict. approve starts the run that builds it; reject sends it back with a comment and the planner revises it from the comment, keeping what the comment does not object to. Read the plan first (taiga_get_plan) and pass its ID; refused if the plan was replaced since, a new one is being written, or a run is already going." + (client.hasFeature("steerRuns") ? " A plan a request for changes revised while its run waits for this review (taiga_request_changes with reviewPlan): approve continues that run on its branch, and reject comments to revise it again." : ""),
28469
28544
  inputSchema: object({
28470
28545
  planId,
28471
28546
  verdict: _enum(["approve", "reject"]).describe("approve builds the plan; reject sends it back"),
@@ -45490,6 +45565,7 @@ const RUN_REPORT_FIELDS = [
45490
45565
  ];
45491
45566
  const runSignature = (run) => JSON.stringify([
45492
45567
  run.status,
45568
+ run.failedReason,
45493
45569
  run.completedSteps,
45494
45570
  run.prUrl,
45495
45571
  run.prState,
@@ -45502,6 +45578,10 @@ function judgeBuild(until) {
45502
45578
  reached: false,
45503
45579
  stoppedBecause: `the run is ${String(run.status)}`
45504
45580
  };
45581
+ if (until !== "changed" && run.status === "replanning" && run.failedReason === "steer_review") return {
45582
+ reached: false,
45583
+ stoppedBecause: "the run waits for a person to review its revised plan (taiga_review_plan)"
45584
+ };
45505
45585
  return null;
45506
45586
  };
45507
45587
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ai/mcp-server",
3
- "version": "0.28.0-dev.597",
3
+ "version": "0.28.0-dev.599",
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
  "license": "UNLICENSED",