@taiga-ai/mcp-server 0.18.0-dev.447 → 0.18.0-dev.448
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 +93 -37
- package/package.json +1 -1
- package/src/client.test.ts +2 -2
- package/src/client.ts +190 -145
- package/src/server.integration.test.ts +10 -9
- package/src/tools/autonomy.ts +2 -4
- package/src/tools/backlogs.ts +27 -30
- package/src/tools/builds.ts +14 -12
- package/src/tools/catalogue.test.ts +5 -5
- package/src/tools/context.ts +32 -30
- package/src/tools/documents.ts +8 -4
- package/src/tools/hints.ts +5 -2
- package/src/tools/inbox.ts +3 -5
- package/src/tools/jobs.ts +6 -5
- package/src/tools/knowledge.ts +4 -6
- package/src/tools/maintenance.ts +9 -14
- package/src/tools/markdown.ts +13 -5
- package/src/tools/observe.ts +17 -14
- package/src/tools/product-documents.ts +3 -5
- package/src/tools/products.ts +25 -22
- package/src/tools/prompts.ts +7 -13
- package/src/tools/queue.ts +36 -15
- package/src/tools/repositories.ts +7 -8
- package/src/tools/resources.ts +11 -14
- package/src/tools/sections.test.ts +3 -3
- package/src/tools/sections.ts +44 -15
- package/src/tools/setup.ts +21 -25
- package/src/tools/summarize.test.ts +20 -0
- package/src/tools/summarize.ts +17 -13
- package/src/tools/wait.ts +28 -21
- package/taiga-ai-mcp-server-0.18.0-dev.448.tgz +0 -0
- package/tsconfig.json +1 -5
- package/taiga-ai-mcp-server-0.18.0-dev.447.tgz +0 -0
package/dist/index.js
CHANGED
|
@@ -2169,8 +2169,9 @@ const DISCOVERY_STEP_MEANING = "discoveryStep is where a product in setup is: co
|
|
|
2169
2169
|
* hears only that guesses another ID, or a name. Each ID a tool takes names
|
|
2170
2170
|
* the tool that lists the right ones; the tool's arguments say which apply.
|
|
2171
2171
|
*/
|
|
2172
|
+
const ITEM_ID_HINT = "taiga_list_backlog_items lists a product's initiatives with their IDs; a run or plan ID is not one.";
|
|
2172
2173
|
const BY_ARGUMENT = {
|
|
2173
|
-
itemId:
|
|
2174
|
+
itemId: ITEM_ID_HINT,
|
|
2174
2175
|
backlogItemId: "taiga_list_backlog_items lists a product's initiatives with their IDs.",
|
|
2175
2176
|
buildId: "taiga_list_builds lists the runs, of one product or of every product the key can see.",
|
|
2176
2177
|
planId: "taiga_get_plan gives the initiative's active plan and its ID.",
|
|
@@ -2189,7 +2190,7 @@ const BY_ARGUMENT = {
|
|
|
2189
2190
|
/** Where an argument means something else on one tool, or the tool's 404 needs its own word. */
|
|
2190
2191
|
const BY_TOOL = {
|
|
2191
2192
|
taiga_resolve_action: () => "taiga_list_action_required lists the items with their IDs.",
|
|
2192
|
-
taiga_get_plan: (said) => /\bplan\b/i.test(said) ? "taiga_get_backlog_item says whether one is being generated (planningStatus planning) or failed, and taiga_plan_again asks for a new one." :
|
|
2193
|
+
taiga_get_plan: (said) => /\bplan\b/i.test(said) ? "taiga_get_backlog_item says whether one is being generated (planningStatus planning) or failed, and taiga_plan_again asks for a new one." : ITEM_ID_HINT
|
|
2193
2194
|
};
|
|
2194
2195
|
/** The hint for a 404 from one tool call: the tool's own, else one per ID it was given. */
|
|
2195
2196
|
function notFoundHint(tool, args) {
|
|
@@ -2366,6 +2367,10 @@ function webOrigin(url) {
|
|
|
2366
2367
|
function isRecord(value) {
|
|
2367
2368
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2368
2369
|
}
|
|
2370
|
+
/** A section stored as an object rather than a bare string. */
|
|
2371
|
+
function isSectionObject(value) {
|
|
2372
|
+
return isRecord(value);
|
|
2373
|
+
}
|
|
2369
2374
|
/** The `sections` map of a document, or null when the document is not section-shaped. */
|
|
2370
2375
|
function sectionsOf(data) {
|
|
2371
2376
|
return isRecord(data.sections) ? data.sections : null;
|
|
@@ -2375,7 +2380,7 @@ function sectionContent(sections, sectionId) {
|
|
|
2375
2380
|
const section = sections[sectionId];
|
|
2376
2381
|
if (section === void 0 || section === null) return null;
|
|
2377
2382
|
if (typeof section === "string") return section;
|
|
2378
|
-
return
|
|
2383
|
+
return isSectionObject(section) && typeof section.content === "string" ? section.content : "";
|
|
2379
2384
|
}
|
|
2380
2385
|
/** The map with one section's text replaced (or the section added); every other section untouched. */
|
|
2381
2386
|
function applySectionEdit(sections, sectionId, content) {
|
|
@@ -2397,8 +2402,9 @@ function applySectionEdit(sections, sectionId, content) {
|
|
|
2397
2402
|
*/
|
|
2398
2403
|
function stripProvenance(doc) {
|
|
2399
2404
|
const { derivedFrom: _derivedFrom, outdated: _outdated, ...rest } = doc;
|
|
2400
|
-
|
|
2401
|
-
|
|
2405
|
+
const { data } = doc;
|
|
2406
|
+
if (!isRecord(data)) return rest;
|
|
2407
|
+
const sections = sectionsOf(data);
|
|
2402
2408
|
if (!sections) return rest;
|
|
2403
2409
|
const stripped = Object.fromEntries(Object.entries(sections).map(([id, section]) => {
|
|
2404
2410
|
if (!isRecord(section)) return [id, section];
|
|
@@ -2408,7 +2414,7 @@ function stripProvenance(doc) {
|
|
|
2408
2414
|
return {
|
|
2409
2415
|
...rest,
|
|
2410
2416
|
data: {
|
|
2411
|
-
...
|
|
2417
|
+
...data,
|
|
2412
2418
|
sections: stripped
|
|
2413
2419
|
}
|
|
2414
2420
|
};
|
|
@@ -2421,7 +2427,7 @@ function stripProvenance(doc) {
|
|
|
2421
2427
|
function instructionsText(sections) {
|
|
2422
2428
|
const parts = [];
|
|
2423
2429
|
for (const section of Object.values(sections)) {
|
|
2424
|
-
const content =
|
|
2430
|
+
const content = isSectionObject(section) ? section.content : void 0;
|
|
2425
2431
|
if (typeof content === "string" && content.trim()) parts.push(content.trim());
|
|
2426
2432
|
}
|
|
2427
2433
|
return parts.join("\n\n").trim() || null;
|
|
@@ -2650,9 +2656,8 @@ async function linkedProducts(client, repository, repositoryHost) {
|
|
|
2650
2656
|
}
|
|
2651
2657
|
/** A document list row as the model sees it: the summary fields plus a display name and the staleness fact. */
|
|
2652
2658
|
function documentSummaries(rows) {
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
const outdated = typed[index].outdated;
|
|
2659
|
+
return pickFields(rows, DOCUMENT_SUMMARY_FIELDS$1).map((picked, index) => {
|
|
2660
|
+
const outdated = rows[index].outdated;
|
|
2656
2661
|
const fact = picked.isDraft !== false ? void 0 : outdated === null ? false : Array.isArray(outdated?.sources) ? outdated.sources.length > 0 : void 0;
|
|
2657
2662
|
return {
|
|
2658
2663
|
type: picked.type,
|
|
@@ -3279,7 +3284,7 @@ async function queueView(client, product, options = { name: "all" }) {
|
|
|
3279
3284
|
const canName = client.hasCapability("backlog:read");
|
|
3280
3285
|
const blocking = blockingIndex(entries, (entry) => entry.state);
|
|
3281
3286
|
const toName = !canName ? [] : options.name === "all" ? entries.slice(0, NAMED_ENTRIES).map((_, index) => index) : blocking >= 0 ? [blocking] : [];
|
|
3282
|
-
const settled = await Promise.allSettled(toName.map((index) => client.getBacklogItem(
|
|
3287
|
+
const settled = await Promise.allSettled(toName.map((index) => client.getBacklogItem(entries[index].backlogItemId)));
|
|
3283
3288
|
const reads = new Map(toName.map((index, at) => [index, settled[at]]));
|
|
3284
3289
|
let unnamed = options.name === "all" ? entries.length - toName.length : 0;
|
|
3285
3290
|
const rows = entries.map((entry, index) => {
|
|
@@ -3475,13 +3480,10 @@ const PLAN_STEP_SUMMARY_FIELDS = [
|
|
|
3475
3480
|
"createsResources"
|
|
3476
3481
|
];
|
|
3477
3482
|
/** The outcome fields of an initiative, with how many of its criteria are met. */
|
|
3478
|
-
const outcomeOf = (item) => {
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
criteria: criteriaCounts(row)
|
|
3483
|
-
};
|
|
3484
|
-
};
|
|
3483
|
+
const outcomeOf = (item) => ({
|
|
3484
|
+
...pickFields([item], ITEM_OUTCOME_FIELDS)[0],
|
|
3485
|
+
criteria: criteriaCounts(item)
|
|
3486
|
+
});
|
|
3485
3487
|
const outcome = (item) => toolResult(outcomeOf(item));
|
|
3486
3488
|
/**
|
|
3487
3489
|
* Each tool is registered under the capability its route requires, so a
|
|
@@ -3566,7 +3568,8 @@ function registerBacklogReads(server, client) {
|
|
|
3566
3568
|
}, (params) => runTool(async () => {
|
|
3567
3569
|
const item = (await client.getBacklogItem(params.itemId)).data;
|
|
3568
3570
|
if (item.planningStatus !== "failed") return toolResult(item);
|
|
3569
|
-
const
|
|
3571
|
+
const ownCause = item.planFailureCause;
|
|
3572
|
+
const planFailure = await planFailureOf(client, params.itemId, ownCause);
|
|
3570
3573
|
return toolResult(planFailure ? {
|
|
3571
3574
|
...item,
|
|
3572
3575
|
planFailure
|
|
@@ -3659,7 +3662,7 @@ const isApiFailure$2 = (error) => error instanceof TaigaApiError || isNetworkFai
|
|
|
3659
3662
|
async function reviewGate(client, item) {
|
|
3660
3663
|
const own = typeof item.autoBuild === "boolean" ? item.autoBuild : null;
|
|
3661
3664
|
try {
|
|
3662
|
-
const product = own === null ? (await client.getProduct(
|
|
3665
|
+
const product = own === null ? (await client.getProduct(item.productId)).data : {};
|
|
3663
3666
|
const builds = resolveAutoBuild({
|
|
3664
3667
|
initiative: own,
|
|
3665
3668
|
product: typeof product.autoBuild === "boolean" ? product.autoBuild : true
|
|
@@ -3677,7 +3680,7 @@ async function reviewGate(client, item) {
|
|
|
3677
3680
|
/** Where a queued initiative stands in its product's line; null, and the outcome says so, when the queue cannot be read. */
|
|
3678
3681
|
async function placeInQueue(client, item) {
|
|
3679
3682
|
try {
|
|
3680
|
-
const view = await queueView(client,
|
|
3683
|
+
const view = await queueView(client, item.productId, { name: "blocking" });
|
|
3681
3684
|
return {
|
|
3682
3685
|
position: view.entries.find((row) => row.backlogItemId === item.id)?.position ?? null,
|
|
3683
3686
|
of: view.entries.length,
|
|
@@ -3853,7 +3856,7 @@ async function activityRows(client, opts) {
|
|
|
3853
3856
|
throw error;
|
|
3854
3857
|
})]);
|
|
3855
3858
|
const nameById = /* @__PURE__ */ new Map();
|
|
3856
|
-
for (const product of products?.data ?? []) nameById.set(
|
|
3859
|
+
for (const product of products?.data ?? []) nameById.set(product.id, {
|
|
3857
3860
|
name: product.name,
|
|
3858
3861
|
slug: product.slug
|
|
3859
3862
|
});
|
|
@@ -4081,7 +4084,7 @@ async function factoryPart(client, factoryId) {
|
|
|
4081
4084
|
* asked. Null, with a note, where unpublished.
|
|
4082
4085
|
*/
|
|
4083
4086
|
async function documentsPart(client, product, depth) {
|
|
4084
|
-
const productId =
|
|
4087
|
+
const productId = product.id;
|
|
4085
4088
|
const origin = defaultOriginFor(product);
|
|
4086
4089
|
const reads = await Promise.allSettled(CONTEXT_DOCUMENT_TYPES.map((type) => client.getProductDocument(productId, type, origin)));
|
|
4087
4090
|
const out = { origin };
|
|
@@ -4206,7 +4209,7 @@ async function buildsPart(client, productId) {
|
|
|
4206
4209
|
return pickFields(rows, CONTEXT_BUILD_FIELDS);
|
|
4207
4210
|
}
|
|
4208
4211
|
async function productContext(client, product, options) {
|
|
4209
|
-
const productId =
|
|
4212
|
+
const productId = product.id;
|
|
4210
4213
|
const granted = (part) => client.hasCapability(PARTS[part]);
|
|
4211
4214
|
const notGranted = Object.keys(PARTS).filter((part) => !granted(part));
|
|
4212
4215
|
const fetchers = {
|
|
@@ -4276,6 +4279,14 @@ function registerContextTools(server, client) {
|
|
|
4276
4279
|
}
|
|
4277
4280
|
//#endregion
|
|
4278
4281
|
//#region src/tools/autonomy.ts
|
|
4282
|
+
/**
|
|
4283
|
+
* Autonomy (docs.tai.ga, Deliver: Initiatives, Auto-merge): whether a landed
|
|
4284
|
+
* plan is built without a review, and whether a pull request merges on its
|
|
4285
|
+
* own. The organization and its factories are ceilings for merging, set by
|
|
4286
|
+
* an administrator in the web; a product sets defaults; an initiative's own
|
|
4287
|
+
* switch is the final word within the ceilings. The initiative's build gate
|
|
4288
|
+
* is taiga_set_auto_build.
|
|
4289
|
+
*/
|
|
4279
4290
|
function registerAutonomyTools(server, client) {
|
|
4280
4291
|
if (client.hasCapability("products:write")) server.registerTool("taiga_set_product_autonomy", {
|
|
4281
4292
|
title: "Set a product's autonomy defaults",
|
|
@@ -4322,6 +4333,13 @@ function registerAutonomyTools(server, client) {
|
|
|
4322
4333
|
}
|
|
4323
4334
|
//#endregion
|
|
4324
4335
|
//#region src/tools/inbox.ts
|
|
4336
|
+
/**
|
|
4337
|
+
* Action Required (docs.tai.ga, Deliver): where work comes back to a person.
|
|
4338
|
+
* Things to review (a plan, a pull request, findings, policy violations) and
|
|
4339
|
+
* things to set up that only a person can (a secret, an account, a cloud
|
|
4340
|
+
* permission). The inbox is the team's: resolving an item clears it for
|
|
4341
|
+
* everybody, so these tools say what they clear.
|
|
4342
|
+
*/
|
|
4325
4343
|
const ITEM_FIELDS = [
|
|
4326
4344
|
"id",
|
|
4327
4345
|
"productId",
|
|
@@ -4433,6 +4451,14 @@ function registerInboxTools(server, client) {
|
|
|
4433
4451
|
}
|
|
4434
4452
|
//#endregion
|
|
4435
4453
|
//#region src/tools/knowledge.ts
|
|
4454
|
+
/**
|
|
4455
|
+
* Knowledge (docs.tai.ga, Context): reference material agents search when
|
|
4456
|
+
* the work calls for it, at the organization, a factory or a product, and
|
|
4457
|
+
* narrowing as it comes down. Not instructions: a rule belongs in
|
|
4458
|
+
* instructions, a forty-page integration guide here. Members manage it on
|
|
4459
|
+
* their factories and products; organization knowledge takes an owner or
|
|
4460
|
+
* admin, which the API enforces.
|
|
4461
|
+
*/
|
|
4436
4462
|
const KNOWLEDGE_FIELDS = [
|
|
4437
4463
|
"id",
|
|
4438
4464
|
"factoryId",
|
|
@@ -4608,6 +4634,13 @@ function registerKnowledgeTools(server, client) {
|
|
|
4608
4634
|
}
|
|
4609
4635
|
//#endregion
|
|
4610
4636
|
//#region src/tools/maintenance.ts
|
|
4637
|
+
/**
|
|
4638
|
+
* Maintaining (docs.tai.ga, Operate): what the sweeps found in a product's
|
|
4639
|
+
* repository and where it stands against the organization's own policies,
|
|
4640
|
+
* and the two things a person does with a finding, Ignore and Fix. Sweeps run
|
|
4641
|
+
* themselves and nothing is marked fixed by hand: a finding resolves when the
|
|
4642
|
+
* next sweep no longer detects it, so there is no tool for either.
|
|
4643
|
+
*/
|
|
4611
4644
|
/** A finding as a list row: enough to decide whether to open, fix or ignore it. */
|
|
4612
4645
|
const FINDING_ROW_FIELDS = [
|
|
4613
4646
|
"id",
|
|
@@ -4814,6 +4847,13 @@ function registerMaintenanceTools(server, client) {
|
|
|
4814
4847
|
}
|
|
4815
4848
|
//#endregion
|
|
4816
4849
|
//#region src/tools/observe.ts
|
|
4850
|
+
/**
|
|
4851
|
+
* What runs where and how it is doing (docs.tai.ga, Deliver: Deployments;
|
|
4852
|
+
* Operate: Monitoring). Taiga observes deployments, it does not perform them:
|
|
4853
|
+
* the history is mirrored from the provider, so these tools read and never
|
|
4854
|
+
* deploy, approve or cancel. Importing history reaches into the connected
|
|
4855
|
+
* provider and is an administrator's action in the web.
|
|
4856
|
+
*/
|
|
4817
4857
|
/** A deployment as a line of history: what shipped where, how it ended, and who shipped it. */
|
|
4818
4858
|
const DEPLOYMENT_FIELDS = [
|
|
4819
4859
|
"id",
|
|
@@ -4849,7 +4889,7 @@ const ENVIRONMENT_FIELDS = [
|
|
|
4849
4889
|
*/
|
|
4850
4890
|
function deploymentLines(rows) {
|
|
4851
4891
|
return rows.map((row) => {
|
|
4852
|
-
const repository = row.metadata?.repositoryFullName;
|
|
4892
|
+
const repository = row.metadata?.["repositoryFullName"];
|
|
4853
4893
|
return {
|
|
4854
4894
|
...pickFields([row], DEPLOYMENT_FIELDS)[0],
|
|
4855
4895
|
repository: typeof repository === "string" ? repository : null
|
|
@@ -5045,7 +5085,7 @@ function registerProductDocumentWrites(server, client) {
|
|
|
5045
5085
|
const canRead = client.hasCapability("products:read");
|
|
5046
5086
|
const productScope = (product, type) => ({
|
|
5047
5087
|
getDraft: () => client.getProductDocumentDraft(product, type),
|
|
5048
|
-
getPublished:
|
|
5088
|
+
getPublished: () => client.getProductDocument(product, type, "plan"),
|
|
5049
5089
|
saveDraft: (data) => client.saveProductDocumentDraft(product, type, { data }),
|
|
5050
5090
|
publish: () => client.publishProductDocument(product, type)
|
|
5051
5091
|
});
|
|
@@ -19554,7 +19594,7 @@ function renderDocument(doc) {
|
|
|
19554
19594
|
].filter((key) => doc[key] !== void 0 && doc[key] !== null).map((key) => `${key}: ${String(doc[key])}`);
|
|
19555
19595
|
const core = Object.keys(sections).filter((id) => {
|
|
19556
19596
|
const section = sections[id];
|
|
19557
|
-
return
|
|
19597
|
+
return isSectionObject(section) && section.priority === "core";
|
|
19558
19598
|
});
|
|
19559
19599
|
if (core.length > 0) facts.push(`core sections: ${core.join(", ")}`);
|
|
19560
19600
|
if (facts.length > 0) lines.push("", facts.join(" | "));
|
|
@@ -19650,7 +19690,7 @@ function atxOf(depth, children) {
|
|
|
19650
19690
|
}
|
|
19651
19691
|
/** The section's title when it carries one, else its id. */
|
|
19652
19692
|
function sectionTitle(section, id) {
|
|
19653
|
-
return
|
|
19693
|
+
return isSectionObject(section) && typeof section.title === "string" && section.title.trim() ? section.title.trim() : id;
|
|
19654
19694
|
}
|
|
19655
19695
|
/** The mime type a read will answer with, for a list entry that must not disagree with it. */
|
|
19656
19696
|
function mimeTypeOf(doc) {
|
|
@@ -19817,6 +19857,14 @@ var NotGranted = class extends Error {
|
|
|
19817
19857
|
};
|
|
19818
19858
|
//#endregion
|
|
19819
19859
|
//#region src/tools/repositories.ts
|
|
19860
|
+
/**
|
|
19861
|
+
* A product's repository (docs.tai.ga, Administration: Integrations and
|
|
19862
|
+
* environments, Source control). A linked repository is the prerequisite for
|
|
19863
|
+
* most of what Taiga does: planning reads the code, runs work on a branch of
|
|
19864
|
+
* it, sweeps scan it and the as-built documents are derived from it. GitHub is
|
|
19865
|
+
* connected once for the organization, in the web; linking one of the
|
|
19866
|
+
* repositories it can reach to a product is a separate act, per product.
|
|
19867
|
+
*/
|
|
19820
19868
|
const LINK_FIELDS = [
|
|
19821
19869
|
"id",
|
|
19822
19870
|
"provider",
|
|
@@ -20038,7 +20086,7 @@ function registerResources(server, client) {
|
|
|
20038
20086
|
title: "Organization document",
|
|
20039
20087
|
description: "A published organization document: a policy, the business goals, the design or brand standards, the tone of voice or the standing instructions. Sections rendered as headings."
|
|
20040
20088
|
}, async (uri, variables) => {
|
|
20041
|
-
const type = requireOneOf("Document type", String(variables
|
|
20089
|
+
const type = requireOneOf("Document type", String(variables["type"]), TENANT_DOCUMENT_TYPES);
|
|
20042
20090
|
try {
|
|
20043
20091
|
return contents(uri.href, (await client.getTenantDocument(type)).data);
|
|
20044
20092
|
} catch (error) {
|
|
@@ -20055,7 +20103,7 @@ function registerResources(server, client) {
|
|
|
20055
20103
|
title: "Factory instructions",
|
|
20056
20104
|
description: "A factory's published standing guidance; taiga_list_factories gives the factory ID. Markdown for a section map, JSON otherwise (the read says which)."
|
|
20057
20105
|
}, async (uri, variables) => {
|
|
20058
|
-
const factoryId = requireId("Factory ID", String(variables
|
|
20106
|
+
const factoryId = requireId("Factory ID", String(variables["factoryId"]));
|
|
20059
20107
|
const doc = await served(client.getFactoryDocument(factoryId, FACTORY_DOCUMENT_TYPES[0]), "This factory has not published instructions, or the key cannot see the factory");
|
|
20060
20108
|
return contents(uri.href, doc.data);
|
|
20061
20109
|
});
|
|
@@ -20075,17 +20123,17 @@ function registerResources(server, client) {
|
|
|
20075
20123
|
title: "Product document on a timeline",
|
|
20076
20124
|
description: "A product document from the named timeline: origin=plan (discovery) or origin=reality (derived from the code). Markdown for a section map, JSON otherwise (the read says which)."
|
|
20077
20125
|
}, async (uri, variables) => {
|
|
20078
|
-
const productId = requireId("Product ID", String(variables
|
|
20079
|
-
const type = documentType(String(variables
|
|
20080
|
-
const origin = requireOneOf("Origin", String(variables
|
|
20126
|
+
const productId = requireId("Product ID", String(variables["productId"]));
|
|
20127
|
+
const type = documentType(String(variables["type"]));
|
|
20128
|
+
const origin = requireOneOf("Origin", String(variables["origin"]), PRODUCT_DOCUMENT_ORIGINS);
|
|
20081
20129
|
return read(uri, productId, type, origin);
|
|
20082
20130
|
});
|
|
20083
20131
|
server.registerResource("product-document", new ResourceTemplate("taiga://products/{productId}/documents/{type}", { list: void 0 }), {
|
|
20084
20132
|
title: "Product document",
|
|
20085
20133
|
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_list_product_documents 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)."
|
|
20086
20134
|
}, async (uri, variables) => {
|
|
20087
|
-
const productId = requireId("Product ID", String(variables
|
|
20088
|
-
const type = documentType(String(variables
|
|
20135
|
+
const productId = requireId("Product ID", String(variables["productId"]));
|
|
20136
|
+
const type = documentType(String(variables["type"]));
|
|
20089
20137
|
const product = (await served(client.getProduct(productId), "No product with that ID the key can see; taiga_list_products shows them")).data;
|
|
20090
20138
|
return read(uri, productId, type, defaultOriginFor(product, type));
|
|
20091
20139
|
});
|
|
@@ -20095,7 +20143,7 @@ function registerResources(server, client) {
|
|
|
20095
20143
|
description: "An initiative in full: description, acceptance criteria, technical context. taiga_list_backlog_items gives the item ID.",
|
|
20096
20144
|
mimeType: "application/json"
|
|
20097
20145
|
}, async (uri, variables) => {
|
|
20098
|
-
const itemId = requireId("Backlog item ID", String(variables
|
|
20146
|
+
const itemId = requireId("Backlog item ID", String(variables["itemId"]));
|
|
20099
20147
|
const item = (await served(client.getBacklogItem(itemId), "No backlog item with that ID the key can see; taiga_list_backlog_items shows what exists")).data;
|
|
20100
20148
|
return { contents: [{
|
|
20101
20149
|
uri: uri.href,
|
|
@@ -20106,6 +20154,14 @@ function registerResources(server, client) {
|
|
|
20106
20154
|
}
|
|
20107
20155
|
//#endregion
|
|
20108
20156
|
//#region src/tools/setup.ts
|
|
20157
|
+
/**
|
|
20158
|
+
* Setting a product up (docs.tai.ga, Start: Your first product, Learn: Import
|
|
20159
|
+
* an existing codebase, Organizations, factories and products): factories to
|
|
20160
|
+
* put products in, a product started from scratch or from an imported
|
|
20161
|
+
* codebase, what Taiga builds for it, the import, and finishing or reopening
|
|
20162
|
+
* Discovery. Who may reach a factory (its members) and deleting a factory or
|
|
20163
|
+
* a product stay in the web.
|
|
20164
|
+
*/
|
|
20109
20165
|
const PRODUCT_FIELDS = [
|
|
20110
20166
|
"id",
|
|
20111
20167
|
"name",
|
|
@@ -20290,7 +20346,7 @@ function registerSetupTools(server, client) {
|
|
|
20290
20346
|
try {
|
|
20291
20347
|
return toolResult(await productRow(client, (await client.completeSetup(params.productId)).data));
|
|
20292
20348
|
} catch (error) {
|
|
20293
|
-
if (!(error instanceof TaigaApiError) || error.details?.reason !== "documents_incomplete") throw error;
|
|
20349
|
+
if (!(error instanceof TaigaApiError) || error.details?.["reason"] !== "documents_incomplete") throw error;
|
|
20294
20350
|
return toolError(error.code ?? "CONFLICT", `${withStop(error.apiMessage ?? "Required documents are not published yet")} ${MISSING_DOCUMENTS_HOW}`, error.details);
|
|
20295
20351
|
}
|
|
20296
20352
|
}));
|
package/package.json
CHANGED
package/src/client.test.ts
CHANGED
|
@@ -154,9 +154,9 @@ describe('TaigaClient', () => {
|
|
|
154
154
|
it('drops tenantId at every depth and keeps an own __proto__ key as data', () => {
|
|
155
155
|
const parsed = JSON.parse(
|
|
156
156
|
'{"__proto__": {"x": 1}, "tenantId": "t", "a": {"tenantId": "t", "b": [{"tenantId": "t", "c": 1}, 2, null]}, "d": "s"}'
|
|
157
|
-
) as
|
|
157
|
+
) as { tenantId: string; a: unknown; d: string };
|
|
158
158
|
|
|
159
|
-
const out = withoutTenant(parsed)
|
|
159
|
+
const out = withoutTenant(parsed);
|
|
160
160
|
|
|
161
161
|
expect(JSON.stringify(out)).not.toContain('tenantId');
|
|
162
162
|
expect(out.a).toEqual({ b: [{ c: 1 }, 2, null] });
|