@taiga-ai/mcp-server 0.26.0-dev.481 → 0.26.0-dev.482

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 +93 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1763,6 +1763,70 @@ const MAINTENANCE_IGNORE_REASONS = [
1763
1763
  "accepted_risk",
1764
1764
  "wont_fix"
1765
1765
  ];
1766
+ /**
1767
+ * What one lockfile's group list says, read against what its osv-scanner
1768
+ * (osv-scalibr) extractor actually writes. Checked per extractor, because the
1769
+ * same list means different things in different files:
1770
+ *
1771
+ * - package-lock.json / npm-shrinkwrap.json: every package entry carries npm's
1772
+ * `dev` / `optional` / `devOptional` flags, transitive ones included, so an
1773
+ * EMPTY list is a runtime dependency, `dev` alone is dev-only, `optional`
1774
+ * alone is shipped, and `dev` + `optional` (devOptional) is left undecided.
1775
+ * - pnpm-lock.yaml: `dev` comes from a per-package `dev: true` that lockfile
1776
+ * v9 no longer writes, so an empty list says nothing; `dev` still means dev.
1777
+ * - poetry.lock: group names with `main` dropped; a non-main, non-optional
1778
+ * group is not in the runtime install. Poetry 1.x lockfiles carry no groups
1779
+ * at all, so an empty list says nothing.
1780
+ * - uv.lock: only direct members of a group are tagged, so an empty list says
1781
+ * nothing; `dev` means dev.
1782
+ * - yarn.lock, go.mod/go.sum, Cargo.lock, Gemfile.lock and the rest carry no
1783
+ * group information: unknown whatever the list says.
1784
+ *
1785
+ * Anything this cannot place is `unknown`, never `runtime`: a wrong "runtime"
1786
+ * on a build tool is the claim this exists to stop, and a wrong "dev only" on a
1787
+ * shipped package is worse.
1788
+ */
1789
+ function lockfileScope(path, groups) {
1790
+ const name = path.slice(path.lastIndexOf("/") + 1);
1791
+ const set = new Set(groups.map((g) => g.trim().toLowerCase()).filter((g) => g !== ""));
1792
+ switch (name) {
1793
+ case "package-lock.json":
1794
+ case "npm-shrinkwrap.json": {
1795
+ if (set.size === 0) return "runtime";
1796
+ const dev = set.has("dev");
1797
+ const optional = set.has("optional");
1798
+ if (dev && optional) return "unknown";
1799
+ if (dev) return "dev";
1800
+ if (optional && set.size === 1) return "runtime";
1801
+ return "unknown";
1802
+ }
1803
+ case "pnpm-lock.yaml":
1804
+ case "uv.lock": return set.size === 1 && set.has("dev") ? "dev" : "unknown";
1805
+ case "poetry.lock": return set.size > 0 && !set.has("optional") && !set.has("main") ? "dev" : "unknown";
1806
+ default: return "unknown";
1807
+ }
1808
+ }
1809
+ /**
1810
+ * Whether a dependency finding sits only in development dependencies, in at
1811
+ * least one runtime dependency set, or in lockfiles that do not say. Decided
1812
+ * per lockfile (`lockfileScope`) and combined pessimistically: one runtime
1813
+ * lockfile makes the finding runtime, one lockfile that cannot say makes it
1814
+ * unknown, and only all-dev reads as dev. Groups the sweep did not record
1815
+ * (older images, code kinds) are unknown. Presentation only: the tier and the
1816
+ * auto-remediation gate do not read this, and a malicious package runs its
1817
+ * install script in any group.
1818
+ */
1819
+ function dependencyScope(finding) {
1820
+ if (finding.kind !== "vulnerable_dependency" && finding.kind !== "outdated_dependency") return "unknown";
1821
+ const entries = Object.entries(finding.dependencyGroups ?? {});
1822
+ if (entries.length === 0) return "unknown";
1823
+ return combineScopes(entries.map(([path, groups]) => lockfileScope(path, Array.isArray(groups) ? groups : [])));
1824
+ }
1825
+ function combineScopes(scopes) {
1826
+ if (scopes.includes("runtime")) return "runtime";
1827
+ if (scopes.includes("unknown") || scopes.length === 0) return "unknown";
1828
+ return "dev";
1829
+ }
1766
1830
  new Set(CODE_REMEDIATION_KINDS);
1767
1831
  /** A non-empty string, or null. */
1768
1832
  const locationText = z.string().min(1).nullable().catch(null);
@@ -4892,6 +4956,30 @@ function withFixability(row) {
4892
4956
  };
4893
4957
  return row;
4894
4958
  }
4959
+ /**
4960
+ * Dev-only, runtime or unknown, said on a dependency row (PRO-2861) in place of the raw per-lockfile groups
4961
+ * the API carries: the groups need the lockfile name to read, which `dependencyScope` does once here.
4962
+ */
4963
+ function withDependencyScope(row, { keepGroups = false } = {}) {
4964
+ const { dependencyGroups: _groups, ...lean } = row;
4965
+ const rest = keepGroups ? row : lean;
4966
+ if (!(row["kind"] === "vulnerable_dependency" || row["kind"] === "outdated_dependency")) return rest;
4967
+ return {
4968
+ ...rest,
4969
+ dependencyScope: dependencyScope({
4970
+ kind: row["kind"],
4971
+ dependencyGroups: row["dependencyGroups"]
4972
+ })
4973
+ };
4974
+ }
4975
+ /** A finding as a tool answers it: the row fields, the scope, and whether Fix would take it. */
4976
+ function findingRows(findings, extra = []) {
4977
+ return pickFields(findings, [
4978
+ ...FINDING_ROW_FIELDS,
4979
+ "dependencyGroups",
4980
+ ...extra
4981
+ ]).map((row) => withDependencyScope(row)).map(withFixability);
4982
+ }
4895
4983
  /** The parts of an advisory record an agent reads; the rest (every affected range, every reference) is full: true. */
4896
4984
  const ADVISORY_KEPT = [
4897
4985
  "id",
@@ -5015,7 +5103,7 @@ function registerMaintenanceTools(server, client) {
5015
5103
  sweep: null,
5016
5104
  sweepNote: "The sweep overview could not be read: an empty list here is not an all-clear."
5017
5105
  } : sweep ? { sweep } : {},
5018
- ...page(pickFields(result.data, FINDING_ROW_FIELDS).map(withFixability), result.nextCursor),
5106
+ ...page(findingRows(result.data), result.nextCursor),
5019
5107
  ...typeof result.totalCount === "number" ? { totalCount: result.totalCount } : {},
5020
5108
  ...params.status === void 0 && params.severity === void 0 && params.kind === void 0 && typeof result.totalCount === "number" && sweep && sweep !== "unread" && typeof sweep["openFindings"] === "number" && sweep["openFindings"] !== result.totalCount ? { totalNote: "totalCount includes findings with a fix under way (remediating) and ignored findings whose ignore has expired; sweep.openFindings counts open ones only." } : {}
5021
5109
  });
@@ -5030,10 +5118,10 @@ function registerMaintenanceTools(server, client) {
5030
5118
  annotations: READ_ONLY
5031
5119
  }, (params) => runTool(async () => {
5032
5120
  const { runId, ...raw } = (await client.getFinding(params.findingId)).data;
5033
- const finding = withFixability(params.full ? raw : {
5121
+ const finding = withFixability(params.full ? withDependencyScope(raw, { keepGroups: true }) : withDependencyScope({
5034
5122
  ...raw,
5035
5123
  data: leanAdvisory(raw["data"])
5036
- });
5124
+ }));
5037
5125
  return toolResult(runId === void 0 ? finding : {
5038
5126
  sweepId: runId,
5039
5127
  ...finding
@@ -5120,7 +5208,7 @@ function registerMaintenanceTools(server, client) {
5120
5208
  ...params.expiresAt !== void 0 ? { ignoreExpiresAt: params.expiresAt } : {}
5121
5209
  } : { status: "open" };
5122
5210
  const finding = (await client.updateFinding(params.findingId, body)).data;
5123
- return toolResult(pickFields([finding], [...FINDING_ROW_FIELDS, "ignoreNote"])[0]);
5211
+ return toolResult(findingRows([finding], ["ignoreNote"])[0]);
5124
5212
  }));
5125
5213
  server.registerTool("taiga_fix_finding", {
5126
5214
  title: "Fix a finding",
@@ -5136,7 +5224,7 @@ function registerMaintenanceTools(server, client) {
5136
5224
  const fixed = (await client.remediateFinding(params.productId, params.findingId)).data;
5137
5225
  return toolResult({
5138
5226
  backlogItemId: fixed.backlogItemId,
5139
- finding: fixed.finding ? pickFields([fixed.finding], FINDING_ROW_FIELDS)[0] : null
5227
+ finding: fixed.finding ? findingRows([fixed.finding])[0] : null
5140
5228
  });
5141
5229
  }
5142
5230
  const finding = (await client.getFinding(params.findingId)).data;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ai/mcp-server",
3
- "version": "0.26.0-dev.481",
3
+ "version": "0.26.0-dev.482",
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",