@taiga-ai/mcp-server 0.26.0-dev.516 → 0.26.0-dev.520

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 +61 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4221,6 +4221,20 @@ const RUN_OUTCOME_FIELDS = [
4221
4221
  * stalled in the hardening pass after the steps and the retry continues it.
4222
4222
  * Null for a run still going or that finished.
4223
4223
  */
4224
+ /**
4225
+ * The line of a failed step's error that says why (PRO-2941): a review found the reason buried in a
4226
+ * 2,000-character dump of git and lint output, behind runtime warnings. The first line that reads as an error,
4227
+ * else the first line; capped. Null when no step failed with an error.
4228
+ */
4229
+ function failureCause(steps) {
4230
+ const failed = steps.find((step) => step["status"] === "failed" && typeof step["error"] === "string");
4231
+ if (!failed) return null;
4232
+ const lines = String(failed["error"]).split("\n").map((line) => line.trim()).filter((line) => line && !/^\(node:\d+\)|^\(Use `node|^[⋯✔↓]/u.test(line));
4233
+ const telling = lines.find((line) => /\berror\b|cannot|not found|denied|refused|no such/iu.test(line)) ?? lines.find((line) => /fail|✖/iu.test(line)) ?? lines[0];
4234
+ if (!telling) return null;
4235
+ const said = telling.length > 200 ? `${telling.slice(0, 197)}...` : telling;
4236
+ return `step ${String(failed["stepOrder"])}: ${said}`;
4237
+ }
4224
4238
  function stoppedIn(run, steps) {
4225
4239
  if (run.prMerged === true || run.status !== "failed" && run.status !== "stopped") return null;
4226
4240
  if (!steps.some((step) => step.status !== "planned")) return "before the first step started, while setting up the run (cloning, preparing the workspace)";
@@ -4625,7 +4639,7 @@ function registerBuildTools(server, client) {
4625
4639
  }));
4626
4640
  server.registerTool("taiga_get_run", {
4627
4641
  title: "Get run",
4628
- description: "Read one coding-agent run: status and outcome (one sentence), pull request, hardening, and each step with its status, commit, files, verification and headline. scanCoverage names security scanners that did not run, and why. verification is Taiga's check after a step (tests, lint, build); verificationNote says when one failed throughout. A failed or stopped run has stoppedIn and failedReasonMeans. full: true adds the complete instructions and reports. To resume: taiga_resume_initiative; to stop: move the initiative out of Build. A maintenance run has no initiative and is retried from its finding.",
4642
+ description: "Read one coding-agent run: status and outcome (one sentence), pull request, hardening, and each step with its status, commit, files, verification and headline. scanCoverage names security scanners that did not run, and why. verification is Taiga's check after a step (tests, lint, build); verificationNote says when one failed throughout. A failed or stopped run has stoppedIn, failedReasonMeans and cause (the line of the failed step's error that says why). full: true adds the complete instructions and reports. To resume: taiga_resume_initiative; to stop: move the initiative out of Build. A maintenance run has no initiative and is retried from its finding.",
4629
4643
  inputSchema: z.object({
4630
4644
  runId,
4631
4645
  full: z.boolean().optional().describe("Carry each step's complete instructions and report (default false: a summary)")
@@ -4636,7 +4650,11 @@ function registerBuildTools(server, client) {
4636
4650
  const runRow = run.data;
4637
4651
  const rows = steps.data.map((step) => neverStarted(step));
4638
4652
  const where = stoppedIn(runRow, rows);
4639
- const stop = where ? { stoppedIn: where } : {};
4653
+ const cause = failureCause(rows);
4654
+ const stop = {
4655
+ ...where ? { stoppedIn: where } : {},
4656
+ ...cause ? { cause } : {}
4657
+ };
4640
4658
  const states = await initiativeStates(client, [runRow]);
4641
4659
  const readable = withInitiativeState(readableRun(runRow), states);
4642
4660
  const note = verificationNote(rows);
@@ -4818,7 +4836,7 @@ async function queueView(client, product, options = { name: "all" }) {
4818
4836
  return {
4819
4837
  linePaused: paused === null ? null : typeof paused === "string",
4820
4838
  entries: rows.map(({ lifecycle: _lifecycle, runId: _runId, ...row }) => row),
4821
- waitingOn: line ?? waitingOn(rows, idle ? station : null),
4839
+ waitingOn: line ?? waitingOn(rows, idle ? station : null) ?? (rows.length === 0 && "offTheLine" in offLine ? `Nothing is queued. ${offLine.offTheLine}` : null),
4822
4840
  ...offLine,
4823
4841
  ...note ? { note } : {}
4824
4842
  };
@@ -5484,12 +5502,31 @@ function groupKey(row) {
5484
5502
  const rule = typeof location?.ruleId === "string" ? location.ruleId : null;
5485
5503
  return rule ? `${String(row["kind"])}|${String(row["severity"])}|${status}|${link}|${rule}` : null;
5486
5504
  }
5487
- /** Where one finding is, in a few characters: its path and line, or its package. */
5505
+ /**
5506
+ * Where one finding is, in a few characters: its file and line (and the resource a scanner names), or its package.
5507
+ * A review read seven "CKV_GHA_7" where the location keeps no path: the file is in sourcePaths.
5508
+ */
5488
5509
  function whereOf(row) {
5489
5510
  const location = row["location"];
5490
- if (typeof location?.path === "string") return location.line ? `${location.path}:${String(location.line)}` : location.path;
5511
+ const paths = Array.isArray(row["sourcePaths"]) ? row["sourcePaths"] : [];
5512
+ const file = typeof location?.path === "string" ? location.path : typeof paths[0] === "string" ? paths[0] : null;
5513
+ if (file) {
5514
+ const at = location?.line ? `${file}:${String(location.line)}` : file;
5515
+ return typeof location?.resource === "string" ? `${at} ${location.resource}` : at;
5516
+ }
5491
5517
  return [row["packageName"], row["currentVersion"]].filter(Boolean).join(" ") || String(row["id"]);
5492
5518
  }
5519
+ /** The words every member's title opens with, at a word boundary; empty when they share fewer than 20 characters. */
5520
+ function sharedTitle(titles) {
5521
+ let prefix = titles[0] ?? "";
5522
+ for (const title of titles.slice(1)) {
5523
+ let at = 0;
5524
+ while (at < prefix.length && prefix[at] === title[at]) at += 1;
5525
+ prefix = prefix.slice(0, at);
5526
+ }
5527
+ const cut = (prefix.length === (titles[0] ?? "").length ? prefix : prefix.slice(0, Math.max(0, prefix.lastIndexOf(" ")))).trim().replace(/[\s,(:;-]+$/, "");
5528
+ return cut.length >= 20 ? cut : "";
5529
+ }
5493
5530
  /**
5494
5531
  * Findings of one rule or license verdict on a page, folded into one row (PRO-2941): a review read seven
5495
5532
  * CKV_GHA_7 rows and fifteen sharp license rows, identical but for where. The first stands for them, with
@@ -5517,12 +5554,14 @@ function grouped(rows, fold) {
5517
5554
  if (members.length === 1) return members[0];
5518
5555
  const where = members.map(whereOf);
5519
5556
  const first = members[0];
5520
- const shared = Object.fromEntries(Object.entries(first).filter(([key, value]) => key !== "id" && members.every((member) => JSON.stringify(member[key]) === JSON.stringify(value))));
5557
+ const shared = Object.fromEntries(Object.entries(first).filter(([key, value]) => key !== "id" && key !== "title" && members.every((member) => JSON.stringify(member[key]) === JSON.stringify(value))));
5521
5558
  const location = first["location"];
5522
- const said = first["kind"] === "license_violation" ? licenseVerdict(first) : typeof location?.ruleId === "string" ? location.ruleId : String(first["title"]);
5559
+ const words = sharedTitle(members.map((member) => String(member["title"] ?? "")));
5560
+ const rule = typeof location?.ruleId === "string" ? location.ruleId : null;
5561
+ const said = first["kind"] === "license_violation" ? licenseVerdict(first) : words ? rule && !words.includes(rule) ? `${rule}: ${words}` : words : rule ?? String(first["title"]);
5523
5562
  return {
5524
5563
  ...shared,
5525
- ...shared["title"] === void 0 ? { title: said } : {},
5564
+ title: said,
5526
5565
  count: members.length,
5527
5566
  findingIds: members.map((member) => member["id"]),
5528
5567
  where: where.slice(0, GROUP_WHERE),
@@ -5661,8 +5700,11 @@ function standingSummary(standing) {
5661
5700
  bySeverity[severity] = (bySeverity[severity] ?? 0) + 1;
5662
5701
  }
5663
5702
  const stated = (state) => controls.filter((c) => c["state"] === state).length;
5703
+ const withAny = (key) => controls.filter((control) => Number(control[key] ?? 0) > 0).length;
5664
5704
  return {
5665
- controls: listed.length,
5705
+ controlsWithViolations: listed.length,
5706
+ controlsBreached: withAny("findingCount"),
5707
+ controlsDiverged: withAny("divergenceCount"),
5666
5708
  controlsInPolicies: controls.length,
5667
5709
  controlsMet: stated("met"),
5668
5710
  controlsNotApplicable: stated("not_applicable"),
@@ -6148,6 +6190,8 @@ function isDue(row, now) {
6148
6190
  }
6149
6191
  /** Pages read for the summary; past this it says it counted only those. */
6150
6192
  const SUMMARY_PAGE = 100;
6193
+ /** Rows an org-wide first page carries when the caller names no limit: the summary answers the rest. */
6194
+ const ORG_WIDE_ROWS = 10;
6151
6195
  /** Pages the summary reads before it says it stopped: 1,000 items. */
6152
6196
  const SUMMARY_PAGES = 10;
6153
6197
  /** Every item the summary counts, up to SUMMARY_PAGES pages; nextCursor set when it stopped short. Null on failure. */
@@ -6269,8 +6313,10 @@ function registerInboxTools(server, client) {
6269
6313
  ...resolved ? { resolved: true } : {}
6270
6314
  };
6271
6315
  const first = params.cursor === void 0;
6316
+ const limit = params.limit ?? (params.productId ? void 0 : ORG_WIDE_ROWS);
6272
6317
  const result = await client.listInbox({
6273
6318
  ...query,
6319
+ ...limit !== void 0 ? { limit } : {},
6274
6320
  ...first ? { withTotal: true } : {}
6275
6321
  });
6276
6322
  const overview = !first || resolved ? null : !result.nextCursor ? result : await everyItem(client, query);
@@ -6309,7 +6355,7 @@ function registerInboxTools(server, client) {
6309
6355
  ...checked.unchecked > 0 ? (() => {
6310
6356
  const onPage = due.filter((row) => checked.uncheckedIds.has(String(row["id"]))).length;
6311
6357
  const beyond = checked.unchecked - onPage;
6312
- return { staleNote: `${[...onPage > 0 ? [`${onPage} review${onPage === 1 ? "" : "s"} on this page could not be checked for whether the subject moved on: those rows say staleChecked: false, and their readWith read tells`] : [], ...beyond > 0 ? [`${beyond} review${beyond === 1 ? "" : "s"} counted in the summary but not on this page could not be checked either, so the summary counts them open`] : []].join("; ")}.` };
6358
+ return { staleNote: `${[...onPage > 0 ? [`${onPage} review${onPage === 1 ? "" : "s"} on this page could not be checked for whether the subject moved on: those rows say staleChecked: false, and their readWith read tells`] : [], ...beyond > 0 ? [`${beyond} review${beyond === 1 ? "" : "s"} past this page could not be checked, so the summary counts them as open`] : []].join("; ")}.` };
6313
6359
  })() : {}
6314
6360
  });
6315
6361
  }));
@@ -6700,13 +6746,14 @@ async function standingPart(client, productId) {
6700
6746
  const standing = (await client.getPolicyStanding(productId)).data;
6701
6747
  const summary = standingSummary(standing);
6702
6748
  const state = standing.assessment?.state;
6703
- const controls = Array.isArray(standing.controls) ? standing.controls : [];
6704
- const withAny = (key) => controls.filter((control) => Number(control[key] ?? 0) > 0).length;
6705
6749
  return {
6706
6750
  openViolations: summary.violations,
6707
6751
  bySeverity: summary.bySeverity,
6708
- controlsBreached: withAny("findingCount"),
6709
- controlsDiverged: withAny("divergenceCount"),
6752
+ controlsInPolicies: summary.controlsInPolicies,
6753
+ controlsNotAssessed: summary.controlsNotAssessed,
6754
+ controlsWithViolations: summary.controlsWithViolations,
6755
+ controlsBreached: summary.controlsBreached,
6756
+ controlsDiverged: summary.controlsDiverged,
6710
6757
  controlsMet: summary.controlsMet,
6711
6758
  controlsNotApplicable: summary.controlsNotApplicable,
6712
6759
  ...state === "never" || state === "failed" ? { note: `The assessment ${state === "never" ? "has never run" : "failed"}: no violations here is not an all-clear.` } : {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ai/mcp-server",
3
- "version": "0.26.0-dev.516",
3
+ "version": "0.26.0-dev.520",
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",