@taiga-ai/mcp-server 0.36.0-rc.892 → 0.36.0-rc.926

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 +337 -18
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -24687,6 +24687,19 @@ const PRODUCT_DOCUMENT_DISPLAY_NAMES = {
24687
24687
  const PRODUCT_DOCUMENT_ORIGINS = ["plan", "reality"];
24688
24688
  /** The forges a repository link can name: `FORGE_PROVIDERS`, under the name links have always used. */
24689
24689
  const VCS_PROVIDERS = FORGE_PROVIDERS;
24690
+ Object.fromEntries(Object.entries({
24691
+ specification: "specification",
24692
+ "user-flows": "user_flow",
24693
+ architecture: "architecture",
24694
+ "tech-decisions": "technology_decisions",
24695
+ "data-flow": "data_flow",
24696
+ "threat-model": "threat_model",
24697
+ dpia: "privacy_assessment",
24698
+ "risk-register": "risk_register",
24699
+ "service-blueprint": "service_blueprint",
24700
+ "ui-direction": "ui_direction",
24701
+ "codebase-snapshot": "codebase_snapshot"
24702
+ }).map(([slug, apiType]) => [apiType, slug]));
24690
24703
  //#endregion
24691
24704
  //#region ../../packages/types/dist/route-key.js
24692
24705
  /**
@@ -25478,6 +25491,7 @@ discriminatedUnion("kind", [
25478
25491
  })).max(20),
25479
25492
  moreFailed: number$1().int().min(0).optional(),
25480
25493
  observedAt: optionalMoment,
25494
+ repository: string$2().min(1).max(255).optional(),
25481
25495
  stepId: optionalUuid
25482
25496
  }),
25483
25497
  object({
@@ -26070,6 +26084,92 @@ object({
26070
26084
  ...outcomes === void 0 ? {} : { outcomes }
26071
26085
  }));
26072
26086
  //#endregion
26087
+ //#region ../../packages/types/dist/registries.js
26088
+ /**
26089
+ * The package ecosystems a registry can serve: every ecosystem the coding
26090
+ * agent builds (its skills and its image), keyed by the install tooling that
26091
+ * reads the credential. `maven` is a Maven repository, and Maven builds get
26092
+ * its credential (PRO-2916); Gradle builds name theirs in their own build
26093
+ * scripts and get none yet (PRO-2928).
26094
+ */
26095
+ const REGISTRY_ECOSYSTEMS = [
26096
+ "npm",
26097
+ "terraform",
26098
+ "pypi",
26099
+ "nuget",
26100
+ "go",
26101
+ "cargo",
26102
+ "maven",
26103
+ "rubygems",
26104
+ "composer"
26105
+ ];
26106
+ const REGISTRY_VENDORS = [
26107
+ "npmjs",
26108
+ "github",
26109
+ "gcp_artifact",
26110
+ "generic",
26111
+ "hcp_terraform",
26112
+ "scalr",
26113
+ "azure_artifacts",
26114
+ "jfrog",
26115
+ "git_host",
26116
+ "aws_codeartifact"
26117
+ ];
26118
+ /** An IAM role ARN, as the CodeArtifact credential takes it. */
26119
+ const AWS_ROLE_ARN_PATTERN = /^arn:aws:iam::\d{12}:role\/[\w+=,.@/-]{1,512}$/;
26120
+ //#endregion
26121
+ //#region ../../packages/types/dist/gitlab.js
26122
+ /**
26123
+ * GitLab connections (PRO-3096, P-PRO-150): the shared request/response
26124
+ * contract of `/v1/gitlab/connections`. A connection is one GitLab host
26125
+ * (GitLab.com, a self-managed instance or GitLab Dedicated) with a bot token
26126
+ * the tenant pasted. The token is write-only and appears in no response.
26127
+ */
26128
+ const GITLAB_CONNECTION_STATUSES = [
26129
+ "valid",
26130
+ "auth_failed",
26131
+ "unreachable"
26132
+ ];
26133
+ //#endregion
26134
+ //#region ../../packages/types/dist/cloud-connections.js
26135
+ const CLOUD_CONNECTION_STATUSES = [
26136
+ "pending_setup",
26137
+ "validating",
26138
+ "active",
26139
+ "error",
26140
+ "suspended",
26141
+ "disconnected"
26142
+ ];
26143
+ /**
26144
+ * Keys in `providerConfig` that only Taiga's provisioning path may write.
26145
+ *
26146
+ * PRO-858. `accountName` is the correlation key the AFT account-ready callback
26147
+ * arrives with, and the webhook resolves it under service context with no
26148
+ * tenant predicate -- it has to, because the callback carries no tenant. It is
26149
+ * also guessable: `generateAccountName` derives it from tenant, team and
26150
+ * project slugs. So a tenant that could write this key could plant a row
26151
+ * claiming another tenant's account name, wait for that callback, and have the
26152
+ * victim's `deployConfig` (accountId, deployRoleArn, stateBucket) delivered to
26153
+ * their own environment.
26154
+ *
26155
+ * `accountKey` is the same identity in unguessable form
26156
+ * (`sha256(tenantId:productId:envType)`), and is what the AFT module is named
26157
+ * for -- equally not the caller's to assert.
26158
+ *
26159
+ * Neither is tenant-facing: no provider schema models them, and
26160
+ * `providerConfigProblem` documents them as the shape a MANAGED row has. The
26161
+ * update path merges and persists the RAW config precisely so Zod does not
26162
+ * strip them off managed rows, which is what made a tenant-supplied value
26163
+ * durable. Rejecting rather than stripping, so a client sending one is told,
26164
+ * instead of silently having it dropped.
26165
+ */
26166
+ const TAIGA_MANAGED_PROVIDER_CONFIG_KEYS = ["accountName", "accountKey"];
26167
+ /** Whether `config` leaves alone the keys only Taiga's provisioning writes. */
26168
+ function isTenantWritableProviderConfig(config) {
26169
+ return !TAIGA_MANAGED_PROVIDER_CONFIG_KEYS.some((key) => key in config);
26170
+ }
26171
+ const TAIGA_MANAGED_PROVIDER_CONFIG_MESSAGE = `providerConfig may not set ${TAIGA_MANAGED_PROVIDER_CONFIG_KEYS.join(" or ")}: Taiga's provisioning owns those keys`;
26172
+ //#endregion
26073
26173
  //#region ../../packages/types/dist/environments.js
26074
26174
  const ENVIRONMENT_TYPES = [
26075
26175
  "development",
@@ -26083,6 +26183,31 @@ const ENVIRONMENT_TYPES = [
26083
26183
  //#endregion
26084
26184
  //#region ../../packages/types/dist/api-keys.js
26085
26185
  /**
26186
+ * API key scopes control what operations a key can perform.
26187
+ * Scopes act as a ceiling -- the effective permissions are the intersection
26188
+ * of the key's scopes and the user's role-based capabilities.
26189
+ *
26190
+ * Convention: "resource:action" matching the existing Capability type.
26191
+ */
26192
+ const API_KEY_SCOPES = [
26193
+ "products:read",
26194
+ "products:write",
26195
+ "backlog:read",
26196
+ "backlog:write",
26197
+ "factories:read",
26198
+ "factories:documents:write",
26199
+ "users:read",
26200
+ "tenant:documents:read",
26201
+ "tenant:documents:write",
26202
+ "vcs:read",
26203
+ "customDocuments:read",
26204
+ "customDocuments:write",
26205
+ "customDocuments:delete",
26206
+ "factories:write",
26207
+ "vcs:manage"
26208
+ ];
26209
+ const API_KEY_PREFIX = "taiga_sk_live_";
26210
+ /**
26086
26211
  * The groups the MCP server's tools come in (PRO-2838). A key may name the ones
26087
26212
  * its AI tool sees; null is every one. This is focus, not security: a tool the
26088
26213
  * key's role or scopes do not allow is never offered, whatever is named here.
@@ -26216,10 +26341,24 @@ function isFixableFinding(finding) {
26216
26341
  if (finding.status !== "open") return false;
26217
26342
  if (finding.location?.historyOnly) return false;
26218
26343
  if (!isAgentRemediable(finding)) return false;
26219
- if (finding.fingerprint?.startsWith("base-image:")) return typeof finding.baseMove === "boolean" ? finding.baseMove : baseImageMoveRemoves(finding.data);
26344
+ if (finding.fingerprint?.startsWith("base-image:") || finding.baseImageFix) {
26345
+ if (finding.baseImageFix) return finding.baseImageFix.way === "move" || finding.baseImageFix.way === "upgrade";
26346
+ return typeof finding.baseMove === "boolean" ? finding.baseMove : baseImageMoveRemoves(finding.data);
26347
+ }
26220
26348
  if (finding.kind === "vulnerable_dependency" && isMaliciousPackageFinding(finding)) return true;
26349
+ if (isPinBlocked(finding)) return false;
26221
26350
  return isCodeRemediationKind(finding.kind) || !!finding.recommendedVersion;
26222
26351
  }
26352
+ /**
26353
+ * Whether a Fix cannot work: another package's pin caps this finding's fix, and
26354
+ * the last fix ended still vulnerable against it (`opentelemetry-api` under
26355
+ * `aws-opentelemetry-distro`'s `==1.44.0`). Another would spend a run to meet
26356
+ * it again. Blocked with no such fix, a person's Fix still runs (AC-17): they
26357
+ * may know a way round. The gate lifts the hold when the pin moves.
26358
+ */
26359
+ function isPinBlocked(finding) {
26360
+ return finding.autoFixHeld?.reason === "blocked_by_dependency" && finding.autoFixHeld.fixFailed === true;
26361
+ }
26223
26362
  function baseImageMoveRemoves(data) {
26224
26363
  const baseImage = typeof data === "object" && data !== null ? data.baseImage : null;
26225
26364
  const bases = typeof baseImage === "object" && baseImage !== null ? baseImage.bases : null;
@@ -27080,6 +27219,14 @@ const cloudProviderConfig = discriminatedUnion("provider", [
27080
27219
  bestPractices: cloudBestPractices
27081
27220
  })
27082
27221
  ]);
27222
+ const newCloudConnection = strictObject({
27223
+ name: string$2().min(1).max(150),
27224
+ provider: _enum(CLOUD_PROVIDERS),
27225
+ providerConfig: cloudProviderConfig
27226
+ }).refine((connection) => connection.provider === connection.providerConfig.provider, {
27227
+ message: "connection.provider must match connection.providerConfig.provider",
27228
+ path: ["providerConfig", "provider"]
27229
+ });
27083
27230
  const columnIndex = number$1().int().min(1).max(EXCEL_MAX_COLUMN);
27084
27231
  const sheetMap = object({
27085
27232
  sheetName: string$2().min(1).max(120),
@@ -27137,7 +27284,7 @@ strictObject({ type: productDocumentType }), object({
27137
27284
  shown: array(string$2()),
27138
27285
  count: number$1().int().min(0)
27139
27286
  })
27140
- }), strictObject({ type: productDocumentType }), strictObject({ type: productDocumentType }), strictObject({}), object({ started: number$1().int().min(0) }), strictObject({
27287
+ }), strictObject({ type: productDocumentType }), strictObject({ type: productDocumentType }), strictObject({
27141
27288
  githubRepositoryId: uuid.optional(),
27142
27289
  /** A GitLab project, by the connection whose bot reaches it and GitLab's numeric id (P-PRO-150). */
27143
27290
  gitlabConnectionId: uuid.optional(),
@@ -27223,15 +27370,8 @@ strictObject({ type: productDocumentType }), object({
27223
27370
  literal(12)
27224
27371
  ]),
27225
27372
  note: string$2().trim().max(1e3).optional()
27226
- }), strictObject({ environments: array(string$2()).default(["development"]) }), strictObject({
27227
- connection: object({
27228
- name: string$2().min(1).max(150),
27229
- provider: _enum(CLOUD_PROVIDERS),
27230
- providerConfig: cloudProviderConfig
27231
- }).refine((connection) => connection.provider === connection.providerConfig.provider, {
27232
- message: "connection.provider must match connection.providerConfig.provider",
27233
- path: ["providerConfig", "provider"]
27234
- }),
27373
+ }), strictObject({ environments: array(string$2()).default(["development"]) }), object({ id: string$2().uuid() }), strictObject({
27374
+ connection: newCloudConnection,
27235
27375
  environment: object({
27236
27376
  name: string$2().min(1).max(100),
27237
27377
  slug: string$2().min(1).max(63).regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Slug must be lowercase letters, digits, and dashes"),
@@ -27240,6 +27380,19 @@ strictObject({ type: productDocumentType }), object({
27240
27380
  isProtected: boolean().default(false)
27241
27381
  }).optional()
27242
27382
  });
27383
+ /**
27384
+ * The MCP toolsets a key's AI tool sees: at least one, each once, or null for
27385
+ * every one (PRO-2838). An empty list would start a server with no tools at all.
27386
+ */
27387
+ const mcpToolsets = array(_enum(MCP_TOOLSETS)).min(1).refine((sets) => new Set(sets).size === sets.length, "Name each toolset once").nullable();
27388
+ strictObject({
27389
+ key: string$2().regex(new RegExp(`^${API_KEY_PREFIX}[0-9a-f]{64}$`)),
27390
+ name: string$2().min(1).max(255),
27391
+ scopes: array(_enum(API_KEY_SCOPES)).optional(),
27392
+ expiresInDays: number$1().int().min(1).max(365).optional(),
27393
+ mcpToolsets: mcpToolsets.optional()
27394
+ }), object({ keyId: uuid });
27395
+ strictObject({ mcpToolsets });
27243
27396
  object({
27244
27397
  url: string$2().url(),
27245
27398
  expiresAt: string$2()
@@ -27261,7 +27414,125 @@ object({
27261
27414
  /** A factory document type, its legacy name read as the current one. */
27262
27415
  const factoryDocumentType = _enum([...FACTORY_DOCUMENT_TYPES, LEGACY_FACTORY_INSTRUCTIONS_TYPE]).transform((type) => type === "team_instructions" ? FACTORY_INSTRUCTIONS_TYPE : type);
27263
27416
  strictObject({ type: factoryDocumentType }), strictObject({ type: factoryDocumentType });
27264
- strictObject({ type: _enum(TENANT_DOCUMENT_TYPES) }), strictObject({ type: _enum(TENANT_DOCUMENT_TYPES).refine((type) => type !== "design_standards", { message: "The design standards draft is not made through sync" }) }), strictObject({
27417
+ /**
27418
+ * A registry scope: an npm scope ("@acme") or a Go module path prefix
27419
+ * ("github.com/acme"), as `POST /registries` takes one (PRO-3670). Which
27420
+ * ecosystem takes which is the service's rule.
27421
+ */
27422
+ const namedSegments = (value) => value.split("/").every((seg) => seg !== "" && seg !== "." && seg !== "..");
27423
+ const registryScope = string$2().regex(/^@?[A-Za-z0-9][A-Za-z0-9._~/-]*$/, "Not a valid scope").max(214).refine(namedSegments, "Not a valid scope");
27424
+ /** The package a registry's check fetches, in the union of the ecosystems' grammars. */
27425
+ const registryTestPackage = string$2().min(1).max(214).regex(/^@?[A-Za-z0-9_][A-Za-z0-9._~/:-]*$/, "Must be a valid package name").refine(namedSegments, "Must be a valid package name");
27426
+ /** KMS direct-encrypt takes 4096 bytes; the raw secret stays well under. */
27427
+ const REGISTRY_CREDENTIAL_MAX_BYTES = 3500;
27428
+ /** A registry's write-only credential: exactly one of a token, a GCP key or an AWS role, as the route takes it. */
27429
+ const registryCredential = strictObject({
27430
+ token: string$2().min(1).max(REGISTRY_CREDENTIAL_MAX_BYTES).optional(),
27431
+ username: string$2().min(1).max(256).regex(/^[^:\s]+$/, "Username must not contain a colon or whitespace").optional(),
27432
+ credentialsJson: string$2().min(2).max(REGISTRY_CREDENTIAL_MAX_BYTES).optional(),
27433
+ awsRoleArn: string$2().max(600).regex(AWS_ROLE_ARN_PATTERN, "Not an IAM role ARN").optional()
27434
+ }).refine((c) => [
27435
+ c.token,
27436
+ c.credentialsJson,
27437
+ c.awsRoleArn
27438
+ ].filter((v) => v != null).length === 1, { message: "Provide exactly one of token, credentialsJson or awsRoleArn" }).refine((c) => c.username == null || c.token != null, { message: "A username goes with a token" }).refine((c) => new TextEncoder().encode(JSON.stringify(c)).length <= 4e3, { message: "Credential is too large to encrypt (must be under 4 KB once JSON-encoded)" });
27439
+ /** An instant as an ISO string, which the service reads as a date. */
27440
+ const isoInstant = string$2().datetime({ offset: true });
27441
+ object({
27442
+ status: _enum([
27443
+ "unvalidated",
27444
+ "valid",
27445
+ "auth_failed",
27446
+ "unreachable"
27447
+ ]),
27448
+ lastValidatedAt: string$2(),
27449
+ detail: string$2().nullable()
27450
+ });
27451
+ strictObject({
27452
+ displayName: string$2().min(1).max(255).optional(),
27453
+ registryUrl: string$2().url().max(512).optional(),
27454
+ scopes: array(registryScope).max(50).optional(),
27455
+ expiresAt: isoInstant.nullable().optional(),
27456
+ credential: registryCredential.optional(),
27457
+ testPackage: registryTestPackage.nullable().optional()
27458
+ });
27459
+ /** A GitLab bot token, as the route takes one. */
27460
+ const gitlabToken = string$2().trim().min(20, "That does not look like a GitLab access token").max(1024).regex(/^[A-Za-z0-9_.-]+$/, "That does not look like a GitLab access token");
27461
+ /** A private CA as PEM certificates; the service parses it. */
27462
+ const gitlabCaBundle = string$2().min(1).max(65536);
27463
+ /** What a connect or a token change learned, beyond the connection's record. */
27464
+ const gitlabValidationReport = object({
27465
+ pushableProjectCount: number$1().int().nullable(),
27466
+ warnings: array(string$2())
27467
+ });
27468
+ strictObject({
27469
+ displayName: string$2().trim().min(1).max(255).optional(),
27470
+ token: gitlabToken.optional(),
27471
+ caBundlePem: gitlabCaBundle.nullable().optional()
27472
+ }), gitlabValidationReport.optional(), object({
27473
+ status: _enum(GITLAB_CONNECTION_STATUSES),
27474
+ lastValidatedAt: string$2(),
27475
+ detail: string$2().nullable()
27476
+ }), object({ finished: boolean() }), strictObject({ search: string$2().trim().max(100).optional() }), object({
27477
+ projects: array(object({
27478
+ id: number$1().int(),
27479
+ pathWithNamespace: string$2(),
27480
+ nameWithNamespace: string$2(),
27481
+ webUrl: string$2(),
27482
+ defaultBranch: string$2().nullable()
27483
+ })).nullable(),
27484
+ truncated: boolean()
27485
+ });
27486
+ object({
27487
+ outcome: _enum([
27488
+ "uninstalled",
27489
+ "left_in_use",
27490
+ "uninstall_failed"
27491
+ ]),
27492
+ manageUrl: string$2().nullable()
27493
+ });
27494
+ /** A value GitHub's redirect carries back: a state token, or a one-time code. */
27495
+ const githubCallbackSecret = string$2().trim().min(1).max(1024);
27496
+ strictObject({
27497
+ displayName: string$2().trim().min(1).max(255),
27498
+ baseUrl: string$2().trim().min(1).max(480),
27499
+ token: gitlabToken,
27500
+ caBundlePem: gitlabCaBundle.nullable().optional()
27501
+ }), object({
27502
+ connectionId: uuid,
27503
+ validation: gitlabValidationReport
27504
+ }), strictObject({
27505
+ displayName: string$2().min(1).max(255),
27506
+ ecosystem: _enum(REGISTRY_ECOSYSTEMS).optional(),
27507
+ vendor: _enum(REGISTRY_VENDORS),
27508
+ registryUrl: string$2().url().max(512),
27509
+ scopes: array(registryScope).max(50).optional(),
27510
+ expiresAt: isoInstant.nullable().optional(),
27511
+ credential: registryCredential,
27512
+ testPackage: registryTestPackage.optional()
27513
+ }), strictObject({ hostId: uuid.optional() }), object({ installUrl: string$2().url() }), strictObject({ state: githubCallbackSecret }), object({
27514
+ clientId: string$2(),
27515
+ authorizeUrl: string$2(),
27516
+ redirectUri: string$2().optional()
27517
+ }), strictObject({
27518
+ installationId: number$1().int().positive(),
27519
+ setupAction: _enum(["install", "update"]),
27520
+ state: githubCallbackSecret.optional(),
27521
+ code: githubCallbackSecret.optional()
27522
+ }), strictObject({
27523
+ subdomain: string$2().min(1).max(39).optional(),
27524
+ serverUrl: string$2().min(1).max(2048).optional(),
27525
+ caBundlePem: string$2().max(65536).optional(),
27526
+ orgLogin: string$2().min(1).max(39),
27527
+ displayName: string$2().max(255).optional()
27528
+ }), object({
27529
+ manifest: record(string$2(), unknown$1()),
27530
+ postUrl: string$2().url(),
27531
+ state: string$2()
27532
+ }), strictObject({
27533
+ state: githubCallbackSecret,
27534
+ code: githubCallbackSecret
27535
+ }), strictObject({ type: _enum(TENANT_DOCUMENT_TYPES) }), strictObject({ type: _enum(TENANT_DOCUMENT_TYPES).refine((type) => type !== "design_standards", { message: "The design standards draft is not made through sync" }) }), strictObject({
27265
27536
  name: string$2().min(1).max(50),
27266
27537
  action: _enum(["accept", "decline"])
27267
27538
  }), object({
@@ -27306,10 +27577,19 @@ strictObject({ type: _enum(TENANT_DOCUMENT_TYPES) }), strictObject({ type: _enum
27306
27577
  }).refine((a) => Boolean(a.approve) !== Boolean(a.remove), { message: "Name one license to approve or remove" }), object({
27307
27578
  enrolled: number$1().int().nonnegative(),
27308
27579
  total: number$1().int().nonnegative()
27580
+ }), object({
27581
+ principalArn: string$2().nullable(),
27582
+ externalId: string$2()
27309
27583
  });
27310
27584
  strictObject({ kind: _enum(["sbom", "notices"]) }), object({
27311
27585
  url: string$2().url().nullable(),
27312
27586
  expiresAt: string$2().nullable()
27587
+ }), object({
27588
+ branches: array(object({
27589
+ name: string$2(),
27590
+ protected: boolean()
27591
+ })).nullable(),
27592
+ truncated: boolean()
27313
27593
  });
27314
27594
  strictObject({
27315
27595
  ignoreReason: _enum(MAINTENANCE_IGNORE_REASONS),
@@ -27322,6 +27602,11 @@ strictObject({ tier: _enum([
27322
27602
  "staging",
27323
27603
  "production"
27324
27604
  ]) });
27605
+ strictObject({
27606
+ name: string$2().min(1).max(150).optional(),
27607
+ status: _enum(CLOUD_CONNECTION_STATUSES).optional(),
27608
+ providerConfig: record(string$2(), unknown$1()).refine(isTenantWritableProviderConfig, { message: TAIGA_MANAGED_PROVIDER_CONFIG_MESSAGE }).optional()
27609
+ });
27325
27610
  object({
27326
27611
  url: string$2().url(),
27327
27612
  expiresAt: string$2(),
@@ -28379,6 +28664,7 @@ const FINDING_ROW_FIELDS = [
28379
28664
  "malicious",
28380
28665
  "baseMove",
28381
28666
  "baseImageMove",
28667
+ "baseImageFix",
28382
28668
  "removal",
28383
28669
  "replaceable",
28384
28670
  "status",
@@ -28506,7 +28792,7 @@ function withFixability(row) {
28506
28792
  return {
28507
28793
  ...row,
28508
28794
  fixable: false,
28509
- fixNote: reason === "not_shipped" ? "No longer applies: the package is only a build for platforms the repository does not deploy to. Nothing to do." : reason === "license_approved" ? "No longer applies: its license is on the organization's approved list. Nothing to do." : reason === "license_undecided" ? "No longer applies: its license can no longer be judged against the list (some of what it can stand for is approved). Nothing to do." : reason === "superseded" ? "Replaced by the repository's finding of its license, which lists every package under it. Nothing to do here." : "Resolved: the sweep no longer reports it. Nothing to do."
28795
+ fixNote: reason === "not_shipped" ? "No longer applies: the package is only a build for platforms the repository does not deploy to. Nothing to do." : reason === "license_approved" ? "No longer applies: its license is on the organization's approved list. Nothing to do." : reason === "license_undecided" ? "No longer applies: its license can no longer be judged against the list (some of what it can stand for is approved). Nothing to do." : reason === "superseded" ? "Replaced by the repository's finding of its license, which lists every package under it. Nothing to do here." : reason === "license_not_judged" ? "No longer applies: an OS package's license is judged only when it restricts use. Its packages keep it. Nothing to do." : "Resolved: the sweep no longer reports it. Nothing to do."
28510
28796
  };
28511
28797
  }
28512
28798
  if (row["kind"] === "license_violation" && row["replaceable"] === true) return {
@@ -28545,7 +28831,30 @@ function withFixability(row) {
28545
28831
  fixable: true,
28546
28832
  fixNote: "A malicious package: taiga_fix_finding removes it (or replaces it), with every advisory on that version."
28547
28833
  } : row;
28548
- if (typeof row["baseMove"] === "boolean" && isAgentRemediable({ remediableBy: typeof row["remediableBy"] === "string" ? row["remediableBy"] : null })) return row["baseMove"] === true && row["status"] === "open" ? {
28834
+ const held = row["autoFixHeld"];
28835
+ const inBase = row["baseImageFix"] != null || typeof row["baseMove"] === "boolean";
28836
+ if (row["status"] === "open" && !inBase && held && isPinBlocked({ autoFixHeld: held })) {
28837
+ const what = held.blockingPackage ?? "another dependency";
28838
+ return {
28839
+ ...row,
28840
+ fixable: false,
28841
+ fixNote: `Waiting on ${what}${held.blockingConstraint ? `, which requires ${held.blockingConstraint}` : ""}. taiga_fix_finding refuses it until that moves.`
28842
+ };
28843
+ }
28844
+ const base = row["baseImageFix"];
28845
+ if (base && base.way !== "move" && isAgentRemediable({ remediableBy: remediableByOf(row) })) {
28846
+ if (base.way === "upgrade") return row["status"] === "open" ? {
28847
+ ...row,
28848
+ fixable: true,
28849
+ fixNote: "taiga_fix_finding adds a temporary line to the Dockerfile that upgrades this package over its base, until the base publishes a build with the fix."
28850
+ } : row;
28851
+ return {
28852
+ ...row,
28853
+ fixable: false,
28854
+ fixNote: baseImageWaitNote(base)
28855
+ };
28856
+ }
28857
+ if (typeof row["baseMove"] === "boolean" && isAgentRemediable({ remediableBy: remediableByOf(row) })) return row["baseMove"] === true && row["status"] === "open" ? {
28549
28858
  ...row,
28550
28859
  fixable: true,
28551
28860
  fixNote: "taiga_fix_finding moves the base images it is in to the builds their tags name now, the FROM line alone, with every other advisory that move removes."
@@ -28615,6 +28924,14 @@ function withResolutionLabel(row) {
28615
28924
  previouslyResolvedReason: resolvedReason
28616
28925
  };
28617
28926
  }
28927
+ /** Why a base image's finding has no Fix, in the page's words (`maintaining_base_wait_*`). */
28928
+ function baseImageWaitNote(fix) {
28929
+ if (fix.way === "wait_for_distro") return `${fix.distro} has no fix for this yet.`;
28930
+ if (fix.way === "end_of_life") return `${fix.distro} has reached its end of life, so upgrading this one package is no fix. It clears when the base moves to a supported release.`;
28931
+ const image = fix.images.join(", ") || "The base image";
28932
+ return `${fix.distro} has fixed this, but ${image} has not published a build with the fix yet. It clears when it does.`;
28933
+ }
28934
+ const remediableByOf = (row) => typeof row["remediableBy"] === "string" ? row["remediableBy"] : null;
28618
28935
  /** A finding as a tool answers it: the row fields, the scope, and whether Fix would take it. */
28619
28936
  function findingRows(findings, extra = []) {
28620
28937
  return pickFields(findings, [
@@ -30602,10 +30919,11 @@ function runOutcome(run) {
30602
30919
  const why = errorSaid(run["error"]) ?? (typeof run["failedReason"] === "string" ? run["failedReason"] : null);
30603
30920
  const handback = handbackSaid(run["reviewEscalation"]);
30604
30921
  const draft = run["prDraft"] === true ? `, still a draft` : "";
30922
+ const blocked = reasonOnOneLine(run["factoryBlockedBecause"]);
30605
30923
  return {
30606
30924
  disposition,
30607
30925
  outcome: {
30608
- in_flight: status === "pending" ? "Queued: waiting to start." : status === "replanning" ? steerPaused(run["failedReason"], progress) : `Running: ${RUNNING[status] ?? status}${progress}.`,
30926
+ in_flight: blocked ? `Blocked: the factory has stopped working on this run${progress}. ${blocked}` : status === "pending" ? "Queued: waiting to start." : status === "replanning" ? steerPaused(run["failedReason"], progress) : `Running: ${RUNNING[status] ?? status}${progress}.`,
30609
30927
  gated: handback ? `Handed back to a person: ${handback}. ${pr}${draft ? `${draft},` : ""} waits for them to ${handbackNextStep(run["reviewEscalation"], run["provider"])}.` : `Waiting for a person to review and merge ${pr}${draft}.`,
30610
30928
  succeeded: `Merged: ${pr} is in ${typeof run["baseBranch"] === "string" ? run["baseBranch"] : "the base branch"}.`,
30611
30929
  unmerged: typeof run["prNumber"] === "number" || run["prUrl"] ? `Settled without merging: ${pr} was left unmerged.` : "Settled without a merged pull request.",
@@ -30629,12 +30947,12 @@ function steerPaused(reason, progress) {
30629
30947
  */
30630
30948
  function replanHandedBack(reason, error, progress) {
30631
30949
  if (reason !== "replan_refused" && reason !== "replan_failed") return null;
30632
- const why = plannerReasonSaid(error);
30950
+ const why = reasonOnOneLine(error);
30633
30951
  if (reason === "replan_refused") return `Stopped${progress}, handed back: the planner would not plan the request for changes${why ? `: ${why}` : "."} Resume continues the original plan.`;
30634
30952
  return `Stopped${progress}, handed back: ${why ?? "the plan could not be revised."} Resume continues the original plan.`;
30635
30953
  }
30636
30954
  /** A hand-back's reason on one line, ending as a sentence, capped for a row. */
30637
- function plannerReasonSaid(error) {
30955
+ function reasonOnOneLine(error) {
30638
30956
  if (typeof error !== "string") return null;
30639
30957
  const text = error.replace(/\s+/g, " ").trim();
30640
30958
  if (!text) return null;
@@ -30720,7 +31038,7 @@ const FAILED_REASON_MEANS = {
30720
31038
  * text out: its first sentence is in the outcome, and taiga_get_run has it all.
30721
31039
  */
30722
31040
  function readableRun(run, opts = {}) {
30723
- const { prDraft, error, reviewEscalation, runtime: _runtime, origin, ...rest } = run;
31041
+ const { prDraft, error, reviewEscalation, runtime: _runtime, origin, factoryBlockedBecause: _inTheOutcome, ...rest } = run;
30724
31042
  const said = runOutcome(run);
30725
31043
  const means = FAILED_REASON_MEANS[String(run["failedReason"])];
30726
31044
  const hardening = run["hardeningResult"];
@@ -31085,6 +31403,7 @@ function registerBuildTools(server, client) {
31085
31403
  }
31086
31404
  /** What happened to a request that steers a run (PRO-3161), in one sentence. */
31087
31405
  function steerSituation(steer) {
31406
+ if (steer === "sent") return "The run takes it as an instruction while it keeps working.";
31088
31407
  if (steer === "replanning") return "The run has paused and its plan is being revised for the request.";
31089
31408
  if (steer === "after_step") return "The run stops when the step it is building ends, then its plan is revised.";
31090
31409
  if (steer === "after_round") return "The run is answering review comments or fixing CI; the request is planned when that round ends, and no further automatic round starts while it is open.";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ai/mcp-server",
3
- "version": "0.36.0-rc.892",
3
+ "version": "0.36.0-rc.926",
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",