@taiga-ai/mcp-server 0.24.0-rc.462 → 0.25.0-dev.465
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.
- package/dist/index.js +107 -116
- package/package.json +1 -1
- package/src/client.ts +0 -8
- package/src/server.integration.test.ts +118 -32
- package/src/tools/catalogue.test.ts +2 -1
- package/src/tools/catalogue.ts +1 -1
- package/src/tools/context.ts +24 -4
- package/src/tools/documents.ts +27 -52
- package/src/tools/inbox.ts +4 -2
- package/src/tools/maintenance.ts +8 -4
- package/src/tools/product-documents.ts +1 -1
- package/src/tools/products.ts +22 -31
- package/src/tools/prompts.ts +1 -1
- package/src/tools/readiness.ts +2 -2
- package/src/tools/repositories.ts +7 -2
- package/src/tools/resources.ts +2 -2
- package/src/tools/sections.ts +1 -1
- package/taiga-ai-mcp-server-0.25.0-dev.465.tgz +0 -0
- package/taiga-ai-mcp-server-0.24.0-rc.462.tgz +0 -0
package/dist/index.js
CHANGED
|
@@ -202,13 +202,6 @@ var TaigaClient = class {
|
|
|
202
202
|
async publishTenantDocument(type) {
|
|
203
203
|
return this.request("POST", `/tenants/current/documents/${seg(type)}/publish`);
|
|
204
204
|
}
|
|
205
|
-
async listFactoryDocuments(factoryId, opts) {
|
|
206
|
-
const params = new URLSearchParams();
|
|
207
|
-
if (opts?.limit) params.set("limit", String(opts.limit));
|
|
208
|
-
if (opts?.cursor) params.set("cursor", opts.cursor);
|
|
209
|
-
const qs = params.toString();
|
|
210
|
-
return this.request("GET", `/factories/${seg(factoryId)}/documents${qs ? `?${qs}` : ""}`);
|
|
211
|
-
}
|
|
212
205
|
async getFactoryDocument(factoryId, type) {
|
|
213
206
|
return this.request("GET", `/factories/${seg(factoryId)}/documents/${seg(type)}`);
|
|
214
207
|
}
|
|
@@ -2251,7 +2244,7 @@ function setupNextStep(product) {
|
|
|
2251
2244
|
if (isImportedProduct(product)) switch (importStatusOf(product)) {
|
|
2252
2245
|
case "analyzing":
|
|
2253
2246
|
case "generating_docs": return "The import is running and derives the documents from the code (taiga_wait_for_job follows its job). Then review them, and taiga_finish_discovery opens the product.";
|
|
2254
|
-
case "complete": return "The import is done: review the reality documents (
|
|
2247
|
+
case "complete": return "The import is done: review the reality documents (taiga_get_product_document without type), then taiga_finish_discovery opens the product.";
|
|
2255
2248
|
case "failed":
|
|
2256
2249
|
case "cancelled": return "The last import did not finish: taiga_start_import runs it again.";
|
|
2257
2250
|
default: return "Link its repository if it has none (taiga_list_linkable_repositories, taiga_link_repository), then taiga_start_import: the import derives the documents from the code and runs by itself.";
|
|
@@ -2735,7 +2728,7 @@ function sectionReadResult(doc, asked, label, options = {}) {
|
|
|
2735
2728
|
const found = Object.keys(controls).length > 0 ? { controls } : {};
|
|
2736
2729
|
const asControl = options.controlIds ? void 0 : unknownSections.find((id) => isControlId(id));
|
|
2737
2730
|
const policy = asControl ? policyOfControl(asControl) : void 0;
|
|
2738
|
-
const controlNote = asControl ? { note: policy ? `${asControl} is a control of ${policy}: read it with taiga_get_document (type: ${policy}, sections: ["${asControl}"]).` : `${asControl} looks like a policy control id, which only a policy resolves:
|
|
2731
|
+
const controlNote = asControl ? { note: policy ? `${asControl} is a control of ${policy}: read it with taiga_get_document (type: ${policy}, sections: ["${asControl}"]).` : `${asControl} looks like a policy control id, which only a policy resolves: taiga_get_document without type lists them.` } : {};
|
|
2739
2732
|
if (known.length === 0) {
|
|
2740
2733
|
const { data: _data, ...meta } = cut.doc;
|
|
2741
2734
|
return toolResult({
|
|
@@ -2826,8 +2819,8 @@ function nextStepFor(state) {
|
|
|
2826
2819
|
case "running": return "The import is running and derives the documents from the code; taiga_wait_for_job follows it.";
|
|
2827
2820
|
case "failed": return "The last import did not finish: taiga_start_import runs it again.";
|
|
2828
2821
|
case "complete":
|
|
2829
|
-
if (state.missing && state.missing.length > 0) return `The import finished but ${named(state.missing)} ${state.missing.length === 1 ? "is" : "are"} not published:
|
|
2830
|
-
return `The import is done: review the reality documents (
|
|
2822
|
+
if (state.missing && state.missing.length > 0) return `The import finished but ${named(state.missing)} ${state.missing.length === 1 ? "is" : "are"} not published: taiga_get_product_document without type shows them, taiga_publish_product_document publishes a draft.`;
|
|
2823
|
+
return `The import is done: review the reality documents (taiga_get_product_document without type), then ${finish}`;
|
|
2831
2824
|
default: return "taiga_start_import derives the documents from the code and runs by itself (paid agent work: ask first).";
|
|
2832
2825
|
}
|
|
2833
2826
|
}
|
|
@@ -3120,7 +3113,7 @@ async function resolveProductRef(client, ref, accepted, usage) {
|
|
|
3120
3113
|
return { product: (await client.getProduct(ref.productId)).data };
|
|
3121
3114
|
} catch (error) {
|
|
3122
3115
|
if (!isMissOrRefusal(error)) throw error;
|
|
3123
|
-
return refusal(apiFailureCode(error), error instanceof TaigaApiError && error.status === 404 ? "No product with that ID the key can see
|
|
3116
|
+
return refusal(apiFailureCode(error), error instanceof TaigaApiError && error.status === 404 ? "No product with that ID the key can see. taiga_list_products (query finds one by name) shows the ones it can; a slug or git remote URL names one too." : `No product with that ID the key can see (${apiFailureText(error)}); taiga_list_products shows the ones it can.`);
|
|
3124
3117
|
}
|
|
3125
3118
|
if (ref.slug) {
|
|
3126
3119
|
const [product] = (await client.listProducts({
|
|
@@ -3242,38 +3235,34 @@ function registerProductTools(server, client) {
|
|
|
3242
3235
|
readiness
|
|
3243
3236
|
} : resolved.product);
|
|
3244
3237
|
}));
|
|
3245
|
-
server.registerTool("taiga_list_product_documents", {
|
|
3246
|
-
title: "List product documents",
|
|
3247
|
-
description: `List a product's documents on both timelines (type, name, version, draft state, review and update times); defaultOrigin is the product's own. ${ORIGIN_MEANING.plan} ${ORIGIN_MEANING.reality} An empty side says why; an unlisted side is null with a note. taiga_get_product_document reads one.`,
|
|
3248
|
-
inputSchema: z.object({ productId }),
|
|
3249
|
-
annotations: READ_ONLY
|
|
3250
|
-
}, (params) => runTool(async () => {
|
|
3251
|
-
const { unlistable: _unlistable, ...timelines } = await documentTimelines(client, params.productId);
|
|
3252
|
-
return toolResult(timelines);
|
|
3253
|
-
}));
|
|
3254
3238
|
server.registerTool("taiga_get_product_document", {
|
|
3255
3239
|
title: "Get product document",
|
|
3256
|
-
description: "Read one product document from one timeline
|
|
3240
|
+
description: "Read one product document from one timeline, or without type list both timelines (type, version, draft and review state; defaultOrigin is the product's own; an empty side says why). plan (specification, architecture, threat_model, technology_decisions) says what to build; reality says what the code does, so read it before changing code. Omit origin for the product's own timeline. sections: [] returns the outline (ids, sizes); then name the sections you need (unknownSections names missing ids). A miss says which documents exist.",
|
|
3257
3241
|
inputSchema: z.object({
|
|
3258
3242
|
productId,
|
|
3259
|
-
type: z.enum(PRODUCT_DOCUMENT_TYPES).describe("Document type
|
|
3243
|
+
type: z.enum(PRODUCT_DOCUMENT_TYPES).optional().describe("Document type; omit to list what the product has on both timelines"),
|
|
3260
3244
|
origin: originInput,
|
|
3261
3245
|
sections: sectionsInput
|
|
3262
3246
|
}),
|
|
3263
3247
|
annotations: READ_ONLY
|
|
3264
3248
|
}, (params) => runTool(async () => {
|
|
3249
|
+
if (params.type === void 0) {
|
|
3250
|
+
if (params.sections || params.origin) return toolError("VALIDATION_ERROR", "Name the document (type) to read one timeline or its sections.");
|
|
3251
|
+
const { unlistable: _unlistable, ...timelines } = await documentTimelines(client, params.productId);
|
|
3252
|
+
return toolResult(timelines);
|
|
3253
|
+
}
|
|
3254
|
+
const type = params.type;
|
|
3265
3255
|
let product;
|
|
3266
3256
|
if (!params.origin) product = (await client.getProduct(params.productId)).data;
|
|
3267
|
-
const origin = params.origin ?? defaultOriginFor(product,
|
|
3257
|
+
const origin = params.origin ?? defaultOriginFor(product, type);
|
|
3268
3258
|
try {
|
|
3269
|
-
const doc = stripProvenance((await client.getProductDocument(params.productId,
|
|
3259
|
+
const doc = stripProvenance((await client.getProductDocument(params.productId, type, origin)).data);
|
|
3270
3260
|
if (!params.sections) return toolResult(doc);
|
|
3271
|
-
return sectionReadResult(doc, params.sections, `${origin} ${
|
|
3261
|
+
return sectionReadResult(doc, params.sections, `${origin} ${type}`);
|
|
3272
3262
|
} catch (error) {
|
|
3273
3263
|
if (!(error instanceof TaigaApiError) || error.status !== 404) throw error;
|
|
3274
3264
|
product ??= (await client.getProduct(params.productId)).data;
|
|
3275
|
-
|
|
3276
|
-
return toolError("NOT_FOUND", describeMiss(params.type, origin, timelines, product));
|
|
3265
|
+
return toolError("NOT_FOUND", describeMiss(type, origin, await documentTimelines(client, params.productId, product), product));
|
|
3277
3266
|
}
|
|
3278
3267
|
}));
|
|
3279
3268
|
}
|
|
@@ -3319,7 +3308,7 @@ function sectionIdFrom(readTool) {
|
|
|
3319
3308
|
return z.string().min(1).describe(`Section id -- the keys of \`data.sections\` in ${readTool}, e.g. summary, scope, apiDesign. An id that does not exist yet adds a section.`);
|
|
3320
3309
|
}
|
|
3321
3310
|
const sectionContentInput = z.string().describe("The full new Markdown text of the section; it replaces the old text");
|
|
3322
|
-
const tenantDocumentType = z.enum(TENANT_DOCUMENT_TYPES).describe(
|
|
3311
|
+
const tenantDocumentType = z.enum(TENANT_DOCUMENT_TYPES).describe("Document type: the policy_* ones are policies");
|
|
3323
3312
|
/** Everything the model needs back from a write, without the content it already holds. */
|
|
3324
3313
|
function documentSummary(doc) {
|
|
3325
3314
|
const { data, ...rest } = doc;
|
|
@@ -3378,42 +3367,42 @@ async function publishDraft(scope) {
|
|
|
3378
3367
|
});
|
|
3379
3368
|
}
|
|
3380
3369
|
function registerDocumentTools(server, client) {
|
|
3381
|
-
if (client.hasCapability("tenant:documents:read")) {
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3370
|
+
if (client.hasCapability("tenant:documents:read")) server.registerTool("taiga_get_document", {
|
|
3371
|
+
title: "Get company document",
|
|
3372
|
+
description: `Read one of the organization's documents by type, or without type list them all (type, name, version, draft state). The ${POLICY_TYPES.length} policies (${named$1(POLICY_TYPES)}) set the rules for every product; the others (${named$1(OTHER_TENANT_DOCUMENT_TYPES)}) carry goals, standards, voice and instructions. Policies are long: pass sections: [] for the outline, then the section ids you need, or a control id such as SEC-5.1; unknownSections names ids it lacks. A miss says which documents are published and whether a draft exists. A section's source (template, overlay-<id>, cloud-preset:<providers>) and priority (core: every organization keeps it) do not change what it requires.`,
|
|
3373
|
+
inputSchema: z.object({
|
|
3374
|
+
type: tenantDocumentType.optional(),
|
|
3375
|
+
sections: policySectionsInput,
|
|
3376
|
+
...pagination
|
|
3377
|
+
}),
|
|
3378
|
+
annotations: READ_ONLY
|
|
3379
|
+
}, (params) => runTool(async () => {
|
|
3380
|
+
if (params.type === void 0) {
|
|
3381
|
+
if (params.sections) return toolError("VALIDATION_ERROR", "Name the document (type) to read its sections.");
|
|
3382
|
+
const result = await client.listTenantDocuments({
|
|
3383
|
+
limit: params.limit,
|
|
3384
|
+
cursor: params.cursor
|
|
3385
|
+
});
|
|
3389
3386
|
return toolResult(page(summaries(result.data), result.nextCursor));
|
|
3390
|
-
}
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
if (all.status === "fulfilled") {
|
|
3410
|
-
const published = publishedList(all.value.data);
|
|
3411
|
-
parts.push(published ? `Published documents: ${published}.` : "Nothing is published yet.");
|
|
3412
|
-
} else parts.push(failedLookup("The published list", all.reason));
|
|
3413
|
-
return toolError("NOT_FOUND", parts.join(" "));
|
|
3414
|
-
}
|
|
3415
|
-
}));
|
|
3416
|
-
}
|
|
3387
|
+
}
|
|
3388
|
+
const type = params.type;
|
|
3389
|
+
if (params.limit !== void 0 || params.cursor !== void 0) return toolError("VALIDATION_ERROR", "limit and cursor page the list; omit type to list, or drop them to read.");
|
|
3390
|
+
try {
|
|
3391
|
+
const doc = stripProvenance((await client.getTenantDocument(type)).data);
|
|
3392
|
+
return params.sections ? sectionReadResult(doc, params.sections, type, { controlIds: POLICY_TYPES.includes(type) }) : toolResult(doc);
|
|
3393
|
+
} catch (error) {
|
|
3394
|
+
if (!(error instanceof TaigaApiError) || error.status !== 404) throw error;
|
|
3395
|
+
const [draft, all] = await Promise.allSettled([client.getTenantDocumentDraft(type), client.listTenantDocuments({ limit: 100 })]);
|
|
3396
|
+
const parts = [`No published ${type}.`];
|
|
3397
|
+
if (draft.status === "fulfilled") parts.push(draft.value.data ? "A draft exists; taiga_publish_document publishes it once the person asks." : "There is no draft either.");
|
|
3398
|
+
else parts.push(failedLookup("The draft", draft.reason));
|
|
3399
|
+
if (all.status === "fulfilled") {
|
|
3400
|
+
const published = publishedList(all.value.data);
|
|
3401
|
+
parts.push(published ? `Published documents: ${published}.` : "Nothing is published yet.");
|
|
3402
|
+
} else parts.push(failedLookup("The published list", all.reason));
|
|
3403
|
+
return toolError("NOT_FOUND", parts.join(" "));
|
|
3404
|
+
}
|
|
3405
|
+
}));
|
|
3417
3406
|
if (client.hasCapability("tenant:documents:read") && client.hasCapability("tenant:documents:write")) {
|
|
3418
3407
|
const tenantScope = (type) => ({
|
|
3419
3408
|
getDraft: () => client.getTenantDocumentDraft(type),
|
|
@@ -3438,41 +3427,27 @@ function registerDocumentTools(server, client) {
|
|
|
3438
3427
|
annotations: PUBLISH
|
|
3439
3428
|
}, (params) => runTool(() => publishDraft(tenantScope(params.type))));
|
|
3440
3429
|
}
|
|
3441
|
-
if (client.hasCapability("factories:read")) {
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
}, (params) => runTool(async () => {
|
|
3463
|
-
try {
|
|
3464
|
-
return toolResult(stripProvenance((await client.getFactoryDocument(params.factoryId, params.type)).data));
|
|
3465
|
-
} catch (error) {
|
|
3466
|
-
if (!(error instanceof TaigaApiError) || error.status !== 404) throw error;
|
|
3467
|
-
await client.getFactory(params.factoryId);
|
|
3468
|
-
const detail = await client.getFactoryDocumentDraft(params.factoryId, params.type).then((draft) => draft.data ? "A draft exists; taiga_publish_factory_document publishes it once the person asks." : "There is no draft either: write one with taiga_update_factory_document, then publish it when the person asks.").catch((reason) => {
|
|
3469
|
-
if (reason instanceof TaigaApiError && reason.status === 404) throw reason;
|
|
3470
|
-
return failedLookup("The draft", reason);
|
|
3471
|
-
});
|
|
3472
|
-
return toolError("NOT_FOUND", `This factory has no published ${params.type} yet. ${detail}`);
|
|
3473
|
-
}
|
|
3474
|
-
}));
|
|
3475
|
-
}
|
|
3430
|
+
if (client.hasCapability("factories:read")) server.registerTool("taiga_get_factory_document", {
|
|
3431
|
+
title: "Get factory instructions",
|
|
3432
|
+
description: "Read a factory's instructions: the domain context, tech preferences and conventions its products follow. If nothing is published, it says whether a draft exists and how to write one.",
|
|
3433
|
+
inputSchema: z.object({
|
|
3434
|
+
factoryId: idInput("factory", "taiga_list_factories (query finds one by name) gives it"),
|
|
3435
|
+
type: z.enum(FACTORY_DOCUMENT_TYPES).default("factory_instructions").describe("Document type")
|
|
3436
|
+
}),
|
|
3437
|
+
annotations: READ_ONLY
|
|
3438
|
+
}, (params) => runTool(async () => {
|
|
3439
|
+
try {
|
|
3440
|
+
return toolResult(stripProvenance((await client.getFactoryDocument(params.factoryId, params.type)).data));
|
|
3441
|
+
} catch (error) {
|
|
3442
|
+
if (!(error instanceof TaigaApiError) || error.status !== 404) throw error;
|
|
3443
|
+
await client.getFactory(params.factoryId);
|
|
3444
|
+
const detail = await client.getFactoryDocumentDraft(params.factoryId, params.type).then((draft) => draft.data ? "A draft exists; taiga_publish_factory_document publishes it once the person asks." : "There is no draft either: write one with taiga_update_factory_document, then publish it when the person asks.").catch((reason) => {
|
|
3445
|
+
if (reason instanceof TaigaApiError && reason.status === 404) throw reason;
|
|
3446
|
+
return failedLookup("The draft", reason);
|
|
3447
|
+
});
|
|
3448
|
+
return toolError("NOT_FOUND", `This factory has no published ${params.type} yet. ${detail}`);
|
|
3449
|
+
}
|
|
3450
|
+
}));
|
|
3476
3451
|
if (client.hasCapability("factories:read") && client.hasCapability("factories:documents:write")) {
|
|
3477
3452
|
const factoryScope = (factoryId, type) => ({
|
|
3478
3453
|
getDraft: () => client.getFactoryDocumentDraft(factoryId, type),
|
|
@@ -4840,7 +4815,7 @@ function registerMaintenanceTools(server, client) {
|
|
|
4840
4815
|
if (!client.hasCapability("products:read")) return;
|
|
4841
4816
|
server.registerTool("taiga_list_findings", {
|
|
4842
4817
|
title: "List maintenance findings",
|
|
4843
|
-
description: "List what maintenance sweeps found in a product's repository: dependencies, secrets, insecure code (sast) and infrastructure. The first page opens with sweep (open counts, latest sweeps), so an old or failed sweep is not read as an all-clear. Rows: severity, tier (act_now: known exploited; urgent: likely and serious; elevated: one of the two; routine), location, remediableBy (upstream_only: no fixed version
|
|
4818
|
+
description: "List what maintenance sweeps found in a product's repository: dependencies, secrets, insecure code (sast) and infrastructure. The first page opens with sweep (open counts, latest sweeps), so an old or failed sweep is not read as an all-clear. Rows: severity, tier (act_now: known exploited; urgent: likely and serious; elevated: one of the two; routine), location, remediableBy (coding_agent: a run here can fix it; upstream_only: the code is in a submodule, so the fix belongs in its own repository), fixable: false when no fixed version is known yet, breached controls and initiativeId of a fix under way. Omit status for open findings. taiga_get_finding reads one.",
|
|
4844
4819
|
inputSchema: z.object({
|
|
4845
4820
|
productId,
|
|
4846
4821
|
status: z.enum(MAINTENANCE_FINDING_STATUSES).optional().describe("Only findings in this status; omit for the ones needing action"),
|
|
@@ -4953,7 +4928,7 @@ function registerMaintenanceTools(server, client) {
|
|
|
4953
4928
|
ignored: z.boolean().describe("true ignores the finding, false reopens it"),
|
|
4954
4929
|
reason: z.enum(MAINTENANCE_IGNORE_REASONS).optional().describe("Why it is ignored; required when ignoring"),
|
|
4955
4930
|
note: z.string().trim().max(280).optional().describe("A short justification, kept for audit"),
|
|
4956
|
-
expiresAt: z.string().datetime({ offset: true }).optional().describe("When the ignore lapses and the finding resurfaces if it still exists (ISO 8601, in the future)")
|
|
4931
|
+
expiresAt: z.string().datetime({ offset: true }).meta({ pattern: void 0 }).optional().describe("When the ignore lapses and the finding resurfaces if it still exists (ISO 8601, in the future)")
|
|
4957
4932
|
}),
|
|
4958
4933
|
annotations: OPERATE
|
|
4959
4934
|
}, (params) => runTool(async () => {
|
|
@@ -4969,7 +4944,7 @@ function registerMaintenanceTools(server, client) {
|
|
|
4969
4944
|
}));
|
|
4970
4945
|
server.registerTool("taiga_fix_finding", {
|
|
4971
4946
|
title: "Fix a finding",
|
|
4972
|
-
description: "Raise an initiative that fixes a maintenance finding, at the end of the product's line; it ends in a pull request a person merges, and the initiative's review gate applies. wholeGroup: true fixes the group in one initiative: every open advisory on that package version in one bump, or every location the same rule flagged. Refused for a secret only in history,
|
|
4947
|
+
description: "Raise an initiative that fixes a maintenance finding, at the end of the product's line; it ends in a pull request a person merges, and the initiative's review gate applies. wholeGroup: true fixes the group in one initiative: every open advisory on that package version in one bump, or every location the same rule flagged. Refused for a secret only in history, remediableBy upstream_only (code in a submodule), and a dependency with no fixed version yet (fixable: false).",
|
|
4973
4948
|
inputSchema: z.object({
|
|
4974
4949
|
productId,
|
|
4975
4950
|
findingId,
|
|
@@ -5046,14 +5021,14 @@ function readWith(row, backlogRead) {
|
|
|
5046
5021
|
case "backlog_item": return backlogRead ? "taiga_get_plan (initiativeId: subjectId)" : void 0;
|
|
5047
5022
|
case "product_document": {
|
|
5048
5023
|
const type = /^[^:]+:([a-z_]+):decision:/.exec(String(row["subjectId"]))?.[1];
|
|
5049
|
-
return type ? `taiga_get_product_document (productId, type: ${type}); the decision's text is this row with full: true` : "
|
|
5024
|
+
return type ? `taiga_get_product_document (productId, type: ${type}); the decision's text is this row with full: true` : "taiga_get_product_document (productId, no type)";
|
|
5050
5025
|
}
|
|
5051
5026
|
case "inbox_task": return "taiga_list_setup_tasks (productId)";
|
|
5052
5027
|
case "product": return {
|
|
5053
5028
|
review_backlog: backlogRead ? "taiga_list_initiatives (productId)" : void 0,
|
|
5054
5029
|
review_maintenance: "taiga_list_findings (productId)",
|
|
5055
5030
|
review_policy: "taiga_get_policy_standing (productId)",
|
|
5056
|
-
review_doc: "
|
|
5031
|
+
review_doc: "taiga_get_product_document (productId, no type)"
|
|
5057
5032
|
}[String(row["kind"])];
|
|
5058
5033
|
default: return;
|
|
5059
5034
|
}
|
|
@@ -5366,7 +5341,7 @@ function registerInboxTools(server, client) {
|
|
|
5366
5341
|
"done",
|
|
5367
5342
|
"dismissed"
|
|
5368
5343
|
]).optional().describe("What became of it"),
|
|
5369
|
-
snoozedUntil: z.string().datetime().nullable().optional().describe("Hide it until this time (ISO 8601 in UTC, ending in Z); null wakes it now")
|
|
5344
|
+
snoozedUntil: z.string().datetime().meta({ pattern: void 0 }).nullable().optional().describe("Hide it until this time (ISO 8601 in UTC, ending in Z); null wakes it now")
|
|
5370
5345
|
}),
|
|
5371
5346
|
annotations: OPERATE
|
|
5372
5347
|
}, (params) => runTool(async () => {
|
|
@@ -5399,7 +5374,7 @@ const REPOSITORY_SUMMARY_FIELDS = [
|
|
|
5399
5374
|
"isManaged"
|
|
5400
5375
|
];
|
|
5401
5376
|
/** How many of the latest runs to carry; taiga_list_runs pages the rest. */
|
|
5402
|
-
const BUILDS_IN_CONTEXT =
|
|
5377
|
+
const BUILDS_IN_CONTEXT = 5;
|
|
5403
5378
|
/** A run as the bundle carries it: enough to say what was built and where; taiga_get_run has the rest. */
|
|
5404
5379
|
const CONTEXT_BUILD_FIELDS = [
|
|
5405
5380
|
"id",
|
|
@@ -5418,6 +5393,7 @@ const CONTEXT_BUILD_FIELDS = [
|
|
|
5418
5393
|
"prDraft",
|
|
5419
5394
|
"reviewEscalation",
|
|
5420
5395
|
"error",
|
|
5396
|
+
"hardeningResult",
|
|
5421
5397
|
"createdAt"
|
|
5422
5398
|
];
|
|
5423
5399
|
/**
|
|
@@ -5586,7 +5562,7 @@ async function documentsPart(client, product, depth) {
|
|
|
5586
5562
|
}
|
|
5587
5563
|
} else if (isNotFound$1(read.reason)) {
|
|
5588
5564
|
out[type] = null;
|
|
5589
|
-
notes.push(`No published ${origin} ${type};
|
|
5565
|
+
notes.push(`No published ${origin} ${type}; taiga_get_product_document without type shows what the product has.`);
|
|
5590
5566
|
} else throw read.reason;
|
|
5591
5567
|
});
|
|
5592
5568
|
return {
|
|
@@ -5669,8 +5645,21 @@ async function repositoriesPart(links) {
|
|
|
5669
5645
|
async function buildsPart(client, productId) {
|
|
5670
5646
|
const rows = (await client.listBuildRuns(productId, { limit: BUILDS_IN_CONTEXT })).data;
|
|
5671
5647
|
const states = await initiativeStates(client, rows);
|
|
5672
|
-
return pickFields(rows, CONTEXT_BUILD_FIELDS).map(readableRow).map((row) => withInitiativeState(row, states));
|
|
5648
|
+
return pickFields(rows, CONTEXT_BUILD_FIELDS).map(readableRow).map((row) => withInitiativeState(row, states)).map((row) => pickFields([row], CONTEXT_RUN_SAID)[0]);
|
|
5673
5649
|
}
|
|
5650
|
+
/** A run in the bundle: which one, for what, how it ended, and its pull request. taiga_get_run has the rest. */
|
|
5651
|
+
const CONTEXT_RUN_SAID = [
|
|
5652
|
+
"id",
|
|
5653
|
+
"runNumber",
|
|
5654
|
+
"backlogItemId",
|
|
5655
|
+
"targetTitle",
|
|
5656
|
+
"disposition",
|
|
5657
|
+
"outcome",
|
|
5658
|
+
"failedReasonMeans",
|
|
5659
|
+
"hardeningNote",
|
|
5660
|
+
"prUrl",
|
|
5661
|
+
"createdAt"
|
|
5662
|
+
];
|
|
5674
5663
|
/**
|
|
5675
5664
|
* What waits on a person for this product, counted as taiga_list_action_required's summary counts it (PRO-2800):
|
|
5676
5665
|
* the reviewing agent called four tools to answer "how does it stand", and two later reviews found this count and
|
|
@@ -5767,7 +5756,7 @@ function registerContextTools(server, client) {
|
|
|
5767
5756
|
if (!client.hasCapability("products:read")) return;
|
|
5768
5757
|
server.registerTool("taiga_get_product_context", {
|
|
5769
5758
|
title: "Get product context",
|
|
5770
|
-
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
|
|
5759
|
+
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.`,
|
|
5771
5760
|
inputSchema: z.object({
|
|
5772
5761
|
productId: productId.optional(),
|
|
5773
5762
|
slug: slug.optional(),
|
|
@@ -6252,7 +6241,7 @@ function registerObserveTools(server, client) {
|
|
|
6252
6241
|
//#endregion
|
|
6253
6242
|
//#region src/tools/product-documents.ts
|
|
6254
6243
|
const EDITABLE_TYPES = PRODUCT_DOCUMENT_TYPES.filter((type) => type !== "codebase_snapshot");
|
|
6255
|
-
const documentType = z.enum(EDITABLE_TYPES).describe("Document type --
|
|
6244
|
+
const documentType = z.enum(EDITABLE_TYPES).describe("Document type -- taiga_get_product_document without type shows which ones the product has");
|
|
6256
6245
|
/** The documents a person reviews: the discovery chain, not the snapshot or the product's own instructions. */
|
|
6257
6246
|
const reviewableType = z.enum(REVIEWABLE_PRODUCT_DOCUMENT_TYPES).describe("Document type -- one of the discovery documents a person reviews");
|
|
6258
6247
|
/** The documents an agent writes from other documents; the same table the worker reads its inputs from. */
|
|
@@ -20979,7 +20968,7 @@ function registerPrompts(server, client) {
|
|
|
20979
20968
|
client.hasCapability("factories:read") ? client.getFactoryDocument(String(product.factoryId), "factory_instructions") : Promise.reject(new NotGranted("factories:read")),
|
|
20980
20969
|
client.hasCapability("tenant:documents:read") ? client.listTenantDocuments({ limit: 100 }) : Promise.reject(new NotGranted("tenant:documents:read"))
|
|
20981
20970
|
]);
|
|
20982
|
-
const architectureText = architecture.status === "fulfilled" ? fence("markdown", renderDocument(stripProvenance(architecture.value.data)).text) : isNotFound(architecture.reason) ? `(No published ${origin} architecture;
|
|
20971
|
+
const architectureText = architecture.status === "fulfilled" ? fence("markdown", renderDocument(stripProvenance(architecture.value.data)).text) : isNotFound(architecture.reason) ? `(No published ${origin} architecture; taiga_get_product_document without type shows what exists.)` : unreadable("The architecture", architecture.reason);
|
|
20983
20972
|
const instructionsText_ = instructions.status === "fulfilled" ? fence("markdown", instructionsText(sectionsOf(instructions.value.data.data) ?? {}) ?? "(The factory instructions are empty.)") : instructions.reason instanceof NotGranted ? instructions.reason.line("The factory instructions") : isNotFound(instructions.reason) ? "(This factory has not published instructions.)" : unreadable("The factory instructions", instructions.reason);
|
|
20984
20973
|
const policiesText = policies.status === "fulfilled" ? fence("text", policies.value.data.filter((row) => POLICY_TYPES.includes(String(row.type)) && row.isDraft === false).map((row) => `- ${String(row.type)}: ${Object.keys(sectionsOf(row.data ?? {}) ?? {}).join(", ")}`).join("\n") || "(No policy is published.)") : policies.reason instanceof NotGranted ? policies.reason.line("The policies") : unreadable("The policies", policies.reason);
|
|
20985
20974
|
return message([
|
|
@@ -21070,7 +21059,8 @@ const GITHUB_REPOSITORY_FIELDS = [
|
|
|
21070
21059
|
"defaultBranch",
|
|
21071
21060
|
"description",
|
|
21072
21061
|
"language",
|
|
21073
|
-
"linked"
|
|
21062
|
+
"linked",
|
|
21063
|
+
"linkedProduct"
|
|
21074
21064
|
];
|
|
21075
21065
|
const repositoryId = idInput("repository link", "taiga_list_product_repositories gives it; a linkable repository id is not one");
|
|
21076
21066
|
/** Round-robin over lists: the first of each, then the second of each, and so on. */
|
|
@@ -21086,7 +21076,8 @@ function interleave(lists) {
|
|
|
21086
21076
|
*/
|
|
21087
21077
|
async function holderOf(client, error, productId) {
|
|
21088
21078
|
if (!(error instanceof TaigaApiError) || error.status !== 409) return null;
|
|
21089
|
-
|
|
21079
|
+
if (error.details?.["holder"]) return null;
|
|
21080
|
+
const name = /^(\S+\/\S+) is already linked to another product\./.exec(error.apiMessage ?? "")?.[1];
|
|
21090
21081
|
if (!name) return null;
|
|
21091
21082
|
try {
|
|
21092
21083
|
const holders = (await client.listProducts({
|
|
@@ -21148,7 +21139,7 @@ function registerRepositoryTools(server, client) {
|
|
|
21148
21139
|
}
|
|
21149
21140
|
if (client.hasCapability("vcs:read")) server.registerTool("taiga_list_linkable_repositories", {
|
|
21150
21141
|
title: "Find a repository to link",
|
|
21151
|
-
description: "List the repositories the organization's GitHub connection can reach, to link one with taiga_link_repository (it takes the row's id as linkableRepositoryId). query searches by name; account (login, owner, or installationId) reads one account, else rows come from every account in turn. linked: true means a product already links it
|
|
21142
|
+
description: "List the repositories the organization's GitHub connection can reach, to link one with taiga_link_repository (it takes the row's id as linkableRepositoryId). query searches by name; account (login, owner, or installationId) reads one account, else rows come from every account in turn. linked: true means a product already links it, and linkedProduct names it to a key that sees every product. No connection means GitHub is not connected yet; someone who can install the app does that once, in the web.",
|
|
21152
21143
|
inputSchema: z.object({
|
|
21153
21144
|
query: z.string().trim().min(1).max(256).optional().describe("Part of the repository name"),
|
|
21154
21145
|
account: z.string().trim().min(1).max(140).optional().describe("The GitHub account login (the owner in owner/repo), case-insensitive"),
|
|
@@ -21339,7 +21330,7 @@ function registerResources(server, client) {
|
|
|
21339
21330
|
return requireOneOf("Document type", value, PRODUCT_DOCUMENT_TYPES);
|
|
21340
21331
|
};
|
|
21341
21332
|
const read = async (uri, productId, type, origin) => {
|
|
21342
|
-
const result = await served(client.getProductDocument(productId, type, origin), `No published ${origin} ${type} for this product, or the key cannot see it;
|
|
21333
|
+
const result = await served(client.getProductDocument(productId, type, origin), `No published ${origin} ${type} for this product, or the key cannot see it; taiga_get_product_document without type shows what exists`);
|
|
21343
21334
|
return contents(uri.href, {
|
|
21344
21335
|
name: productDocumentName(type),
|
|
21345
21336
|
...result.data
|
|
@@ -21356,7 +21347,7 @@ function registerResources(server, client) {
|
|
|
21356
21347
|
});
|
|
21357
21348
|
server.registerResource("product-document", new ResourceTemplate("taiga://products/{productId}/documents/{type}", { list: void 0 }), {
|
|
21358
21349
|
title: "Product document",
|
|
21359
|
-
description: "A product document from the product's own timeline (reality for an imported product, plan otherwise; the documents a person writes are always on the plan side).
|
|
21350
|
+
description: "A product document from the product's own timeline (reality for an imported product, plan otherwise; the documents a person writes are always on the plan side). taiga_get_product_document without type shows what exists; add ?origin=plan or ?origin=reality to read the other side. Markdown for a section map, JSON otherwise (the read says which)."
|
|
21360
21351
|
}, async (uri, variables) => {
|
|
21361
21352
|
const productId = requireId("Product ID", String(variables["productId"]));
|
|
21362
21353
|
const type = documentType(String(variables["type"]));
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -345,14 +345,6 @@ export class TaigaClient {
|
|
|
345
345
|
// Factory Documents
|
|
346
346
|
// ---------------------------------------------------------------------------
|
|
347
347
|
|
|
348
|
-
async listFactoryDocuments(factoryId: string, opts?: { limit?: number | undefined; cursor?: string | undefined }) {
|
|
349
|
-
const params = new URLSearchParams();
|
|
350
|
-
if (opts?.limit) params.set('limit', String(opts.limit));
|
|
351
|
-
if (opts?.cursor) params.set('cursor', opts.cursor);
|
|
352
|
-
const qs = params.toString();
|
|
353
|
-
return this.request<Page<FactoryDocument>>('GET', `/factories/${seg(factoryId)}/documents${qs ? `?${qs}` : ''}`);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
348
|
async getFactoryDocument(factoryId: string, type: string) {
|
|
357
349
|
return this.request<One<FactoryDocument>>('GET', `/factories/${seg(factoryId)}/documents/${seg(type)}`);
|
|
358
350
|
}
|