@taiga-ai/mcp-server 0.17.0-rc.442 → 0.18.0-dev.443
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 +209 -85
- package/package.json +1 -1
- package/src/client.ts +15 -2
- package/src/server.integration.test.ts +404 -37
- package/src/tools/autonomy.ts +2 -2
- package/src/tools/backlogs.ts +67 -8
- package/src/tools/catalogue.test.ts +15 -1
- package/src/tools/context.ts +31 -18
- package/src/tools/documents.ts +13 -10
- package/src/tools/hints.ts +1 -1
- package/src/tools/inbox.ts +24 -5
- package/src/tools/index.ts +23 -2
- package/src/tools/instructions.ts +7 -6
- package/src/tools/knowledge.ts +3 -3
- package/src/tools/maintenance.ts +5 -3
- package/src/tools/observe.ts +29 -13
- package/src/tools/product-documents.ts +4 -5
- package/src/tools/products.ts +7 -11
- package/src/tools/repositories.ts +47 -16
- package/src/tools/sections.ts +17 -5
- package/src/tools/setup.ts +8 -8
- package/taiga-ai-mcp-server-0.18.0-dev.443.tgz +0 -0
- package/taiga-ai-mcp-server-0.17.0-rc.442.tgz +0 -0
package/dist/index.js
CHANGED
|
@@ -284,6 +284,10 @@ var TaigaClient = class {
|
|
|
284
284
|
async getBacklogItem(itemId, signal) {
|
|
285
285
|
return this.request("GET", `/backlog/${seg(itemId)}`, void 0, { signal });
|
|
286
286
|
}
|
|
287
|
+
/** The initiative's history, oldest first: every turn event and audited move, each with who did it. */
|
|
288
|
+
async listBacklogItemActivity(itemId) {
|
|
289
|
+
return this.request("GET", `/backlog/${seg(itemId)}/activity`);
|
|
290
|
+
}
|
|
287
291
|
/** A person's move on the line board: the column dropped into, and what to follow there. */
|
|
288
292
|
async transitionInitiative(itemId, body) {
|
|
289
293
|
return this.request("PUT", `/backlog/${seg(itemId)}/state`, body);
|
|
@@ -2178,7 +2182,7 @@ const BY_ARGUMENT = {
|
|
|
2178
2182
|
factoryId: "taiga_list_factories lists the factories the key can see.",
|
|
2179
2183
|
findingId: "taiga_list_findings lists a product's findings.",
|
|
2180
2184
|
violationId: "taiga_get_policy_standing lists a product's policy violations.",
|
|
2181
|
-
environmentId: "
|
|
2185
|
+
environmentId: "taiga_list_environments lists the product's environments with their IDs; a product ID is not one.",
|
|
2182
2186
|
knowledgeId: "taiga_list_knowledge lists the knowledge files.",
|
|
2183
2187
|
productId: "taiga_list_products lists the products the key can see; a name is not an ID (taiga_get_product takes a slug)."
|
|
2184
2188
|
};
|
|
@@ -2473,17 +2477,23 @@ function budgetSections(doc, budget) {
|
|
|
2473
2477
|
return cutTo(doc, sections, Object.fromEntries(kept));
|
|
2474
2478
|
}
|
|
2475
2479
|
/**
|
|
2480
|
+
* The `sections` input of a document read: some sections by id, none for the
|
|
2481
|
+
* outline (every id with its size and no text), or omitted for the whole.
|
|
2482
|
+
*/
|
|
2483
|
+
const sectionsInput = z.array(z.string().min(1).max(200)).max(50).optional().describe("Only these sections, by id; [] for the outline (every section id and its size, no text); omit for the whole document");
|
|
2484
|
+
/**
|
|
2476
2485
|
* The answer to a read that names sections: the ones the document has, the
|
|
2477
2486
|
* rest listed with their sizes, and the asked-for ids it lacks under
|
|
2478
|
-
* unknownSections. One
|
|
2479
|
-
*
|
|
2487
|
+
* unknownSections. One shape whatever matched (PRO-2778): a wrong id does not
|
|
2488
|
+
* throw away the right ones, and an answer with none right still lists every
|
|
2489
|
+
* real id with its size, so the retry needs nothing else. No ids is the
|
|
2490
|
+
* outline.
|
|
2480
2491
|
*/
|
|
2481
2492
|
function sectionReadResult(doc, ids, label) {
|
|
2482
2493
|
const cut = pickSections(doc, new Set(ids));
|
|
2483
2494
|
if (!cut) return toolError("VALIDATION_ERROR", `The ${label} is not made of sections; read it without sections.`);
|
|
2484
2495
|
const all = Object.keys(sectionsOf(doc.data) ?? {});
|
|
2485
2496
|
const unknownSections = ids.filter((id) => !all.includes(id));
|
|
2486
|
-
if (unknownSections.length === ids.length) return toolError("NOT_FOUND", `The ${label} has no section ${ids.join(", ")}; its sections are ${all.join(", ")}.`);
|
|
2487
2497
|
return toolResult({
|
|
2488
2498
|
...cut.doc,
|
|
2489
2499
|
omittedSections: cut.omittedSections,
|
|
@@ -2847,7 +2857,7 @@ function registerProductTools(server, client) {
|
|
|
2847
2857
|
}));
|
|
2848
2858
|
server.registerTool("taiga_list_product_documents", {
|
|
2849
2859
|
title: "List product documents",
|
|
2850
|
-
description: `List a product's documents on both timelines, as summaries (type, name, version, draft state, review and update times), and say which timeline is the product's own (defaultOrigin). Two timelines: ${ORIGIN_MEANING.plan} ${ORIGIN_MEANING.reality} An empty side comes with a note saying why; a side the API could not list is null, with a note. Use taiga_get_product_document with the type and origin to read one in full.`,
|
|
2860
|
+
description: `List a product's documents on both timelines, as summaries (type, name, version, draft state, review and update times), and say which timeline is the product's own (defaultOrigin). Two timelines: ${ORIGIN_MEANING.plan} ${ORIGIN_MEANING.reality} An empty side comes with a note saying why; a side the API could not list is null, with a note. Use taiga_get_product_document with the type and origin to read one in full, or with sections: [] for its outline (section ids and sizes) first.`,
|
|
2851
2861
|
inputSchema: z.object({ productId }),
|
|
2852
2862
|
annotations: READ_ONLY
|
|
2853
2863
|
}, (params) => runTool(async () => {
|
|
@@ -2856,12 +2866,12 @@ function registerProductTools(server, client) {
|
|
|
2856
2866
|
}));
|
|
2857
2867
|
server.registerTool("taiga_get_product_document", {
|
|
2858
2868
|
title: "Get product document",
|
|
2859
|
-
description: "Get the full content of one product document from one timeline. Read the plan to learn what to build: specification (requirements), architecture (design), threat_model (security risks), technology_decisions. Read reality, when the product has it, to learn what the code actually does before changing it. Omit origin for the product's own timeline. A large document runs to tens of thousands of characters:
|
|
2869
|
+
description: "Get the full content of one product document from one timeline. Read the plan to learn what to build: specification (requirements), architecture (design), threat_model (security risks), technology_decisions. Read reality, when the product has it, to learn what the code actually does before changing it. Omit origin for the product's own timeline. A large document runs to tens of thousands of characters: sections: [] reads its outline (every section id with its size, no text) on either timeline, then name the sections you need and the read carries only those, listing the rest with their sizes and any id the document lacks (unknownSections). A read that finds nothing says which documents and timelines the product does have.",
|
|
2860
2870
|
inputSchema: z.object({
|
|
2861
2871
|
productId,
|
|
2862
2872
|
type: z.enum(PRODUCT_DOCUMENT_TYPES).describe("Document type -- taiga_list_product_documents shows which ones the product has"),
|
|
2863
2873
|
origin: originInput,
|
|
2864
|
-
sections:
|
|
2874
|
+
sections: sectionsInput
|
|
2865
2875
|
}),
|
|
2866
2876
|
annotations: READ_ONLY
|
|
2867
2877
|
}, (params) => runTool(async () => {
|
|
@@ -2993,10 +3003,10 @@ function registerDocumentTools(server, client) {
|
|
|
2993
3003
|
}));
|
|
2994
3004
|
server.registerTool("taiga_get_document", {
|
|
2995
3005
|
title: "Get company document",
|
|
2996
|
-
description: "Get the full content of one of the organization's documents, sections included. Use this when you need the security requirements, data handling rules, architecture standards or compliance controls that apply to your code, or the goals, standards and voice the organization has set down. A policy runs to thousands of tokens: name the sections you need (the ids taiga_get_product_context lists for every policy) and the read carries only those, listing the rest with their sizes and any id the document lacks (unknownSections). A read that finds
|
|
3006
|
+
description: "Get the full content of one of the organization's documents, sections included. Use this when you need the security requirements, data handling rules, architecture standards or compliance controls that apply to your code, or the goals, standards and voice the organization has set down. A policy runs to thousands of tokens: name the sections you need (the ids taiga_get_product_context lists for every policy, or sections: [] for the outline) and the read carries only those, listing the rest with their sizes and any id the document lacks (unknownSections). A read that finds no document says which documents are published and whether a draft exists.",
|
|
2997
3007
|
inputSchema: z.object({
|
|
2998
3008
|
type: tenantDocumentType,
|
|
2999
|
-
sections:
|
|
3009
|
+
sections: sectionsInput
|
|
3000
3010
|
}),
|
|
3001
3011
|
annotations: READ_ONLY
|
|
3002
3012
|
}, (params) => runTool(async () => {
|
|
@@ -3399,6 +3409,51 @@ const ITEM_OUTCOME_FIELDS = [
|
|
|
3399
3409
|
"archivedAt",
|
|
3400
3410
|
"lifecycle"
|
|
3401
3411
|
];
|
|
3412
|
+
/** What each plan failure cause means for whoever reads it next (PLAN_FAILURE_CAUSES in @taiga/types). */
|
|
3413
|
+
const PLAN_FAILURE_MEANING = {
|
|
3414
|
+
agent_error: "The planning agent failed. Planning again is the usual next step.",
|
|
3415
|
+
worker_lost: "Nobody was running the planning any more (the worker stopped). Planning again is the usual next step.",
|
|
3416
|
+
cancelled: "A person stopped the planning (by says who). Ask before planning it again."
|
|
3417
|
+
};
|
|
3418
|
+
/**
|
|
3419
|
+
* Who or what ended a failed plan, and when, from the initiative's history
|
|
3420
|
+
* (PRO-2780): planFailureCause alone said "cancelled" with nobody behind it.
|
|
3421
|
+
* Best effort: a history the API will not give leaves the item as it was.
|
|
3422
|
+
*/
|
|
3423
|
+
async function planFailureOf(client, itemId, itemCause) {
|
|
3424
|
+
let events;
|
|
3425
|
+
try {
|
|
3426
|
+
events = (await client.listBacklogItemActivity(itemId)).data;
|
|
3427
|
+
} catch (error) {
|
|
3428
|
+
if (error instanceof TaigaApiError || isNetworkFailure(error)) return null;
|
|
3429
|
+
throw error;
|
|
3430
|
+
}
|
|
3431
|
+
let failed;
|
|
3432
|
+
for (const event of [...events].reverse()) {
|
|
3433
|
+
if (event.kind !== "turn") continue;
|
|
3434
|
+
if (event.trigger === "plan_started") break;
|
|
3435
|
+
if (event.trigger === "plan_failed") {
|
|
3436
|
+
failed = event;
|
|
3437
|
+
break;
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
if (!failed) return null;
|
|
3441
|
+
const actor = failed.actor ?? {};
|
|
3442
|
+
const data = failed.data ?? {};
|
|
3443
|
+
const cause = typeof data.cause === "string" ? data.cause : null;
|
|
3444
|
+
if (typeof itemCause === "string" && cause !== itemCause) return {
|
|
3445
|
+
cause: itemCause,
|
|
3446
|
+
meaning: PLAN_FAILURE_MEANING[itemCause] ?? "Planning ended without a plan.",
|
|
3447
|
+
at: null,
|
|
3448
|
+
by: null
|
|
3449
|
+
};
|
|
3450
|
+
return {
|
|
3451
|
+
cause,
|
|
3452
|
+
meaning: (cause && PLAN_FAILURE_MEANING[cause]) ?? "Planning ended without a plan.",
|
|
3453
|
+
at: failed.occurredAt ?? null,
|
|
3454
|
+
by: typeof actor.name === "string" ? actor.name : actor.type ?? null
|
|
3455
|
+
};
|
|
3456
|
+
}
|
|
3402
3457
|
const planId = idInput("plan", "taiga_get_plan gives the initiative's active plan and its ID");
|
|
3403
3458
|
const itemId = idInput("backlog item", "taiga_list_backlog_items (query finds one by title) gives it");
|
|
3404
3459
|
/** The plan without its steps' prose: enough to approve it or say what a run will do. */
|
|
@@ -3505,11 +3560,17 @@ function registerBacklogReads(server, client) {
|
|
|
3505
3560
|
}));
|
|
3506
3561
|
server.registerTool("taiga_get_backlog_item", {
|
|
3507
3562
|
title: "Get backlog item",
|
|
3508
|
-
description: "Get one backlog item in full: description, acceptance criteria (and which are met), technical context (patterns, constraints, and the assumptions to verify before starting), dependencies and source documents. Read this before implementing an item, and read its runs with taiga_list_builds to see what the coding agent already did. lifecycle says whose turn it is and what moves it on: cta start_plan is taiga_queue_backlog_item, retry_plan taiga_plan_again (planFailureCause says why the plan stopped), start_build taiga_approve_plan, resume_build taiga_resume_backlog_item, retry_build a fresh run (start over, in the web), review_pr and view_pr the pull request. needsHumanCause is set only when the line stopped on it.",
|
|
3563
|
+
description: "Get one backlog item in full: description, acceptance criteria (and which are met), technical context (patterns, constraints, and the assumptions to verify before starting), dependencies and source documents. Read this before implementing an item, and read its runs with taiga_list_builds to see what the coding agent already did. A failed plan carries planFailure: the cause, what it means, when, and by whom. lifecycle says whose turn it is and what moves it on: cta start_plan is taiga_queue_backlog_item, retry_plan taiga_plan_again (planFailureCause says why the plan stopped), start_build taiga_approve_plan, resume_build taiga_resume_backlog_item, retry_build a fresh run (start over, in the web), review_pr and view_pr the pull request. needsHumanCause is set only when the line stopped on it.",
|
|
3509
3564
|
inputSchema: z.object({ itemId }),
|
|
3510
3565
|
annotations: READ_ONLY
|
|
3511
3566
|
}, (params) => runTool(async () => {
|
|
3512
|
-
|
|
3567
|
+
const item = (await client.getBacklogItem(params.itemId)).data;
|
|
3568
|
+
if (item.planningStatus !== "failed") return toolResult(item);
|
|
3569
|
+
const planFailure = await planFailureOf(client, params.itemId, item.planFailureCause);
|
|
3570
|
+
return toolResult(planFailure ? {
|
|
3571
|
+
...item,
|
|
3572
|
+
planFailure
|
|
3573
|
+
} : item);
|
|
3513
3574
|
}));
|
|
3514
3575
|
server.registerTool("taiga_get_plan", {
|
|
3515
3576
|
title: "Get the active plan",
|
|
@@ -3572,7 +3633,7 @@ const BUILD_STATION = /* @__PURE__ */ new Set(["plan", "build"]);
|
|
|
3572
3633
|
function registerPlanAgain(server, client) {
|
|
3573
3634
|
server.registerTool("taiga_plan_again", {
|
|
3574
3635
|
title: "Plan an initiative again",
|
|
3575
|
-
description: "Supersede the plan of the initiative at the Build station and ask the planner for a new one: for a plan that failed, a plan you do not want built, or a run whose direction was wrong once it has stopped. The earlier plan is kept, and every run records which plan version it built; a pull request still open stays open. Offered only when the turn is yours: refused (CONFLICT) while an agent holds it (planning, building, reviewing). Also refused while a dependency is still in flight or a UI direction has no chosen concept, unless force says to plan anyway, which only the person decides. Anywhere off the Build station, put the initiative in the queue instead (taiga_queue_backlog_item): the queue is the only way to start work. To send a plan back with direction, taiga_reject_plan. Starts a planner run
|
|
3636
|
+
description: "Supersede the plan of the initiative at the Build station and ask the planner for a new one: for a plan that failed, a plan you do not want built, or a run whose direction was wrong once it has stopped. The earlier plan is kept, and every run records which plan version it built; a pull request still open stays open. Offered only when the turn is yours: refused (CONFLICT) while an agent holds it (planning, building, reviewing). Also refused while a dependency is still in flight or a UI direction has no chosen concept, unless force says to plan anyway, which only the person decides. Anywhere off the Build station, put the initiative in the queue instead (taiga_queue_backlog_item): the queue is the only way to start work. To send a plan back with direction, taiga_reject_plan. Starts a planner run.",
|
|
3576
3637
|
inputSchema: z.object({
|
|
3577
3638
|
itemId,
|
|
3578
3639
|
force: z.boolean().optional().describe("Plan anyway past an unsettled dependency or an unchosen UI direction; only when the person says so")
|
|
@@ -3657,7 +3718,7 @@ function registerQueue(server, client) {
|
|
|
3657
3718
|
function registerReviewGate(server, client) {
|
|
3658
3719
|
server.registerTool("taiga_set_auto_build", {
|
|
3659
3720
|
title: "Set an initiative's review gate",
|
|
3660
|
-
description: "Set whether an initiative's plan is built as soon as it lands (true) or waits for a person to read it and approve or reject it (false); null follows the product's default. Standing: it applies to every plan the initiative gets, including one being written now. Opening the gate on a plan that is already waiting starts its build
|
|
3721
|
+
description: "Set whether an initiative's plan is built as soon as it lands (true) or waits for a person to read it and approve or reject it (false); null follows the product's default. Standing: it applies to every plan the initiative gets, including one being written now. Opening the gate on a plan that is already waiting starts its build at once. Returns the initiative with the gate that now applies.",
|
|
3661
3722
|
inputSchema: z.object({
|
|
3662
3723
|
itemId,
|
|
3663
3724
|
autoBuild: autoBuildInput
|
|
@@ -3676,7 +3737,7 @@ function registerReviewGate(server, client) {
|
|
|
3676
3737
|
function registerResume(server, client) {
|
|
3677
3738
|
server.registerTool("taiga_resume_backlog_item", {
|
|
3678
3739
|
title: "Resume backlog item",
|
|
3679
|
-
description: "Resume an initiative the line stopped on, which is when needsHumanCause is set (the line sets it): a failed or cancelled plan is planned again, a stopped or failed run resumes from its first unfinished part (the steps already committed are kept), a refused build start is cleared and retried. This is the way to carry work on: a run is the record of an attempt, and decisions about the work are made on the initiative. Refused (CONFLICT) when nothing is flagged or the cause has no resume: a plan that failed with needsHumanCause null (lifecycle.cta retry_plan, planFailureCause says why) is planned again with taiga_plan_again. Starts paid work
|
|
3740
|
+
description: "Resume an initiative the line stopped on, which is when needsHumanCause is set (the line sets it): a failed or cancelled plan is planned again, a stopped or failed run resumes from its first unfinished part (the steps already committed are kept), a refused build start is cleared and retried. This is the way to carry work on: a run is the record of an attempt, and decisions about the work are made on the initiative. Refused (CONFLICT) when nothing is flagged or the cause has no resume: a plan that failed with needsHumanCause null (lifecycle.cta retry_plan, planFailureCause says why) is planned again with taiga_plan_again. Starts paid work.",
|
|
3680
3741
|
inputSchema: z.object({ itemId }),
|
|
3681
3742
|
annotations: OPERATE
|
|
3682
3743
|
}, (params) => runTool(async () => outcome((await client.resumeInitiative(params.itemId)).data)));
|
|
@@ -3684,13 +3745,13 @@ function registerResume(server, client) {
|
|
|
3684
3745
|
function registerPlanVerdicts(server, client) {
|
|
3685
3746
|
server.registerTool("taiga_approve_plan", {
|
|
3686
3747
|
title: "Approve a plan",
|
|
3687
|
-
description: "Approve an initiative's plan: its build starts now, as the approver, the same start the Start run button makes. Read the plan first with taiga_get_plan and name its ID; an approval of a plan that was replaced since is refused (CONFLICT), as is one while a new plan is being generated or a build is already running. Starts a coding run that pushes commits and opens a pull request
|
|
3748
|
+
description: "Approve an initiative's plan: its build starts now, as the approver, the same start the Start run button makes. Read the plan first with taiga_get_plan and name its ID; an approval of a plan that was replaced since is refused (CONFLICT), as is one while a new plan is being generated or a build is already running. Starts a coding run that pushes commits and opens a pull request.",
|
|
3688
3749
|
inputSchema: z.object({ planId }),
|
|
3689
3750
|
annotations: OPERATE
|
|
3690
3751
|
}, (params) => runTool(async () => outcome((await client.approvePlan(params.planId)).data)));
|
|
3691
3752
|
server.registerTool("taiga_reject_plan", {
|
|
3692
3753
|
title: "Reject a plan",
|
|
3693
|
-
description: "Send an initiative's plan back with direction: a new plan is generated with the comment as reviewer feedback and the rejected plan as its starting point, which supersedes it when the new one lands. Read the plan first with taiga_get_plan and name its ID. Starts a planner run
|
|
3754
|
+
description: "Send an initiative's plan back with direction: a new plan is generated with the comment as reviewer feedback and the rejected plan as its starting point, which supersedes it when the new one lands. Read the plan first with taiga_get_plan and name its ID. Starts a planner run.",
|
|
3694
3755
|
inputSchema: z.object({
|
|
3695
3756
|
planId,
|
|
3696
3757
|
comment: z.string().trim().min(1).max(PLAN_REJECTION_COMMENT_MAX_CHARS).describe("What the next plan should do differently: a paragraph of direction")
|
|
@@ -3943,6 +4004,17 @@ function withoutCitations(doc) {
|
|
|
3943
4004
|
dropped
|
|
3944
4005
|
};
|
|
3945
4006
|
}
|
|
4007
|
+
/**
|
|
4008
|
+
* How much of the two documents the bundle carries. The outline by default
|
|
4009
|
+
* (PRO-2778): the whole sections that fit a budget made the opening read run
|
|
4010
|
+
* to 43k characters for an active product, most of it prose a "where does it
|
|
4011
|
+
* stand" question never reads.
|
|
4012
|
+
*/
|
|
4013
|
+
const DOCUMENT_DEPTHS = [
|
|
4014
|
+
"outline",
|
|
4015
|
+
"budget",
|
|
4016
|
+
"full"
|
|
4017
|
+
];
|
|
3946
4018
|
/** Pages of 100 the backlog part follows before it stops and says so. */
|
|
3947
4019
|
const BACKLOG_PAGE_LIMIT = 10;
|
|
3948
4020
|
/**
|
|
@@ -4005,7 +4077,7 @@ async function factoryPart(client, factoryId) {
|
|
|
4005
4077
|
}
|
|
4006
4078
|
/**
|
|
4007
4079
|
* The specification and architecture from the product's own timeline: their
|
|
4008
|
-
* whole sections up to a budget
|
|
4080
|
+
* outline by default, the whole sections up to a budget or in full when
|
|
4009
4081
|
* asked. Null, with a note, where unpublished.
|
|
4010
4082
|
*/
|
|
4011
4083
|
async function documentsPart(client, product, depth) {
|
|
@@ -4039,11 +4111,16 @@ async function documentsPart(client, product, depth) {
|
|
|
4039
4111
|
} else out[type] = cut.doc;
|
|
4040
4112
|
} else {
|
|
4041
4113
|
const sections = sectionsOf(doc.data ?? {});
|
|
4042
|
-
if (sections)
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4114
|
+
if (sections) {
|
|
4115
|
+
const outline = Object.keys(sections).map((id) => ({
|
|
4116
|
+
id,
|
|
4117
|
+
characters: sectionContent(sections, id)?.length ?? 0
|
|
4118
|
+
}));
|
|
4119
|
+
out[type] = {
|
|
4120
|
+
version: doc.version,
|
|
4121
|
+
sections: outline
|
|
4122
|
+
};
|
|
4123
|
+
} else {
|
|
4047
4124
|
out[type] = {
|
|
4048
4125
|
version: doc.version,
|
|
4049
4126
|
sections: null
|
|
@@ -4160,7 +4237,7 @@ async function productContext(client, product, options) {
|
|
|
4160
4237
|
...bundle,
|
|
4161
4238
|
notGranted: notGranted.map((part) => `${part} (needs ${PARTS[part]})`),
|
|
4162
4239
|
unavailable,
|
|
4163
|
-
readNext: "Read a policy with taiga_get_document (sections: the ids listed here, for only those), another product document with taiga_get_product_document, a backlog item with taiga_get_backlog_item (taiga_list_backlog_items pages a truncated backlog) and a run with taiga_get_build."
|
|
4240
|
+
readNext: "Read a policy with taiga_get_document (sections: the ids listed here, for only those), the specification's or architecture's sections or another product document with taiga_get_product_document, a backlog item with taiga_get_backlog_item (taiga_list_backlog_items pages a truncated backlog) and a run with taiga_get_build."
|
|
4164
4241
|
};
|
|
4165
4242
|
let characters = JSON.stringify(result, null, 2).length;
|
|
4166
4243
|
for (let measured = -1; measured !== characters;) {
|
|
@@ -4179,12 +4256,12 @@ function registerContextTools(server, client) {
|
|
|
4179
4256
|
if (!client.hasCapability("products:read")) return;
|
|
4180
4257
|
server.registerTool("taiga_get_product_context", {
|
|
4181
4258
|
title: "Get product context",
|
|
4182
|
-
description: `Everything a coding session starts from, in one call: the product and which document timeline is its own, its factory with the instructions in full, the specification and architecture from that timeline, every published policy as its section ids, the open backlog items, the linked repositories and the latest runs. Name the product by ID, slug or the git remote URL of the checkout. A part the API refused or failed is named under unavailable with what it said; a part the key cannot read is named under notGranted; neither is shown as empty. The two documents
|
|
4259
|
+
description: `Everything a coding session starts from, in one call: the product and which document timeline is its own, its factory with the instructions in full, the specification and architecture from that timeline, every published policy as its section ids, the open backlog items, the linked repositories and the latest runs. Name the product by ID, slug or the git remote URL of the checkout. A part the API refused or failed is named under unavailable with what it said; a part the key cannot read is named under notGranted; neither is shown as empty. The two documents come as their outline by default: each section id with its size, to read with taiga_get_product_document naming the sections. Before writing code, documents: budget brings as many whole sections as fit, about ${DOCUMENT_BUDGET / 1e3}k characters each, naming the ones left out; full brings them whole, tens of thousands of tokens on a large product and more than some clients accept. characters says how large the result is. Call this first, then read what you need.`,
|
|
4183
4260
|
inputSchema: z.object({
|
|
4184
4261
|
productId: productId.optional(),
|
|
4185
4262
|
slug: slug.optional(),
|
|
4186
4263
|
remoteUrl: remoteUrl.optional(),
|
|
4187
|
-
|
|
4264
|
+
documents: z.enum(DOCUMENT_DEPTHS).optional().describe("The specification and architecture as outline (section ids and sizes, the default), budget (the whole sections that fit) or full")
|
|
4188
4265
|
}),
|
|
4189
4266
|
annotations: READ_ONLY
|
|
4190
4267
|
}, (params) => runTool(async () => {
|
|
@@ -4194,8 +4271,7 @@ function registerContextTools(server, client) {
|
|
|
4194
4271
|
"remoteUrl"
|
|
4195
4272
|
]);
|
|
4196
4273
|
if ("refusal" in resolved) return toolError(resolved.refusal.code, resolved.refusal.message);
|
|
4197
|
-
|
|
4198
|
-
return productContext(client, resolved.product, { documents });
|
|
4274
|
+
return productContext(client, resolved.product, { documents: params.documents ?? "outline" });
|
|
4199
4275
|
}));
|
|
4200
4276
|
}
|
|
4201
4277
|
//#endregion
|
|
@@ -4203,7 +4279,7 @@ function registerContextTools(server, client) {
|
|
|
4203
4279
|
function registerAutonomyTools(server, client) {
|
|
4204
4280
|
if (client.hasCapability("products:write")) server.registerTool("taiga_set_product_autonomy", {
|
|
4205
4281
|
title: "Set a product's autonomy defaults",
|
|
4206
|
-
description: "Set the defaults a product's initiatives follow when they have not decided for themselves. autoBuild: false makes every landed plan wait for a person to approve or reject it; true builds it as soon as the product has room. autoMerge: false makes pull requests wait for a person to merge them; null follows the organization and factory above (autonomous merge needs the organization's switch on, and a factory can turn it off for everything under it; neither can be opened from here). Defaults, not rules: an initiative can decide otherwise for itself. Changes how work proceeds
|
|
4282
|
+
description: "Set the defaults a product's initiatives follow when they have not decided for themselves. autoBuild: false makes every landed plan wait for a person to approve or reject it; true builds it as soon as the product has room. autoMerge: false makes pull requests wait for a person to merge them; null follows the organization and factory above (autonomous merge needs the organization's switch on, and a factory can turn it off for everything under it; neither can be opened from here). Defaults, not rules: an initiative can decide otherwise for itself. Changes how work proceeds.",
|
|
4207
4283
|
inputSchema: z.object({
|
|
4208
4284
|
productId,
|
|
4209
4285
|
autoBuild: z.boolean().optional().describe("Build landed plans without a review by default"),
|
|
@@ -4227,7 +4303,7 @@ function registerAutonomyTools(server, client) {
|
|
|
4227
4303
|
}));
|
|
4228
4304
|
if (client.hasCapability("backlog:write")) server.registerTool("taiga_set_auto_merge", {
|
|
4229
4305
|
title: "Set whether an initiative's pull request merges on its own",
|
|
4230
|
-
description: "An initiative's own switch for merging on its own: true lets its pull request merge itself once the repository's own rules pass (required checks, reviews, a code reviewer you made Taiga wait for); false makes it wait for a person; null follows the product's default. The final word within what the organization and factories allow: where either has turned autonomous merge off, true changes nothing. Taiga merges through your rules, never around them.
|
|
4306
|
+
description: "An initiative's own switch for merging on its own: true lets its pull request merge itself once the repository's own rules pass (required checks, reviews, a code reviewer you made Taiga wait for); false makes it wait for a person; null follows the product's default. The final word within what the organization and factories allow: where either has turned autonomous merge off, true changes nothing. Taiga merges through your rules, never around them.",
|
|
4231
4307
|
inputSchema: z.object({
|
|
4232
4308
|
itemId: idInput("backlog item", "taiga_list_backlog_items (query finds one by title) gives it"),
|
|
4233
4309
|
autoMerge: z.boolean().nullable().describe("true merges on its own, false waits for a person, null follows the product")
|
|
@@ -4279,11 +4355,15 @@ const SETUP_TASK_FIELDS = [
|
|
|
4279
4355
|
"inboxItemStatus",
|
|
4280
4356
|
"createdAt"
|
|
4281
4357
|
];
|
|
4358
|
+
/** The API's count across every page, on the first page; nothing from an API that does not send one. */
|
|
4359
|
+
function totalOf(result) {
|
|
4360
|
+
return typeof result.totalCount === "number" ? { totalCount: result.totalCount } : {};
|
|
4361
|
+
}
|
|
4282
4362
|
function registerInboxTools(server, client) {
|
|
4283
4363
|
if (!client.hasCapability("products:read")) return;
|
|
4284
4364
|
server.registerTool("taiga_list_action_required", {
|
|
4285
4365
|
title: "List what needs a person",
|
|
4286
|
-
description: "Read Action Required, where work comes back to a person: things to review (a plan, a set of initiatives, a document, a pull request, maintenance findings, new policy violations), each a decision an agent handed back rather than made, and things to set up that only a person can (a secret, an environment variable, an account, a package registry, a cloud permission). Across every product the key can open (each row names its product), or one product. category gate is the reviews, setup the setup items; resolved: true reads the archive of done and dismissed ones. The inbox is shared: it is the team's, not one person's. An item snoozed until later is left out until then, as the page leaves it out (snoozedHidden counts them). taiga_list_setup_tasks has the setup tasks with what exactly to set.",
|
|
4366
|
+
description: "Read Action Required, where work comes back to a person: things to review (a plan, a set of initiatives, a document, a pull request, maintenance findings, new policy violations), each a decision an agent handed back rather than made, and things to set up that only a person can (a secret, an environment variable, an account, a package registry, a cloud permission). Across every product the key can open (each row names its product), or one product. category gate is the reviews, setup the setup items; resolved: true reads the archive of done and dismissed ones. The inbox is shared: it is the team's, not one person's. An item snoozed until later is left out until then, as the page leaves it out (snoozedHidden counts them). The first page says how many there are on every page (totalCount, snoozed ones included). taiga_list_setup_tasks has the setup tasks with what exactly to set.",
|
|
4287
4367
|
inputSchema: z.object({
|
|
4288
4368
|
productId: productId.optional(),
|
|
4289
4369
|
category: z.enum(["gate", "setup"]).optional().describe("gate: things to review; setup: things to set up"),
|
|
@@ -4295,7 +4375,8 @@ function registerInboxTools(server, client) {
|
|
|
4295
4375
|
const { resolved, ...rest } = params;
|
|
4296
4376
|
const result = await client.listInbox({
|
|
4297
4377
|
...rest,
|
|
4298
|
-
...resolved ? { resolved: true } : {}
|
|
4378
|
+
...resolved ? { resolved: true } : {},
|
|
4379
|
+
...params.cursor === void 0 ? { withTotal: true } : {}
|
|
4299
4380
|
});
|
|
4300
4381
|
const now = Date.now();
|
|
4301
4382
|
const rows = result.data;
|
|
@@ -4303,12 +4384,13 @@ function registerInboxTools(server, client) {
|
|
|
4303
4384
|
const hidden = rows.length - due.length;
|
|
4304
4385
|
return toolResult({
|
|
4305
4386
|
...page(pickFields(due, ITEM_FIELDS), result.nextCursor),
|
|
4387
|
+
...totalOf(result),
|
|
4306
4388
|
...hidden > 0 ? { snoozedHidden: hidden } : {}
|
|
4307
4389
|
});
|
|
4308
4390
|
}));
|
|
4309
4391
|
server.registerTool("taiga_list_setup_tasks", {
|
|
4310
4392
|
title: "List setup tasks",
|
|
4311
|
-
description: "The setup tasks agents raised for work only a person can do, with what exactly to set and where: the name of an environment variable, secret or API key to create (key; never its value, which Taiga does not store or display), the provider page to do it on, and the phase that says when it blocks you (prereq: before the deploy, which fails without it; wiring: connecting generated values back to providers; followup: billing approvals, extra permissions and the like that can wait until after launch). A build does not wait for any of them. Across every product the key can open (each row names its product), or one. The tasks still to do, oldest first, the order they depend on each other; includeDone: true adds the done and dismissed ones.",
|
|
4393
|
+
description: "The setup tasks agents raised for work only a person can do, with what exactly to set and where: the name of an environment variable, secret or API key to create (key; never its value, which Taiga does not store or display), the provider page to do it on, and the phase that says when it blocks you (prereq: before the deploy, which fails without it; wiring: connecting generated values back to providers; followup: billing approvals, extra permissions and the like that can wait until after launch). A build does not wait for any of them. Across every product the key can open (each row names its product), or one. The tasks still to do, oldest first, the order they depend on each other; includeDone: true adds the done and dismissed ones. The first page says how many there are on every page (totalCount).",
|
|
4312
4394
|
inputSchema: z.object({
|
|
4313
4395
|
productId: productId.optional(),
|
|
4314
4396
|
includeDone: z.boolean().optional().describe("Also the tasks already done or dismissed (default false)"),
|
|
@@ -4319,9 +4401,13 @@ function registerInboxTools(server, client) {
|
|
|
4319
4401
|
const { includeDone, ...rest } = params;
|
|
4320
4402
|
const result = await client.listSetupTasks({
|
|
4321
4403
|
...rest,
|
|
4322
|
-
...includeDone ? {} : { openOnly: true }
|
|
4404
|
+
...includeDone ? {} : { openOnly: true },
|
|
4405
|
+
...params.cursor === void 0 ? { withTotal: true } : {}
|
|
4406
|
+
});
|
|
4407
|
+
return toolResult({
|
|
4408
|
+
...page(pickFields(result.data, SETUP_TASK_FIELDS), result.nextCursor),
|
|
4409
|
+
...totalOf(result)
|
|
4323
4410
|
});
|
|
4324
|
-
return toolResult(page(pickFields(result.data, SETUP_TASK_FIELDS), result.nextCursor));
|
|
4325
4411
|
}));
|
|
4326
4412
|
if (!client.hasCapability("products:write")) return;
|
|
4327
4413
|
server.registerTool("taiga_resolve_action", {
|
|
@@ -4477,7 +4563,7 @@ function registerKnowledgeTools(server, client) {
|
|
|
4477
4563
|
if (!client.hasCapability("customDocuments:write")) return;
|
|
4478
4564
|
server.registerTool("taiga_add_knowledge", {
|
|
4479
4565
|
title: "Add knowledge",
|
|
4480
|
-
description: "File a Markdown or plain-text reference document as knowledge, at the level it is relevant: a product (productId), a factory (factoryId), or the organization (neither; that takes an owner or admin, because every factory and product inherits it). Good candidates are what a new engineer would be handed to read: specifications and contracts to conform to, glossaries and code lists, third-party documentation, internal write-ups. A rule that applies every time belongs in instructions instead. The file is processed before agents can read it; taiga_list_knowledge says when it is ready. PDFs and spreadsheets are added in the web. The same content already filed at that level comes back as that file, with deduplicated: true: it was not created here, so do not delete it as if it were. Every agent at that level reads it from then on, and processing it is paid work
|
|
4566
|
+
description: "File a Markdown or plain-text reference document as knowledge, at the level it is relevant: a product (productId), a factory (factoryId), or the organization (neither; that takes an owner or admin, because every factory and product inherits it). Good candidates are what a new engineer would be handed to read: specifications and contracts to conform to, glossaries and code lists, third-party documentation, internal write-ups. A rule that applies every time belongs in instructions instead. The file is processed before agents can read it; taiga_list_knowledge says when it is ready. PDFs and spreadsheets are added in the web. The same content already filed at that level comes back as that file, with deduplicated: true: it was not created here, so do not delete it as if it were. Every agent at that level reads it from then on, and processing it is paid work.",
|
|
4481
4567
|
inputSchema: z.object({
|
|
4482
4568
|
...scopeInputs,
|
|
4483
4569
|
fileName: z.string().trim().min(1).max(200).regex(/\.(md|txt)$/i, "A Markdown (.md) or plain-text (.txt) file name").describe("The file name, ending in .md or .txt"),
|
|
@@ -4502,14 +4588,14 @@ function registerKnowledgeTools(server, client) {
|
|
|
4502
4588
|
}));
|
|
4503
4589
|
server.registerTool("taiga_retry_knowledge", {
|
|
4504
4590
|
title: "Retry processing a knowledge file",
|
|
4505
|
-
description: "Retry processing a knowledge file that failed (status failed): until it is processed, nothing reads it. Processing is paid work
|
|
4591
|
+
description: "Retry processing a knowledge file that failed (status failed): until it is processed, nothing reads it. Processing is paid work.",
|
|
4506
4592
|
inputSchema: z.object({ knowledgeId }),
|
|
4507
4593
|
annotations: OPERATE
|
|
4508
4594
|
}, (params) => runTool(async () => toolResult(pickFields([(await client.retryKnowledge(params.knowledgeId)).data], KNOWLEDGE_FIELDS)[0])));
|
|
4509
4595
|
if (!client.hasCapability("customDocuments:delete")) return;
|
|
4510
4596
|
server.registerTool("taiga_delete_knowledge", {
|
|
4511
4597
|
title: "Delete a knowledge file",
|
|
4512
|
-
description: "Delete a knowledge file that is stale or wrong: stale reference material is worse than none, because it is confidently specific, and nothing expires on its own. Agents stop reading it at once. It cannot be undone
|
|
4598
|
+
description: "Delete a knowledge file that is stale or wrong: stale reference material is worse than none, because it is confidently specific, and nothing expires on its own. Agents stop reading it at once. It cannot be undone. Organization knowledge takes an owner or admin.",
|
|
4513
4599
|
inputSchema: z.object({ knowledgeId }),
|
|
4514
4600
|
annotations: OPERATE
|
|
4515
4601
|
}, (params) => runTool(async () => {
|
|
@@ -4617,7 +4703,7 @@ function registerMaintenanceTools(server, client) {
|
|
|
4617
4703
|
}, (params) => runTool(async () => toolResult((await client.getFinding(params.findingId)).data)));
|
|
4618
4704
|
server.registerTool("taiga_get_policy_standing", {
|
|
4619
4705
|
title: "Get a product's policy standing",
|
|
4620
|
-
description: "Where a product stands against the organization's own published policies: the controls it violates, each with its violations (critical and high only, each citing the policy's sentence and the document passage that contradicts it), what is being done about each (an initiative raised, a fix in progress, awaiting verification), and when the documents were derived and assessed, led by a summary of the counts. A control with no violation is not listed, and it is not \"passing\" either: there is no such state. Pass full: true for every violation's evidence and the resolved ones. Fix a control with taiga_fix_policy_violation.",
|
|
4706
|
+
description: "Where a product stands against the organization's own published policies: the controls it violates, each with its violations (critical and high only, each citing the policy's sentence and the document passage that contradicts it), what is being done about each (an initiative raised, a fix in progress, awaiting verification), and when the documents were derived and assessed, led by a summary of the counts. A control breached by scanner findings counts them by tier (findingsByTier): a breach of routine findings only, such as secrets in test fixtures, is not an urgent one. A control with no violation is not listed, and it is not \"passing\" either: there is no such state. Pass full: true for every violation's evidence and the resolved ones. Fix a control with taiga_fix_policy_violation.",
|
|
4621
4707
|
inputSchema: z.object({
|
|
4622
4708
|
productId,
|
|
4623
4709
|
full: z.boolean().optional().describe("Carry each violation's evidence, the policy excerpt and the resolved violations (default false)")
|
|
@@ -4657,6 +4743,7 @@ function registerMaintenanceTools(server, client) {
|
|
|
4657
4743
|
"state",
|
|
4658
4744
|
"divergenceCount",
|
|
4659
4745
|
"findingCount",
|
|
4746
|
+
"findingsByTier",
|
|
4660
4747
|
"plannedCount"
|
|
4661
4748
|
]),
|
|
4662
4749
|
violations: pickFields(violations, VIOLATION_FIELDS)
|
|
@@ -4687,7 +4774,7 @@ function registerMaintenanceTools(server, client) {
|
|
|
4687
4774
|
}));
|
|
4688
4775
|
server.registerTool("taiga_fix_finding", {
|
|
4689
4776
|
title: "Fix a finding",
|
|
4690
|
-
description: "Ask for a finding's fix: Taiga raises an initiative and adds it to the end of the product's queue, where the line plans it in turn; whether the build then waits for a person is the initiative's review gate, as for any initiative. The fix is a normal run that ends in a pull request you review and merge. With wholeGroup: true it fixes the finding's whole group in one initiative, as the page does: for a dependency, every open advisory on that package at that installed version in one version bump; for code or infrastructure, every location the same rule flagged. Absent for a secret only in history, anything in a submodule, and a dependency with no fixed version (remediableBy says upstream_only); the API refuses those. Starts paid work
|
|
4777
|
+
description: "Ask for a finding's fix: Taiga raises an initiative and adds it to the end of the product's queue, where the line plans it in turn; whether the build then waits for a person is the initiative's review gate, as for any initiative. The fix is a normal run that ends in a pull request you review and merge. With wholeGroup: true it fixes the finding's whole group in one initiative, as the page does: for a dependency, every open advisory on that package at that installed version in one version bump; for code or infrastructure, every location the same rule flagged. Absent for a secret only in history, anything in a submodule, and a dependency with no fixed version (remediableBy says upstream_only); the API refuses those. Starts paid work.",
|
|
4691
4778
|
inputSchema: z.object({
|
|
4692
4779
|
productId,
|
|
4693
4780
|
findingId,
|
|
@@ -4713,7 +4800,7 @@ function registerMaintenanceTools(server, client) {
|
|
|
4713
4800
|
}));
|
|
4714
4801
|
server.registerTool("taiga_fix_policy_violation", {
|
|
4715
4802
|
title: "Fix policy violations",
|
|
4716
|
-
description: "Ask for the fix of a product's policy violations: name a control to cover every violation recorded against it that has no initiative yet, or a single violation (violationId) when the control has more than one. Taiga raises one initiative and adds it to the end of the product's queue, exactly as a finding fix does. The violation stays listed while the work is under way: only the next assessment can record that the documents no longer show it. Starts paid work
|
|
4803
|
+
description: "Ask for the fix of a product's policy violations: name a control to cover every violation recorded against it that has no initiative yet, or a single violation (violationId) when the control has more than one. Taiga raises one initiative and adds it to the end of the product's queue, exactly as a finding fix does. The violation stays listed while the work is under way: only the next assessment can record that the documents no longer show it. Starts paid work.",
|
|
4717
4804
|
inputSchema: z.object({
|
|
4718
4805
|
productId,
|
|
4719
4806
|
controlId: z.string().min(1).max(40).optional().describe("The control's identifier, e.g. SEC-3.4"),
|
|
@@ -4755,8 +4842,13 @@ const ENVIRONMENT_FIELDS = [
|
|
|
4755
4842
|
"isProtected",
|
|
4756
4843
|
"url"
|
|
4757
4844
|
];
|
|
4758
|
-
const environmentId = idInput("environment", "
|
|
4759
|
-
|
|
4845
|
+
const environmentId = idInput("environment", "taiga_list_environments lists them; a product ID is not one");
|
|
4846
|
+
/** The windows the pages offer, as the schema's own values rather than a number checked afterwards. */
|
|
4847
|
+
const windowDays = z.literal([
|
|
4848
|
+
30,
|
|
4849
|
+
60,
|
|
4850
|
+
90
|
|
4851
|
+
]).optional().describe("Days to look back: 30, 60 or 90 (default 30)");
|
|
4760
4852
|
/** Whether a failure is the API's (refused, missing, unreachable) rather than a bug here. */
|
|
4761
4853
|
const isApiFailure = (error) => error instanceof TaigaApiError || isNetworkFailure(error);
|
|
4762
4854
|
/** A part that may fail on its own without failing the read: null and the reason, or the value. */
|
|
@@ -4828,22 +4920,30 @@ function registerObserveTools(server, client) {
|
|
|
4828
4920
|
...note ? { note } : {}
|
|
4829
4921
|
});
|
|
4830
4922
|
}));
|
|
4923
|
+
server.registerTool("taiga_list_environments", {
|
|
4924
|
+
title: "List a product's environments",
|
|
4925
|
+
description: "The places a product's software runs (development, staging, production or its own ladder), each with its ID, type, order and App URL. The environment IDs taiga_get_delivery_metrics, taiga_get_monitoring and taiga_update_environment take. taiga_get_deployments lists them too, beside what runs where.",
|
|
4926
|
+
inputSchema: z.object({ productId }),
|
|
4927
|
+
annotations: READ_ONLY
|
|
4928
|
+
}, (params) => runTool(async () => {
|
|
4929
|
+
const { rows, more } = await allEnvironments(client, params.productId);
|
|
4930
|
+
return toolResult({
|
|
4931
|
+
data: pickFields(rows, ENVIRONMENT_FIELDS),
|
|
4932
|
+
...more ? { more: "More environments than this answer reads; the rest were left out." } : {},
|
|
4933
|
+
...rows.length === 0 ? { note: "No environment is defined yet; taiga_define_environment adds one." } : {}
|
|
4934
|
+
});
|
|
4935
|
+
}));
|
|
4831
4936
|
server.registerTool("taiga_get_delivery_metrics", {
|
|
4832
4937
|
title: "Get delivery metrics",
|
|
4833
4938
|
description: "DORA's five software delivery metrics for one environment (the one marked production, as the page reports them), computed from its deployment history over the last 30, 60 or 90 days: deployment frequency, lead time and failed deployment recovery time (throughput), change-failure rate and rework rate (instability), each with its trend against the window before, and a performance band for all but rework rate, which DORA has published none for. Failures and recoveries are read from deployments, not incidents. A trend built on fewer than ten deployments is approximate; a metric no deployment fed says so instead of showing a number. Previews and checks do not count.",
|
|
4834
4939
|
inputSchema: z.object({
|
|
4835
4940
|
productId,
|
|
4836
4941
|
environmentId,
|
|
4837
|
-
windowDays
|
|
4942
|
+
windowDays
|
|
4838
4943
|
}),
|
|
4839
4944
|
annotations: READ_ONLY
|
|
4840
4945
|
}, (params) => runTool(async () => {
|
|
4841
4946
|
const days = params.windowDays ?? 30;
|
|
4842
|
-
if (![
|
|
4843
|
-
30,
|
|
4844
|
-
60,
|
|
4845
|
-
90
|
|
4846
|
-
].includes(days)) return toolError("VALIDATION_ERROR", "windowDays is 30, 60 or 90.");
|
|
4847
4947
|
return toolResult((await client.getDeliveryMetrics(params.productId, params.environmentId, days)).data);
|
|
4848
4948
|
}));
|
|
4849
4949
|
server.registerTool("taiga_get_monitoring", {
|
|
@@ -4852,7 +4952,7 @@ function registerObserveTools(server, client) {
|
|
|
4852
4952
|
inputSchema: z.object({
|
|
4853
4953
|
productId,
|
|
4854
4954
|
environmentId: environmentId.optional(),
|
|
4855
|
-
windowDays
|
|
4955
|
+
windowDays
|
|
4856
4956
|
}),
|
|
4857
4957
|
annotations: READ_ONLY
|
|
4858
4958
|
}, (params) => runTool(async () => {
|
|
@@ -4862,11 +4962,6 @@ function registerObserveTools(server, client) {
|
|
|
4862
4962
|
...health.data.length === 0 ? { note: "No environment is monitored: monitoring starts when an environment has its App URL (taiga_define_environment, taiga_update_environment). No health is not good health." } : {}
|
|
4863
4963
|
});
|
|
4864
4964
|
const days = params.windowDays ?? 30;
|
|
4865
|
-
if (![
|
|
4866
|
-
30,
|
|
4867
|
-
60,
|
|
4868
|
-
90
|
|
4869
|
-
].includes(days)) return toolError("VALIDATION_ERROR", "windowDays is 30, 60 or 90, the windows the page offers.");
|
|
4870
4965
|
const env = params.environmentId;
|
|
4871
4966
|
const [uptime, experience, clientHealth] = await Promise.all([
|
|
4872
4967
|
part(() => client.getMonitoringSignal(params.productId, "uptime", env, days)),
|
|
@@ -4892,7 +4987,7 @@ function registerObserveTools(server, client) {
|
|
|
4892
4987
|
if (!client.hasCapability("products:write")) return;
|
|
4893
4988
|
server.registerTool("taiga_define_environment", {
|
|
4894
4989
|
title: "Define an environment",
|
|
4895
|
-
description: "Add a place a product's software runs: development, staging, production or whatever ladder it uses. Deployments are recorded against an environment, monitoring is reported per environment, and plans read where the work will run. Describing one gives Taiga no access to it: the pipeline deploys, Taiga learns what happened from source control. Member-level work. Give it its App URL afterwards with taiga_update_environment, which is all monitoring needs to start checking. Changes the product's setup for everybody
|
|
4990
|
+
description: "Add a place a product's software runs: development, staging, production or whatever ladder it uses. Deployments are recorded against an environment, monitoring is reported per environment, and plans read where the work will run. Describing one gives Taiga no access to it: the pipeline deploys, Taiga learns what happened from source control. Member-level work. Give it its App URL afterwards with taiga_update_environment, which is all monitoring needs to start checking. Changes the product's setup for everybody.",
|
|
4896
4991
|
inputSchema: z.object({
|
|
4897
4992
|
productId,
|
|
4898
4993
|
name: z.string().trim().min(1).max(100).describe("What the team calls it, e.g. staging"),
|
|
@@ -4908,7 +5003,7 @@ function registerObserveTools(server, client) {
|
|
|
4908
5003
|
}));
|
|
4909
5004
|
server.registerTool("taiga_update_environment", {
|
|
4910
5005
|
title: "Update an environment",
|
|
4911
|
-
description: "Rename an environment, change its kind, or set its public App URL (null clears it). The App URL is all monitoring needs: once it is saved, Taiga checks the app's availability every few minutes on its own. Marking an environment production makes it the one delivery metrics report on. Changes the product's setup for everybody
|
|
5006
|
+
description: "Rename an environment, change its kind, or set its public App URL (null clears it). The App URL is all monitoring needs: once it is saved, Taiga checks the app's availability every few minutes on its own. Marking an environment production makes it the one delivery metrics report on. Changes the product's setup for everybody.",
|
|
4912
5007
|
inputSchema: z.object({
|
|
4913
5008
|
environmentId,
|
|
4914
5009
|
name: z.string().trim().min(1).max(100).optional().describe("A new name"),
|
|
@@ -4967,11 +5062,11 @@ function registerProductDocumentWrites(server, client) {
|
|
|
4967
5062
|
productId,
|
|
4968
5063
|
type: reviewableType
|
|
4969
5064
|
}),
|
|
4970
|
-
annotations:
|
|
5065
|
+
annotations: OPERATE
|
|
4971
5066
|
}, (params) => runTool(async () => toolResult((await client.markProductDocumentReviewed(params.productId, params.type)).data)));
|
|
4972
5067
|
if (canRead) server.registerTool("taiga_regenerate_product_document", {
|
|
4973
5068
|
title: "Regenerate a product document",
|
|
4974
|
-
description: "Ask an agent to generate one product document again from the documents it is derived from, for one marked outdated or one that needs another pass. The Specification comes from the Discovery conversation and the Look & Feel from its concepts, both in the web. The documents Discovery requires are regenerated while it is open only: a finished Discovery locks them, and reopening it is done in the web. The optional Service Blueprint can be generated whenever someone wants one. The result is published as the new version, and the documents generated from it are marked outdated in turn. Runs an agent in the background: this returns the job; taiga_wait_for_job says when it is done.
|
|
5069
|
+
description: "Ask an agent to generate one product document again from the documents it is derived from, for one marked outdated or one that needs another pass. The Specification comes from the Discovery conversation and the Look & Feel from its concepts, both in the web. The documents Discovery requires are regenerated while it is open only: a finished Discovery locks them, and reopening it is done in the web. The optional Service Blueprint can be generated whenever someone wants one. The result is published as the new version, and the documents generated from it are marked outdated in turn. Runs an agent in the background: this returns the job; taiga_wait_for_job says when it is done.",
|
|
4975
5070
|
inputSchema: z.object({
|
|
4976
5071
|
productId,
|
|
4977
5072
|
type: generatedType
|
|
@@ -4990,13 +5085,13 @@ function registerProductDocumentWrites(server, client) {
|
|
|
4990
5085
|
}));
|
|
4991
5086
|
server.registerTool("taiga_generate_product_documents", {
|
|
4992
5087
|
title: "Generate the remaining product documents",
|
|
4993
|
-
description: "Generate remaining: every document the product still needs, and every one marked outdated, in the order they build on each other, one after another (each next one starts when the one before it is published). Refused (CONFLICT) while a document is already being generated. On an imported product, it queues every unpublished document. Runs agents in the background: this returns the jobs it started (jobIds); taiga_stop_generating_documents stops the run.
|
|
5088
|
+
description: "Generate remaining: every document the product still needs, and every one marked outdated, in the order they build on each other, one after another (each next one starts when the one before it is published). Refused (CONFLICT) while a document is already being generated. On an imported product, it queues every unpublished document. Runs agents in the background: this returns the jobs it started (jobIds); taiga_stop_generating_documents stops the run.",
|
|
4994
5089
|
inputSchema: z.object({ productId }),
|
|
4995
5090
|
annotations: OPERATE
|
|
4996
5091
|
}, (params) => runTool(async () => toolResult((await client.generateRemainingDocuments(params.productId)).data)));
|
|
4997
5092
|
server.registerTool("taiga_stop_generating_documents", {
|
|
4998
5093
|
title: "Stop generating product documents",
|
|
4999
|
-
description: "Stop a Generate remaining run: every document of it still being generated is cancelled and nothing after them starts; published documents stay. An empty list means no run was going. Interrupts agent work
|
|
5094
|
+
description: "Stop a Generate remaining run: every document of it still being generated is cancelled and nothing after them starts; published documents stay. An empty list means no run was going. Interrupts agent work.",
|
|
5000
5095
|
inputSchema: z.object({ productId }),
|
|
5001
5096
|
annotations: OPERATE
|
|
5002
5097
|
}, (params) => runTool(async () => toolResult((await client.stopGeneratingDocuments(params.productId)).data)));
|
|
@@ -19738,6 +19833,13 @@ const GITHUB_REPOSITORY_FIELDS = [
|
|
|
19738
19833
|
"language"
|
|
19739
19834
|
];
|
|
19740
19835
|
const repositoryId = idInput("repository link", "taiga_list_product_repositories gives it; a GitHub repository id is not one");
|
|
19836
|
+
/** Round-robin over lists: the first of each, then the second of each, and so on. */
|
|
19837
|
+
function interleave(lists) {
|
|
19838
|
+
const out = [];
|
|
19839
|
+
const longest = Math.max(0, ...lists.map((list) => list.length));
|
|
19840
|
+
for (let index = 0; index < longest; index++) for (const list of lists) if (index < list.length) out.push(list[index]);
|
|
19841
|
+
return out;
|
|
19842
|
+
}
|
|
19741
19843
|
function registerRepositoryTools(server, client) {
|
|
19742
19844
|
if (client.hasCapability("products:read")) {
|
|
19743
19845
|
server.registerTool("taiga_list_product_repositories", {
|
|
@@ -19746,7 +19848,11 @@ function registerRepositoryTools(server, client) {
|
|
|
19746
19848
|
inputSchema: z.object({ productId }),
|
|
19747
19849
|
annotations: READ_ONLY
|
|
19748
19850
|
}, (params) => runTool(async () => {
|
|
19749
|
-
|
|
19851
|
+
const result = await client.listProductRepositories(params.productId);
|
|
19852
|
+
return toolResult({
|
|
19853
|
+
data: pickFields(result.data, LINK_FIELDS),
|
|
19854
|
+
truncated: result.nextCursor !== null
|
|
19855
|
+
});
|
|
19750
19856
|
}));
|
|
19751
19857
|
server.registerTool("taiga_list_repository_branches", {
|
|
19752
19858
|
title: "List a repository's branches",
|
|
@@ -19759,16 +19865,17 @@ function registerRepositoryTools(server, client) {
|
|
|
19759
19865
|
}, (params) => runTool(async () => {
|
|
19760
19866
|
const result = await client.listRepositoryBranches(params.productId, params.repositoryId);
|
|
19761
19867
|
return toolResult({
|
|
19762
|
-
|
|
19868
|
+
data: result.data,
|
|
19763
19869
|
truncated: result.truncated
|
|
19764
19870
|
});
|
|
19765
19871
|
}));
|
|
19766
19872
|
}
|
|
19767
19873
|
if (client.hasCapability("vcs:read")) server.registerTool("taiga_list_linkable_repositories", {
|
|
19768
19874
|
title: "Find a repository to link",
|
|
19769
|
-
description: "The repositories the organization's GitHub connection can reach, to link one to a product (taiga_link_repository takes its id). Searches by name with query; without
|
|
19875
|
+
description: "The repositories the organization's GitHub connection can reach, to link one to a product (taiga_link_repository takes its id). Searches by name with query. Name the GitHub account (account: its login, the owner in owner/repo, or installationId) to read only its repositories; without one it reads every connected account and takes rows from each in turn, so one account cannot fill the page. No connection means GitHub is not connected yet: that is done once, in the web, by someone who can install the app.",
|
|
19770
19876
|
inputSchema: z.object({
|
|
19771
19877
|
query: z.string().trim().min(1).max(256).optional().describe("Part of the repository name"),
|
|
19878
|
+
account: z.string().trim().min(1).max(140).optional().describe("The GitHub account login (the owner in owner/repo), case-insensitive"),
|
|
19772
19879
|
installationId: idInput("GitHub account", "this tool lists them").optional(),
|
|
19773
19880
|
...pagination
|
|
19774
19881
|
}),
|
|
@@ -19777,10 +19884,11 @@ function registerRepositoryTools(server, client) {
|
|
|
19777
19884
|
const installations = (await client.listGithubInstallations()).data;
|
|
19778
19885
|
const accounts = pickFields(installations, INSTALLATION_FIELDS);
|
|
19779
19886
|
if (installations.length === 0) return toolError("NOT_CONNECTED", "GitHub is not connected for this organization. It is connected once, in the web; then link.");
|
|
19780
|
-
const
|
|
19887
|
+
const login = params.account?.split("/")[0].toLowerCase();
|
|
19888
|
+
const chosen = installations.filter((i) => (!params.installationId || i.id === params.installationId) && (!login || String(i.accountLogin ?? "").toLowerCase() === login));
|
|
19781
19889
|
if (chosen.length === 0) return toolError("NOT_FOUND", "No such GitHub connection; these are the connected accounts.", { accounts });
|
|
19782
19890
|
if (params.cursor !== void 0 && params.query !== void 0) return toolError("VALIDATION_ERROR", SEARCH_WITH_CURSOR);
|
|
19783
|
-
if (chosen.length > 1 && params.cursor !== void 0) return toolError("VALIDATION_ERROR", "A cursor pages one account: name
|
|
19891
|
+
if (chosen.length > 1 && params.cursor !== void 0) return toolError("VALIDATION_ERROR", "A cursor pages one account: name it (account or installationId).", { accounts });
|
|
19784
19892
|
const max = params.limit ?? 25;
|
|
19785
19893
|
const rows = (data) => pickFields(data, GITHUB_REPOSITORY_FIELDS);
|
|
19786
19894
|
if (chosen.length === 1) {
|
|
@@ -19800,18 +19908,18 @@ function registerRepositoryTools(server, client) {
|
|
|
19800
19908
|
q: params.query,
|
|
19801
19909
|
limit: max
|
|
19802
19910
|
})));
|
|
19803
|
-
const found = pages.
|
|
19911
|
+
const found = interleave(pages.map((p) => rows(p.data)));
|
|
19804
19912
|
const cut = found.length > max || pages.some((p) => p.nextCursor);
|
|
19805
19913
|
return toolResult({
|
|
19806
19914
|
accounts,
|
|
19807
19915
|
repositories: page(found.slice(0, max), null),
|
|
19808
|
-
...cut ? { more: "More than the limit across accounts: name an
|
|
19916
|
+
...cut ? { more: "More than the limit across accounts: name an account, or narrow the query." } : {}
|
|
19809
19917
|
});
|
|
19810
19918
|
}));
|
|
19811
19919
|
if (!client.hasCapability("vcs:manage")) return;
|
|
19812
19920
|
server.registerTool("taiga_link_repository", {
|
|
19813
19921
|
title: "Link a repository to a product",
|
|
19814
|
-
description: "Link a repository the GitHub connection can reach (taiga_list_linkable_repositories gives its id) to a product, or with replacesRepositoryId swap the one linked now for it in one step (naming the same repository there changes nothing; linking one already linked without it is refused). A link Taiga manages (isManaged) cannot be replaced or unlinked. Once linked, planning reads its code, runs open pull requests in it, sweeps scan it, and its deployment history is imported in the background.
|
|
19922
|
+
description: "Link a repository the GitHub connection can reach (taiga_list_linkable_repositories gives its id) to a product, or with replacesRepositoryId swap the one linked now for it in one step (naming the same repository there changes nothing; linking one already linked without it is refused). A link Taiga manages (isManaged) cannot be replaced or unlinked. Once linked, planning reads its code, runs open pull requests in it, sweeps scan it, and its deployment history is imported in the background.",
|
|
19815
19923
|
inputSchema: z.object({
|
|
19816
19924
|
productId,
|
|
19817
19925
|
githubRepositoryId: idInput("GitHub repository", "taiga_list_linkable_repositories gives it"),
|
|
@@ -19826,7 +19934,7 @@ function registerRepositoryTools(server, client) {
|
|
|
19826
19934
|
}));
|
|
19827
19935
|
server.registerTool("taiga_set_repository_branch", {
|
|
19828
19936
|
title: "Choose the branch Taiga works on",
|
|
19829
|
-
description: "Choose the branch of a product's repository that Taiga branches from and opens its pull requests against (taiga_list_repository_branches lists them), or null to follow the repository's default again. A protected branch is not refused, but Taiga may not be able to merge into it: the answer says when it is protected. Optionally a label for the link.
|
|
19937
|
+
description: "Choose the branch of a product's repository that Taiga branches from and opens its pull requests against (taiga_list_repository_branches lists them), or null to follow the repository's default again. A protected branch is not refused, but Taiga may not be able to merge into it: the answer says when it is protected. Optionally a label for the link.",
|
|
19830
19938
|
inputSchema: z.object({
|
|
19831
19939
|
productId,
|
|
19832
19940
|
repositoryId,
|
|
@@ -19847,7 +19955,7 @@ function registerRepositoryTools(server, client) {
|
|
|
19847
19955
|
}));
|
|
19848
19956
|
server.registerTool("taiga_unlink_repository", {
|
|
19849
19957
|
title: "Unlink a repository from a product",
|
|
19850
|
-
description: "Unlink a repository from a product. Without one, the product can still have documents and initiatives but no changes: nothing is planned against code, built or swept. The repository itself is untouched, and a repository Taiga created for the product cannot be unlinked. To swap it for another, use taiga_link_repository with replacesRepositoryId instead.
|
|
19958
|
+
description: "Unlink a repository from a product. Without one, the product can still have documents and initiatives but no changes: nothing is planned against code, built or swept. The repository itself is untouched, and a repository Taiga created for the product cannot be unlinked. To swap it for another, use taiga_link_repository with replacesRepositoryId instead.",
|
|
19851
19959
|
inputSchema: z.object({
|
|
19852
19960
|
productId,
|
|
19853
19961
|
repositoryId
|
|
@@ -20063,7 +20171,7 @@ function registerSetupTools(server, client) {
|
|
|
20063
20171
|
}, (params) => runTool(async () => toolResult(pickFields([(await client.createFactory({ name: params.name })).data], FACTORY_FIELDS)[0])));
|
|
20064
20172
|
server.registerTool("taiga_rename_factory", {
|
|
20065
20173
|
title: "Rename a factory",
|
|
20066
|
-
description: "Rename a factory. Its products, members, knowledge and instructions stay as they are, but its slug follows the new name, so links to the factory change.
|
|
20174
|
+
description: "Rename a factory. Its products, members, knowledge and instructions stay as they are, but its slug follows the new name, so links to the factory change.",
|
|
20067
20175
|
inputSchema: z.object({
|
|
20068
20176
|
factoryId,
|
|
20069
20177
|
name: z.string().trim().min(1).max(255).describe("The new name")
|
|
@@ -20081,7 +20189,7 @@ function registerSetupTools(server, client) {
|
|
|
20081
20189
|
}, (params) => runTool(async () => toolResult((await client.previewProductMove(params.productId, params.factoryId)).data)));
|
|
20082
20190
|
server.registerTool("taiga_move_product", {
|
|
20083
20191
|
title: "Move a product to another factory",
|
|
20084
|
-
description: "Move a product to another factory. Access follows the factory, so this changes who can reach the product (taiga_preview_product_move says who); the product's own knowledge files are re-filed onto the new factory, and factory knowledge and instructions stay with the factory that owns them. Refused (CONFLICT) while a plan or build is in flight: stopLine: true stops the line first and then moves, which interrupts that work.
|
|
20192
|
+
description: "Move a product to another factory. Access follows the factory, so this changes who can reach the product (taiga_preview_product_move says who); the product's own knowledge files are re-filed onto the new factory, and factory knowledge and instructions stay with the factory that owns them. Refused (CONFLICT) while a plan or build is in flight: stopLine: true stops the line first and then moves, which interrupts that work.",
|
|
20085
20193
|
inputSchema: z.object({
|
|
20086
20194
|
productId,
|
|
20087
20195
|
factoryId: factoryId.describe("The factory to move it to"),
|
|
@@ -20105,7 +20213,7 @@ function registerSetupTools(server, client) {
|
|
|
20105
20213
|
if (!client.hasCapability("products:write")) return;
|
|
20106
20214
|
server.registerTool("taiga_create_product", {
|
|
20107
20215
|
title: "Create a product",
|
|
20108
|
-
description: "Create a product in a factory, the way New product does. It starts one of two ways, chosen now and not later: scratch, where Discovery is a conversation that writes the specification (held in the web), or import (projectType brownfield), where Taiga reads an existing repository and drafts the documents from the code: link the repository (taiga_link_repository; linking is for owners and admins, so a member asks one), then taiga_start_import. Also what Taiga builds for it: its infrastructure code and its CI/CD pipelines, each following the organization unless decided here; when one is off, deploymentNotes says in a sentence how that half is done today. Material the product should be built from is worth adding before Discovery starts (taiga_add_knowledge with the productId): what is added first shapes the specification.",
|
|
20216
|
+
description: "Create a product in a factory, the way New product does. It starts one of two ways, chosen now and not later: scratch (afterwards projectType greenfield), where Discovery is a conversation that writes the specification (held in the web), or import (projectType brownfield), where Taiga reads an existing repository and drafts the documents from the code: link the repository (taiga_link_repository; linking is for owners and admins, so a member asks one), then taiga_start_import. Also what Taiga builds for it: its infrastructure code and its CI/CD pipelines, each following the organization unless decided here; when one is off, deploymentNotes says in a sentence how that half is done today. Material the product should be built from is worth adding before Discovery starts (taiga_add_knowledge with the productId): what is added first shapes the specification.",
|
|
20109
20217
|
inputSchema: z.object({
|
|
20110
20218
|
factoryId,
|
|
20111
20219
|
name: z.string().trim().min(1).max(255).describe("The product name"),
|
|
@@ -20129,7 +20237,7 @@ function registerSetupTools(server, client) {
|
|
|
20129
20237
|
}));
|
|
20130
20238
|
server.registerTool("taiga_update_product", {
|
|
20131
20239
|
title: "Change a product's settings",
|
|
20132
|
-
description: "Change a product's name (its slug follows the name, so links to the product change) or what Taiga builds for it: its infrastructure code and CI/CD pipelines (null returns one to the organization default), the sentence on how a half Taiga does not build is done today, and whether it inherits the organization's design standards (null follows the default for how the product started). Later plans read the new answer; work already planned is not redone. Autonomy is taiga_set_product_autonomy.
|
|
20240
|
+
description: "Change a product's name (its slug follows the name, so links to the product change) or what Taiga builds for it: its infrastructure code and CI/CD pipelines (null returns one to the organization default), the sentence on how a half Taiga does not build is done today, and whether it inherits the organization's design standards (null follows the default for how the product started). Later plans read the new answer; work already planned is not redone. Autonomy is taiga_set_product_autonomy.",
|
|
20133
20241
|
inputSchema: z.object({
|
|
20134
20242
|
productId,
|
|
20135
20243
|
name: z.string().trim().min(1).max(255).optional().describe("A new name"),
|
|
@@ -20152,7 +20260,7 @@ function registerSetupTools(server, client) {
|
|
|
20152
20260
|
}));
|
|
20153
20261
|
server.registerTool("taiga_refresh_reality_documents", {
|
|
20154
20262
|
title: "Refresh a product's documents from its code",
|
|
20155
|
-
description: "Derive the reality documents again from the code on the branch Taiga works on (the link's effectiveBranch: the chosen branch, or the repository's default): all of them, or the ones named (with what they are derived from). For a document the import misread, refresh it rather than importing again; unchanged sections are not republished. Runs an agent in the background and returns the job; taiga_wait_for_job says when it is done.
|
|
20263
|
+
description: "Derive the reality documents again from the code on the branch Taiga works on (the link's effectiveBranch: the chosen branch, or the repository's default): all of them, or the ones named (with what they are derived from). For a document the import misread, refresh it rather than importing again; unchanged sections are not republished. Runs an agent in the background and returns the job; taiga_wait_for_job says when it is done.",
|
|
20156
20264
|
inputSchema: z.object({
|
|
20157
20265
|
productId,
|
|
20158
20266
|
docTypes: z.array(z.enum(REALITY_DERIVED_DOCUMENT_TYPES)).min(1).max(REALITY_DERIVED_DOCUMENT_TYPES.length).optional().describe("The documents to refresh; omit for all")
|
|
@@ -20174,14 +20282,14 @@ function registerSetupTools(server, client) {
|
|
|
20174
20282
|
}));
|
|
20175
20283
|
server.registerTool("taiga_reopen_discovery", {
|
|
20176
20284
|
title: "Reopen Discovery",
|
|
20177
|
-
description: "Reopen a finished product's Discovery so its documents can be amended; finish it again afterwards. Nothing built from them is undone.
|
|
20285
|
+
description: "Reopen a finished product's Discovery so its documents can be amended; finish it again afterwards. Nothing built from them is undone.",
|
|
20178
20286
|
inputSchema: z.object({ productId }),
|
|
20179
20287
|
annotations: OPERATE
|
|
20180
20288
|
}, (params) => runTool(async () => toolResult(await productRow(client, (await client.reopenSetup(params.productId)).data))));
|
|
20181
20289
|
if (!client.hasCapability("products:read")) return;
|
|
20182
20290
|
server.registerTool("taiga_start_import", {
|
|
20183
20291
|
title: "Import a codebase into a product",
|
|
20184
|
-
description: "Start the import of a product created to import a codebase: Taiga reads its linked repository and derives the full document set from the code, each document citing the files it came from. Importing is one decision; the rest runs by itself. The scanners then sweep the repository, the documents are assessed against the organization's policies, and the first initiatives are planned when Discovery is finished. Refused for a product started from scratch (the choice is made at creation), without a linked repository, or while an import runs. Runs agents in the background and returns the job; taiga_wait_for_job says when it is done (it takes a while).
|
|
20292
|
+
description: "Start the import of a product created to import a codebase: Taiga reads its linked repository and derives the full document set from the code, each document citing the files it came from. Importing is one decision; the rest runs by itself. The scanners then sweep the repository, the documents are assessed against the organization's policies, and the first initiatives are planned when Discovery is finished. Refused for a product started from scratch (the choice is made at creation), without a linked repository, or while an import runs. Runs agents in the background and returns the job; taiga_wait_for_job says when it is done (it takes a while).",
|
|
20185
20293
|
inputSchema: z.object({ productId }),
|
|
20186
20294
|
annotations: OPERATE
|
|
20187
20295
|
}, (params) => runTool(async () => {
|
|
@@ -20545,14 +20653,29 @@ function withStrictInputs(server) {
|
|
|
20545
20653
|
if (schema !== void 0 && !(schema instanceof z.ZodObject)) throw new Error(`Tool ${name}: inputSchema must be a z.object() so unknown arguments can be refused`);
|
|
20546
20654
|
const handler = callback;
|
|
20547
20655
|
const withHint = (args, ctx) => notFoundHints.run(notFoundHint(name, args), () => handler(args, ctx));
|
|
20548
|
-
|
|
20656
|
+
const described = {
|
|
20549
20657
|
...config,
|
|
20658
|
+
description: `${writeNotice(name, config.annotations)}${config.description ?? ""}`
|
|
20659
|
+
};
|
|
20660
|
+
return register(name, schema ? {
|
|
20661
|
+
...described,
|
|
20550
20662
|
inputSchema: schema.strict()
|
|
20551
|
-
} :
|
|
20663
|
+
} : described, withHint);
|
|
20552
20664
|
});
|
|
20553
20665
|
return server;
|
|
20554
20666
|
}
|
|
20555
20667
|
/**
|
|
20668
|
+
* What a tool's annotations say, in its description's first words (PRO-2778).
|
|
20669
|
+
* Annotations are for the client: Claude Code does not show them to the
|
|
20670
|
+
* model, so an agent read 79 tools without knowing which ones write. A tool
|
|
20671
|
+
* with none is refused here, so no tool reaches the catalogue unclassified.
|
|
20672
|
+
*/
|
|
20673
|
+
function writeNotice(name, annotations) {
|
|
20674
|
+
if (!annotations || annotations.readOnlyHint === void 0) throw new Error(`Tool ${name}: annotations must say whether it is read-only`);
|
|
20675
|
+
if (annotations.readOnlyHint) return "";
|
|
20676
|
+
return annotations.destructiveHint ? "Acts on paid or shared work: ask the person first. " : "Writes. ";
|
|
20677
|
+
}
|
|
20678
|
+
/**
|
|
20556
20679
|
* Register all MCP tools, resources and prompts based on the API key's effective capabilities.
|
|
20557
20680
|
* Only tools matching the key's scopes are registered -- the AI assistant
|
|
20558
20681
|
* never sees tools it can't use.
|
|
@@ -20586,15 +20709,16 @@ const SERVER_INSTRUCTIONS = [
|
|
|
20586
20709
|
"**Start here.** Call taiga_get_product_context first, naming the product by its git remote URL inside a",
|
|
20587
20710
|
"checkout (git remote get-url origin), else by slug or ID (taiga_list_products finds one by name with",
|
|
20588
20711
|
"query). It returns the product (and, while it is in setup, nextStep: what it waits for), its factory",
|
|
20589
|
-
"instructions, its specification and architecture, every policy as its section ids, the
|
|
20590
|
-
"the repositories and the latest runs. Then read only what you need:
|
|
20591
|
-
"taiga_get_document
|
|
20592
|
-
"(taiga_list_action_required) for what
|
|
20712
|
+
"instructions, the outline of its specification and architecture, every policy as its section ids, the",
|
|
20713
|
+
"open initiatives, the repositories and the latest runs. Then read only what you need: sections by id with",
|
|
20714
|
+
"taiga_get_document or taiga_get_product_document (sections: [] is any document's outline), an",
|
|
20715
|
+
"initiative with taiga_get_backlog_item. Check Action Required (taiga_list_action_required) for what",
|
|
20716
|
+
"waits on a person.",
|
|
20593
20717
|
"",
|
|
20594
20718
|
"**Before you write.** Tell the person what you are about to do and wait for their yes before a tool that",
|
|
20595
20719
|
"starts paid agent work (queueing, approving, resuming, requesting, importing, generating), interrupts it",
|
|
20596
|
-
"(stopping, pausing) or changes what others read (publishing, resolving, settings, repositories).
|
|
20597
|
-
"that prompts on destructive tools does part of
|
|
20720
|
+
"(stopping, pausing) or changes what others read (publishing, resolving, settings, repositories). Such a",
|
|
20721
|
+
"tool says so first in its description; a client that prompts on destructive tools does part of it.",
|
|
20598
20722
|
"",
|
|
20599
20723
|
"**Answers.** A refusal carries the API reason: say it as it is. NOT_FOUND names the tool that lists the",
|
|
20600
20724
|
"right IDs; an ID is a UUID, never a name. An empty result says why when it has a reason.",
|