@taiga-ai/mcp-server 0.26.0-rc.496 → 0.26.0-rc.504
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 +334 -61
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -562,6 +562,31 @@ function lenientList(item) {
|
|
|
562
562
|
return list;
|
|
563
563
|
}
|
|
564
564
|
//#endregion
|
|
565
|
+
//#region ../../packages/types/dist/wire-fields.js
|
|
566
|
+
/**
|
|
567
|
+
* Zod fields shared by more than one wire schema, so a change to the rule
|
|
568
|
+
* lands once rather than at every schema that carries the field.
|
|
569
|
+
*/
|
|
570
|
+
/**
|
|
571
|
+
* Reusable optional-UUID field for nested schemas (e.g. discriminated-union
|
|
572
|
+
* members that all accept an optional resource id). Centralising the rule
|
|
573
|
+
* means a future change (UUID v7-only, error-message tweak) lands in one
|
|
574
|
+
* place instead of N call sites.
|
|
575
|
+
*/
|
|
576
|
+
const optionalUuid = z.string().uuid().optional();
|
|
577
|
+
/**
|
|
578
|
+
* A source line anchor on a finding, or nothing.
|
|
579
|
+
*
|
|
580
|
+
* 0 means "no line anchor" -- a dependency in go.mod, a checkov rule on a
|
|
581
|
+
* whole file -- and the scanners emit 0 for it. Rejecting it with `.min(1)`
|
|
582
|
+
* bounced the WHOLE hardening report of staging run 01a07123 (52 findings,
|
|
583
|
+
* `/findings/46/lineNumber too_small`), so the UI never saw the phase
|
|
584
|
+
* (PRO-1481). Normalized to absent, never stored as 0. One schema for every
|
|
585
|
+
* wire shape that carries a line (findings, narrative, residual findings) so
|
|
586
|
+
* the next tightening cannot land on one of them and not the others.
|
|
587
|
+
*/
|
|
588
|
+
const optionalLineAnchor = z.number().int().min(0).optional().transform((v) => v ? v : void 0);
|
|
589
|
+
//#endregion
|
|
565
590
|
//#region ../../packages/types/dist/tenants.js
|
|
566
591
|
const TENANT_DOCUMENT_TYPES = [
|
|
567
592
|
"policy_security",
|
|
@@ -1432,6 +1457,149 @@ function hardeningCoverage(result) {
|
|
|
1432
1457
|
gaps
|
|
1433
1458
|
};
|
|
1434
1459
|
}
|
|
1460
|
+
z.discriminatedUnion("kind", [
|
|
1461
|
+
z.object({
|
|
1462
|
+
type: z.literal("narrative"),
|
|
1463
|
+
kind: z.literal("decision"),
|
|
1464
|
+
what: z.string().min(1).max(2e3),
|
|
1465
|
+
why: z.string().min(1).max(2e3),
|
|
1466
|
+
stepId: optionalUuid
|
|
1467
|
+
}),
|
|
1468
|
+
z.object({
|
|
1469
|
+
type: z.literal("narrative"),
|
|
1470
|
+
kind: z.literal("tradeoff"),
|
|
1471
|
+
picked: z.string().min(1).max(2e3),
|
|
1472
|
+
over: z.string().min(1).max(2e3),
|
|
1473
|
+
reason: z.string().min(1).max(2e3),
|
|
1474
|
+
stepId: optionalUuid
|
|
1475
|
+
}),
|
|
1476
|
+
z.object({
|
|
1477
|
+
type: z.literal("narrative"),
|
|
1478
|
+
kind: z.literal("assumption"),
|
|
1479
|
+
what: z.string().min(1).max(2e3),
|
|
1480
|
+
current_choice: z.string().min(1).max(2e3),
|
|
1481
|
+
stepId: optionalUuid
|
|
1482
|
+
}),
|
|
1483
|
+
z.object({
|
|
1484
|
+
type: z.literal("narrative"),
|
|
1485
|
+
kind: z.literal("attention"),
|
|
1486
|
+
what: z.string().min(1).max(2e3),
|
|
1487
|
+
currently_handling: z.string().min(1).max(2e3),
|
|
1488
|
+
stepId: optionalUuid
|
|
1489
|
+
}),
|
|
1490
|
+
z.object({
|
|
1491
|
+
type: z.literal("narrative"),
|
|
1492
|
+
kind: z.literal("scope_change"),
|
|
1493
|
+
what: z.string().min(1).max(2e3),
|
|
1494
|
+
reason: z.string().min(1).max(2e3),
|
|
1495
|
+
stepId: optionalUuid
|
|
1496
|
+
}),
|
|
1497
|
+
z.object({
|
|
1498
|
+
type: z.literal("narrative"),
|
|
1499
|
+
kind: z.literal("risk"),
|
|
1500
|
+
what: z.string().min(1).max(2e3),
|
|
1501
|
+
stepId: optionalUuid
|
|
1502
|
+
}),
|
|
1503
|
+
z.object({
|
|
1504
|
+
type: z.literal("narrative"),
|
|
1505
|
+
kind: z.literal("outcome"),
|
|
1506
|
+
capability: z.string().min(1).max(2e3),
|
|
1507
|
+
stepId: optionalUuid
|
|
1508
|
+
}),
|
|
1509
|
+
z.object({
|
|
1510
|
+
type: z.literal("narrative"),
|
|
1511
|
+
kind: z.literal("agent_update"),
|
|
1512
|
+
agent: z.string().min(1).max(120),
|
|
1513
|
+
state: z.string().min(1).max(60),
|
|
1514
|
+
activity: z.string().max(200).optional(),
|
|
1515
|
+
message: z.string().max(2e3).optional(),
|
|
1516
|
+
stepId: optionalUuid
|
|
1517
|
+
}),
|
|
1518
|
+
z.object({
|
|
1519
|
+
type: z.literal("narrative"),
|
|
1520
|
+
kind: z.literal("activity"),
|
|
1521
|
+
verb: z.enum([
|
|
1522
|
+
"exploring",
|
|
1523
|
+
"drafting",
|
|
1524
|
+
"testing",
|
|
1525
|
+
"reviewing",
|
|
1526
|
+
"preparing"
|
|
1527
|
+
]),
|
|
1528
|
+
target: z.string().min(1).max(2e3),
|
|
1529
|
+
stepId: optionalUuid
|
|
1530
|
+
}),
|
|
1531
|
+
z.object({
|
|
1532
|
+
type: z.literal("narrative"),
|
|
1533
|
+
kind: z.literal("setup_required"),
|
|
1534
|
+
category: z.enum([
|
|
1535
|
+
"env_var",
|
|
1536
|
+
"secret",
|
|
1537
|
+
"third_party_account",
|
|
1538
|
+
"api_key",
|
|
1539
|
+
"oauth_app",
|
|
1540
|
+
"dns_record",
|
|
1541
|
+
"billing_approval",
|
|
1542
|
+
"cloud_permission",
|
|
1543
|
+
"manual_config"
|
|
1544
|
+
]),
|
|
1545
|
+
title: z.string().min(1).max(300),
|
|
1546
|
+
instructions: z.string().min(1).max(2e3),
|
|
1547
|
+
key: z.string().regex(/^[A-Za-z_][A-Za-z0-9_.]*$/).max(255).optional(),
|
|
1548
|
+
isSecret: z.boolean().default(false),
|
|
1549
|
+
target: z.enum([
|
|
1550
|
+
"github_secret",
|
|
1551
|
+
"github_env",
|
|
1552
|
+
"cloud_env",
|
|
1553
|
+
"secrets_manager",
|
|
1554
|
+
"local_env",
|
|
1555
|
+
"external"
|
|
1556
|
+
]).optional(),
|
|
1557
|
+
providerDocUrl: z.string().url().max(1e3).refine((u) => /^https?:\/\//i.test(u), { message: "providerDocUrl must use http or https scheme" }).optional(),
|
|
1558
|
+
providerActionUrl: z.string().url().max(1e3).refine((u) => /^https?:\/\//i.test(u), { message: "providerActionUrl must use http or https scheme" }).optional(),
|
|
1559
|
+
phase: z.enum([
|
|
1560
|
+
"prereq",
|
|
1561
|
+
"wiring",
|
|
1562
|
+
"followup"
|
|
1563
|
+
]).optional(),
|
|
1564
|
+
suggestedValue: z.string().max(2e3).optional(),
|
|
1565
|
+
stepId: optionalUuid
|
|
1566
|
+
}),
|
|
1567
|
+
z.object({
|
|
1568
|
+
type: z.literal("narrative"),
|
|
1569
|
+
kind: z.literal("quality_finding"),
|
|
1570
|
+
source: z.enum([
|
|
1571
|
+
"tests",
|
|
1572
|
+
"self_review",
|
|
1573
|
+
"hardening",
|
|
1574
|
+
"pr_review",
|
|
1575
|
+
"ci_fix",
|
|
1576
|
+
"post_remediation"
|
|
1577
|
+
]),
|
|
1578
|
+
severity: z.enum([
|
|
1579
|
+
"blocking",
|
|
1580
|
+
"advisory",
|
|
1581
|
+
"info",
|
|
1582
|
+
"pass"
|
|
1583
|
+
]),
|
|
1584
|
+
title: z.string().min(1).max(2e3),
|
|
1585
|
+
body: z.string().max(2e3).optional(),
|
|
1586
|
+
filePath: z.string().max(500).optional(),
|
|
1587
|
+
lineNumber: optionalLineAnchor,
|
|
1588
|
+
stepId: optionalUuid,
|
|
1589
|
+
stats: z.object({
|
|
1590
|
+
testsTotal: z.number().int().min(0).optional(),
|
|
1591
|
+
testsPassed: z.number().int().min(0).optional(),
|
|
1592
|
+
testsFailed: z.number().int().min(0).optional(),
|
|
1593
|
+
testsAdded: z.number().int().min(0).optional(),
|
|
1594
|
+
coveragePct: z.number().min(0).max(100).optional(),
|
|
1595
|
+
coverageDelta: z.number().optional(),
|
|
1596
|
+
blockingFindings: z.number().int().min(0).optional(),
|
|
1597
|
+
advisoryFindings: z.number().int().min(0).optional(),
|
|
1598
|
+
criticalsAddressed: z.number().int().min(0).optional(),
|
|
1599
|
+
cycles: z.number().int().min(0).optional()
|
|
1600
|
+
}).optional()
|
|
1601
|
+
})
|
|
1602
|
+
]).options.map((option) => option.shape.kind.value);
|
|
1435
1603
|
[...BUILD_RUN_ACTIVE_STATUSES.filter((s) => s !== "reviewing")];
|
|
1436
1604
|
//#endregion
|
|
1437
1605
|
//#region ../../packages/types/dist/agents.js
|
|
@@ -1751,6 +1919,7 @@ const ENVIRONMENT_TYPES = [
|
|
|
1751
1919
|
const MAINTENANCE_FINDING_KINDS = [
|
|
1752
1920
|
"vulnerable_dependency",
|
|
1753
1921
|
"outdated_dependency",
|
|
1922
|
+
"license_violation",
|
|
1754
1923
|
"secret",
|
|
1755
1924
|
"sast",
|
|
1756
1925
|
"iac_misconfig"
|
|
@@ -1775,7 +1944,11 @@ const CODE_REMEDIATION_KINDS = [
|
|
|
1775
1944
|
"iac_misconfig"
|
|
1776
1945
|
];
|
|
1777
1946
|
new Map(Object.entries({
|
|
1778
|
-
deps: [
|
|
1947
|
+
deps: [
|
|
1948
|
+
"vulnerable_dependency",
|
|
1949
|
+
"outdated_dependency",
|
|
1950
|
+
"license_violation"
|
|
1951
|
+
],
|
|
1779
1952
|
secrets: ["secret"],
|
|
1780
1953
|
sast: ["sast"],
|
|
1781
1954
|
iac: ["iac_misconfig"]
|
|
@@ -1862,6 +2035,19 @@ function lockfileScope(path, groups) {
|
|
|
1862
2035
|
}
|
|
1863
2036
|
}
|
|
1864
2037
|
/**
|
|
2038
|
+
* The kinds that are a package in a lockfile (PRO-2862): the ones with a
|
|
2039
|
+
* dependency scope, a relation and an inventory entry. A code kind reuses the
|
|
2040
|
+
* dependency columns for its rule and category and has none of those.
|
|
2041
|
+
*/
|
|
2042
|
+
const DEPENDENCY_FINDING_KINDS = [
|
|
2043
|
+
"vulnerable_dependency",
|
|
2044
|
+
"outdated_dependency",
|
|
2045
|
+
"license_violation"
|
|
2046
|
+
];
|
|
2047
|
+
function isDependencyFindingKind(kind) {
|
|
2048
|
+
return DEPENDENCY_FINDING_KINDS.includes(kind);
|
|
2049
|
+
}
|
|
2050
|
+
/**
|
|
1865
2051
|
* Whether a dependency finding sits only in development dependencies, in at
|
|
1866
2052
|
* least one runtime dependency set, or in lockfiles that do not say. Decided
|
|
1867
2053
|
* per lockfile (`lockfileScope`) and combined pessimistically: one runtime
|
|
@@ -1872,8 +2058,12 @@ function lockfileScope(path, groups) {
|
|
|
1872
2058
|
* install script in any group.
|
|
1873
2059
|
*/
|
|
1874
2060
|
function dependencyScope(finding) {
|
|
1875
|
-
if (finding.kind
|
|
1876
|
-
|
|
2061
|
+
if (!isDependencyFindingKind(finding.kind)) return "unknown";
|
|
2062
|
+
return scopeFromGroups(finding.dependencyGroups);
|
|
2063
|
+
}
|
|
2064
|
+
/** The scope read from per-lockfile groups alone: for an inventory record, which is a package by construction. */
|
|
2065
|
+
function scopeFromGroups(dependencyGroups) {
|
|
2066
|
+
const entries = Object.entries(dependencyGroups ?? {});
|
|
1877
2067
|
if (entries.length === 0) return "unknown";
|
|
1878
2068
|
return combineScopes(entries.map(([path, groups]) => lockfileScope(path, Array.isArray(groups) ? groups : [])));
|
|
1879
2069
|
}
|
|
@@ -1891,8 +2081,12 @@ function combineScopes(scopes) {
|
|
|
1891
2081
|
* remediation step already finds every resolved copy and its parent itself.
|
|
1892
2082
|
*/
|
|
1893
2083
|
function dependencyRelation(finding) {
|
|
1894
|
-
if (finding.kind
|
|
1895
|
-
|
|
2084
|
+
if (!isDependencyFindingKind(finding.kind)) return "unknown";
|
|
2085
|
+
return relationFromLockfiles(finding.dependencyRelation);
|
|
2086
|
+
}
|
|
2087
|
+
/** The relation read from per-lockfile answers alone: for an inventory record, which is a package by construction. */
|
|
2088
|
+
function relationFromLockfiles(dependencyRelation) {
|
|
2089
|
+
const values = Object.values(dependencyRelation ?? {});
|
|
1896
2090
|
if (values.includes("direct")) return "direct";
|
|
1897
2091
|
if (values.length > 0 && values.every((v) => v === "transitive")) return "transitive";
|
|
1898
2092
|
return "unknown";
|
|
@@ -2436,7 +2630,7 @@ function importStatusOf(product) {
|
|
|
2436
2630
|
/** Why a side of the timeline is empty, for a list or a miss that shows one. */
|
|
2437
2631
|
function emptyOriginNote(origin, product) {
|
|
2438
2632
|
const imported = isImportedProduct(product);
|
|
2439
|
-
if (origin === "plan") return imported ? `This product
|
|
2633
|
+
if (origin === "plan") return imported ? `This product is set up from its code (an import), so no discovery chain is generated for it: its derived documents are on the reality side. Only the documents a person writes (${PLAN_ONLY_DOCUMENT_TYPES.map(productDocumentName).join(", ")}) would appear here.` : "No plan documents are published yet: they are written during discovery in Taiga.";
|
|
2440
2634
|
if (!imported) return "No reality documents yet: they are derived from the code once the first initiative is done (a merged pull request marks it done) or the repository is pushed to. Until then read the plan side.";
|
|
2441
2635
|
switch (importStatusOf(product)) {
|
|
2442
2636
|
case "pending": return "No reality documents yet: the import has not started. taiga_start_import starts it once a repository is linked; taiga_get_product's readiness says whether one is.";
|
|
@@ -2506,7 +2700,7 @@ const BY_ARGUMENT = {
|
|
|
2506
2700
|
/** Where an argument means something else on one tool, or the tool's 404 needs its own word. */
|
|
2507
2701
|
const BY_TOOL = {
|
|
2508
2702
|
taiga_resolve_action: () => "taiga_list_action_required lists the items with their IDs.",
|
|
2509
|
-
taiga_get_plan: (said) => /\bplan\b/i.test(said) ? "taiga_get_initiative's situation says whether one is being planned or failed, and
|
|
2703
|
+
taiga_get_plan: (said) => /\bplan\b/i.test(said) ? "taiga_get_initiative's situation says whether one is being planned or failed, and names the call that asks for one (taiga_queue_initiative off the line, taiga_plan_again for a failed plan)." : ITEM_ID_HINT
|
|
2510
2704
|
};
|
|
2511
2705
|
/**
|
|
2512
2706
|
* What the API calls the thing each ID names, as its 404 says it ("Environment
|
|
@@ -3145,7 +3339,7 @@ const SEARCH_PAGE_LIMIT = 10;
|
|
|
3145
3339
|
* the whole list to find its ID. `more` is true when it stopped before the end.
|
|
3146
3340
|
*/
|
|
3147
3341
|
async function searchPages(fetchPage, query, text, limit, keep = () => true, nameOf) {
|
|
3148
|
-
const needle = query
|
|
3342
|
+
const needle = searchable(query);
|
|
3149
3343
|
const rows = [];
|
|
3150
3344
|
const names = [];
|
|
3151
3345
|
let cursor;
|
|
@@ -3155,7 +3349,7 @@ async function searchPages(fetchPage, query, text, limit, keep = () => true, nam
|
|
|
3155
3349
|
for (const [at, row] of page.entries()) {
|
|
3156
3350
|
if (!keep(row)) continue;
|
|
3157
3351
|
if (nameOf) names.push(nameOf(row));
|
|
3158
|
-
if (!text(row).some((value) => typeof value === "string" && value
|
|
3352
|
+
if (!text(row).some((value) => typeof value === "string" && searchable(value).includes(needle))) continue;
|
|
3159
3353
|
rows.push(row);
|
|
3160
3354
|
if (rows.length === limit) return {
|
|
3161
3355
|
rows,
|
|
@@ -3178,12 +3372,18 @@ async function searchPages(fetchPage, query, text, limit, keep = () => true, nam
|
|
|
3178
3372
|
};
|
|
3179
3373
|
}
|
|
3180
3374
|
/**
|
|
3375
|
+
* Text as a search compares it: lower case, punctuation as spaces, so "sign in" finds "Staff Sign-In" (PRO-2823).
|
|
3376
|
+
*/
|
|
3377
|
+
function searchable(text) {
|
|
3378
|
+
return text.normalize("NFC").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, " ").trim() || text.toLowerCase();
|
|
3379
|
+
}
|
|
3380
|
+
/**
|
|
3181
3381
|
* Up to three names close to a query that matched none: a typo of the name or
|
|
3182
3382
|
* of one of its words, within a third of the query's length ("tiaga", "rally"
|
|
3183
3383
|
* for ref-rallly), or a shared word of three letters or more. Closest first.
|
|
3184
3384
|
*/
|
|
3185
3385
|
function closestNames(query, names) {
|
|
3186
|
-
const words = (text) => text
|
|
3386
|
+
const words = (text) => searchable(text).split(" ").filter((word) => word.length >= 3);
|
|
3187
3387
|
const wanted = new Set(words(query));
|
|
3188
3388
|
const ranked = names.map((name) => ({
|
|
3189
3389
|
name,
|
|
@@ -4064,15 +4264,28 @@ function runOutcome(run) {
|
|
|
4064
4264
|
}[disposition] ?? status
|
|
4065
4265
|
};
|
|
4066
4266
|
}
|
|
4267
|
+
/** Words a sentence does not end at: "e.g." and the like, and a lone initial. */
|
|
4268
|
+
const ABBREVIATION = /(?:\b(?:e\.g|i\.e|etc|vs|cf|incl|approx)|(?:^|\s)[A-Za-z])\.$/i;
|
|
4269
|
+
/** A finding's first sentence, capped: it ends at . ! or ? before white space, but not at an abbreviation. */
|
|
4270
|
+
function firstSentence$1(text) {
|
|
4271
|
+
let sentence = text;
|
|
4272
|
+
for (const match of text.matchAll(/[.!?](?=\s|$)/g)) {
|
|
4273
|
+
const end = match.index + 1;
|
|
4274
|
+
if (ABBREVIATION.test(text.slice(0, end))) continue;
|
|
4275
|
+
sentence = text.slice(0, end);
|
|
4276
|
+
break;
|
|
4277
|
+
}
|
|
4278
|
+
return sentence.length > 160 ? `${sentence.slice(0, 157)}...` : sentence;
|
|
4279
|
+
}
|
|
4067
4280
|
/**
|
|
4068
4281
|
* What hardening left blocking, in a sentence: the findings the run names (PRO-2817), or, for a run reported before
|
|
4069
4282
|
* it recorded them, how many.
|
|
4070
4283
|
*/
|
|
4071
4284
|
function hardeningSaid(blocking, named) {
|
|
4072
4285
|
const count = `Hardening ended with ${blocking} blocking finding${blocking === 1 ? "" : "s"} it could not fix`;
|
|
4073
|
-
if (named.length === 0) return `${count};
|
|
4286
|
+
if (named.length === 0) return `${count}; this run predates recording which.`;
|
|
4074
4287
|
const shown = named.slice(0, 3).map((finding) => {
|
|
4075
|
-
const message = String(finding["message"]);
|
|
4288
|
+
const message = firstSentence$1(String(finding["message"]));
|
|
4076
4289
|
const where = finding["line"] ? `${String(finding["path"])}:${String(finding["line"])}` : String(finding["path"]);
|
|
4077
4290
|
return `${message} (${String(finding["tool"])}, ${where})`;
|
|
4078
4291
|
});
|
|
@@ -4907,7 +5120,7 @@ function registerBacklogReads(server, client) {
|
|
|
4907
5120
|
}
|
|
4908
5121
|
if (params.cursor !== void 0) return toolError("VALIDATION_ERROR", "A title search answers one page; narrow the title, or leave query out to page through a column.");
|
|
4909
5122
|
let closedSeen = 0;
|
|
4910
|
-
const needle = query
|
|
5123
|
+
const needle = searchable(query);
|
|
4911
5124
|
const found = await searchPages((cursor) => client.listBacklogItems(id, {
|
|
4912
5125
|
...apiFilters,
|
|
4913
5126
|
...column === "open" ? { open: void 0 } : {},
|
|
@@ -4915,14 +5128,15 @@ function registerBacklogReads(server, client) {
|
|
|
4915
5128
|
cursor
|
|
4916
5129
|
}), query, (row) => [row.title], params.limit ?? 25, (row) => {
|
|
4917
5130
|
if (column !== "open" || !["done", "canceled"].includes(String(row.state))) return true;
|
|
4918
|
-
if (String(row.title)
|
|
5131
|
+
if (searchable(String(row.title)).includes(needle)) closedSeen += 1;
|
|
4919
5132
|
return false;
|
|
4920
|
-
});
|
|
5133
|
+
}, (row) => String(row.title));
|
|
4921
5134
|
const hidden = column === "open" && !found.more ? closedSeen : 0;
|
|
4922
5135
|
return toolResult({
|
|
4923
5136
|
...page(situatedRows(found.rows), null),
|
|
4924
5137
|
...hidden > 0 ? { closedMatches: `${hidden} done or canceled initiative${hidden === 1 ? " also matches" : "s also match"}; column: 'all' lists them.` } : {},
|
|
4925
|
-
...found.more ? { more: "There may be more than the limit carries: narrow the title, or pick one column." } : {}
|
|
5138
|
+
...found.more ? { more: "There may be more than the limit carries: narrow the title, or pick one column." } : {},
|
|
5139
|
+
...hidden === 0 ? nearNote(found.near) : {}
|
|
4926
5140
|
});
|
|
4927
5141
|
}));
|
|
4928
5142
|
server.registerTool("taiga_get_initiative", {
|
|
@@ -5178,6 +5392,11 @@ const FINDING_ROW_FIELDS = [
|
|
|
5178
5392
|
* read off the row; remediableBy stays as the sweep recorded it.
|
|
5179
5393
|
*/
|
|
5180
5394
|
function withFixability(row) {
|
|
5395
|
+
if (row["kind"] === "license_violation") return {
|
|
5396
|
+
...row,
|
|
5397
|
+
fixable: false,
|
|
5398
|
+
fixNote: "A license violation has no fix version: review the license or replace the package; taiga_fix_finding refuses it."
|
|
5399
|
+
};
|
|
5181
5400
|
if ((row["kind"] === "vulnerable_dependency" || row["kind"] === "outdated_dependency") && !row["recommendedVersion"] && row["remediableBy"] === "coding_agent") return {
|
|
5182
5401
|
...row,
|
|
5183
5402
|
fixable: false,
|
|
@@ -5195,7 +5414,7 @@ function withDependencyScope(row, { keepGroups = false } = {}) {
|
|
|
5195
5414
|
...row,
|
|
5196
5415
|
dependencyRelation: void 0
|
|
5197
5416
|
} : lean;
|
|
5198
|
-
if (!(row["kind"]
|
|
5417
|
+
if (!isDependencyFindingKind(String(row["kind"]))) return rest;
|
|
5199
5418
|
const kind = row["kind"];
|
|
5200
5419
|
return {
|
|
5201
5420
|
...rest,
|
|
@@ -5387,7 +5606,7 @@ function registerMaintenanceTools(server, client) {
|
|
|
5387
5606
|
const summary = standingSummary(standing);
|
|
5388
5607
|
const assessment = standing.assessment;
|
|
5389
5608
|
const assessedAt = typeof assessment?.assessedAt === "string" ? assessment.assessedAt : null;
|
|
5390
|
-
const note = assessment?.state === "never" ? "Never assessed: the documents have not been checked against the policies yet, so nothing here is an all-clear. An assessment runs when the reality documents are derived or refreshed (taiga_refresh_reality_documents, paid work: ask first)." : summary.controlsMet + summary.controlsNotApplicable === 0 && assessedAt ? `No control is recorded as met or not applicable: the last assessment (${assessedAt}) either ran before assessments recorded those verdicts, or found none. Until the next one, only violations are known; the rest are not assessed, which is no pass.` : null;
|
|
5609
|
+
const note = assessment?.state === "never" ? "Never assessed: the documents have not been checked against the policies yet, so nothing here is an all-clear. " + (standing.canAssess === false && standing.hasPolicies !== false ? "It has no reality documents to assess yet: an import derives them (taiga_start_import for a product set up from its code, paid work: ask first), and the assessment follows." : "An assessment runs when the reality documents are derived or refreshed (taiga_refresh_reality_documents, paid work: ask first).") : summary.controlsMet + summary.controlsNotApplicable === 0 && assessedAt ? `No control is recorded as met or not applicable: the last assessment (${assessedAt}) either ran before assessments recorded those verdicts, or found none. Until the next one, only violations are known; the rest are not assessed, which is no pass.` : null;
|
|
5391
5610
|
const resolvedAny = Array.isArray(standing.resolvedDivergences) && standing.resolvedDivergences.length > 0;
|
|
5392
5611
|
const wording = violations.length > 0 || resolvedAny ? { wordingNote: "A violation's evidence is from its assessedAt; its title and summary may be from when it was first recorded. Where they disagree, the evidence is current." } : {};
|
|
5393
5612
|
if (params.controlId && params.violationId) return toolError("VALIDATION_ERROR", "Name either a control (controlId) or one violation (violationId), not both.");
|
|
@@ -5400,8 +5619,8 @@ function registerMaintenanceTools(server, client) {
|
|
|
5400
5619
|
...control,
|
|
5401
5620
|
...control.state === "no_evidence" ? { meaning: "Not assessed: no finding or violation is recorded against it, and the documents were not found to show it met. That is no evidence either way, not a pass." } : {},
|
|
5402
5621
|
...control.assessment == null && (control.state === "breached" || control.state === "diverged") ? { assessmentNote: "No met or not-applicable verdict is shown: an open finding or violation against the control outranks one." } : {},
|
|
5622
|
+
...Number(control.findingCount ?? 0) > 0 ? { findingsNote: `${String(control.findingCount)} open scanner finding${control.findingCount === 1 ? "" : "s"} breach it; taiga_list_findings (productId) lists them with the controls each breaches. violations below are the documents' only.` } : {},
|
|
5403
5623
|
violations: pickFields(own, VIOLATION_FIELDS),
|
|
5404
|
-
...Number(control.findingCount ?? 0) > 0 ? { findingsNote: `${String(control.findingCount)} open scanner finding${control.findingCount === 1 ? "" : "s"} breach it; taiga_list_findings (productId) lists them with the controls each breaches.` } : {},
|
|
5405
5624
|
...own.length > 0 ? wording : {}
|
|
5406
5625
|
});
|
|
5407
5626
|
}
|
|
@@ -5659,7 +5878,7 @@ const STALE_CHECKS = 10;
|
|
|
5659
5878
|
async function staleReviews(client, rows) {
|
|
5660
5879
|
const byId = new Map(rows.filter((row) => row["kind"] === "review_pr" && row["subjectType"] === "build_run").map((row) => [String(row["id"]), row]));
|
|
5661
5880
|
const candidates = [...byId.values()].slice(0, STALE_CHECKS);
|
|
5662
|
-
|
|
5881
|
+
const uncheckedIds = new Set([...byId.keys()].slice(STALE_CHECKS));
|
|
5663
5882
|
const out = /* @__PURE__ */ new Map();
|
|
5664
5883
|
await Promise.all(candidates.map(async (row) => {
|
|
5665
5884
|
try {
|
|
@@ -5667,19 +5886,20 @@ async function staleReviews(client, rows) {
|
|
|
5667
5886
|
if (run["prMerged"] === true) return void out.set(String(row["id"]), "its pull request has merged");
|
|
5668
5887
|
if (run["prState"] === "closed") return void out.set(String(row["id"]), "its pull request was closed");
|
|
5669
5888
|
if (typeof run["backlogItemId"] !== "string") return;
|
|
5670
|
-
if (!client.hasCapability("backlog:read")) return void (
|
|
5889
|
+
if (!client.hasCapability("backlog:read")) return void uncheckedIds.add(String(row["id"]));
|
|
5671
5890
|
const item = (await client.getBacklogItem(run["backlogItemId"])).data;
|
|
5672
5891
|
if (item["state"] === "canceled" || item["status"] === "canceled") out.set(String(row["id"]), "its initiative was canceled; the pull request is still open on the forge");
|
|
5673
5892
|
else if (item["state"] === "done" || item["status"] === "done") out.set(String(row["id"]), "its initiative is done; this run is not the one that landed it");
|
|
5674
5893
|
} catch (error) {
|
|
5675
5894
|
if (!(error instanceof TaigaApiError || isNetworkFailure(error))) throw error;
|
|
5676
|
-
|
|
5895
|
+
uncheckedIds.add(String(row["id"]));
|
|
5677
5896
|
}
|
|
5678
5897
|
}));
|
|
5679
|
-
|
|
5898
|
+
await staleBacklogReviews(client, rows, out, uncheckedIds);
|
|
5680
5899
|
return {
|
|
5681
5900
|
stale: out,
|
|
5682
|
-
unchecked
|
|
5901
|
+
unchecked: uncheckedIds.size,
|
|
5902
|
+
uncheckedIds
|
|
5683
5903
|
};
|
|
5684
5904
|
}
|
|
5685
5905
|
/**
|
|
@@ -5687,12 +5907,13 @@ async function staleReviews(client, rows) {
|
|
|
5687
5907
|
* waiting 15 days on a product whose initiatives had all moved on, with nothing an agent could act on. Best effort,
|
|
5688
5908
|
* a handful of products; returns how many could not be checked.
|
|
5689
5909
|
*/
|
|
5690
|
-
async function staleBacklogReviews(client, rows, out) {
|
|
5910
|
+
async function staleBacklogReviews(client, rows, out, unchecked) {
|
|
5691
5911
|
const asks = [...new Map(rows.filter((row) => row["kind"] === "review_backlog" && typeof row["productId"] === "string").map((row) => [String(row["id"]), row])).values()];
|
|
5692
|
-
|
|
5693
|
-
if (
|
|
5912
|
+
const skip = (which) => which.forEach((row) => unchecked.add(String(row["id"])));
|
|
5913
|
+
if (asks.length === 0) return;
|
|
5914
|
+
if (!client.hasCapability("backlog:read")) return skip(asks);
|
|
5694
5915
|
const checked = [...new Set(asks.map((row) => String(row["productId"])))].slice(0, STALE_CHECKS);
|
|
5695
|
-
|
|
5916
|
+
skip(asks.filter((row) => !checked.includes(String(row["productId"]))));
|
|
5696
5917
|
const empty = /* @__PURE__ */ new Set();
|
|
5697
5918
|
await Promise.all(checked.map(async (productId) => {
|
|
5698
5919
|
try {
|
|
@@ -5703,11 +5924,10 @@ async function staleBacklogReviews(client, rows, out) {
|
|
|
5703
5924
|
empty.add(productId);
|
|
5704
5925
|
} catch (error) {
|
|
5705
5926
|
if (!(error instanceof TaigaApiError || isNetworkFailure(error))) throw error;
|
|
5706
|
-
|
|
5927
|
+
skip(asks.filter((row) => row["productId"] === productId));
|
|
5707
5928
|
}
|
|
5708
5929
|
}));
|
|
5709
5930
|
for (const row of asks) if (empty.has(String(row["productId"]))) out.set(String(row["id"]), "no initiative is in the backlog or todo now: the ones it asked about have moved on");
|
|
5710
|
-
return unchecked;
|
|
5711
5931
|
}
|
|
5712
5932
|
/**
|
|
5713
5933
|
* One product's Action Required as counts, the way the list's summary counts them: open is what still stands and is
|
|
@@ -5733,6 +5953,35 @@ function isDue(row, now) {
|
|
|
5733
5953
|
}
|
|
5734
5954
|
/** Pages read for the summary; past this it says it counted only those. */
|
|
5735
5955
|
const SUMMARY_PAGE = 100;
|
|
5956
|
+
/** Pages the summary reads before it says it stopped: 1,000 items. */
|
|
5957
|
+
const SUMMARY_PAGES = 10;
|
|
5958
|
+
/** Every item the summary counts, up to SUMMARY_PAGES pages; nextCursor set when it stopped short. Null on failure. */
|
|
5959
|
+
async function everyItem(client, query, orNull = true) {
|
|
5960
|
+
const data = [];
|
|
5961
|
+
let cursor;
|
|
5962
|
+
try {
|
|
5963
|
+
for (let index = 0; index < SUMMARY_PAGES; index += 1) {
|
|
5964
|
+
const page = await client.listInbox({
|
|
5965
|
+
...query,
|
|
5966
|
+
limit: SUMMARY_PAGE,
|
|
5967
|
+
cursor
|
|
5968
|
+
});
|
|
5969
|
+
data.push(...page.data);
|
|
5970
|
+
if (!page.nextCursor) return {
|
|
5971
|
+
data,
|
|
5972
|
+
nextCursor: null
|
|
5973
|
+
};
|
|
5974
|
+
cursor = page.nextCursor;
|
|
5975
|
+
}
|
|
5976
|
+
return {
|
|
5977
|
+
data,
|
|
5978
|
+
nextCursor: cursor ?? null
|
|
5979
|
+
};
|
|
5980
|
+
} catch (error) {
|
|
5981
|
+
if (orNull && (error instanceof TaigaApiError || isNetworkFailure(error))) return null;
|
|
5982
|
+
throw error;
|
|
5983
|
+
}
|
|
5984
|
+
}
|
|
5736
5985
|
const SETUP_TASK_FIELDS = [
|
|
5737
5986
|
"id",
|
|
5738
5987
|
"productId",
|
|
@@ -5827,14 +6076,7 @@ function registerInboxTools(server, client) {
|
|
|
5827
6076
|
...query,
|
|
5828
6077
|
...first ? { withTotal: true } : {}
|
|
5829
6078
|
});
|
|
5830
|
-
const overview = !first || resolved ? null : !result.nextCursor ? result : await client
|
|
5831
|
-
...query,
|
|
5832
|
-
limit: SUMMARY_PAGE,
|
|
5833
|
-
cursor: void 0
|
|
5834
|
-
}).catch((error) => {
|
|
5835
|
-
if (error instanceof TaigaApiError || isNetworkFailure(error)) return null;
|
|
5836
|
-
throw error;
|
|
5837
|
-
});
|
|
6079
|
+
const overview = !first || resolved ? null : !result.nextCursor ? result : await everyItem(client, query);
|
|
5838
6080
|
const now = Date.now();
|
|
5839
6081
|
const due_ = (row) => isDue(row, now);
|
|
5840
6082
|
const rows = result.data;
|
|
@@ -5843,26 +6085,35 @@ function registerInboxTools(server, client) {
|
|
|
5843
6085
|
const overviewDue = overview ? overview.data.filter(due_) : [];
|
|
5844
6086
|
const checked = resolved ? {
|
|
5845
6087
|
stale: /* @__PURE__ */ new Map(),
|
|
5846
|
-
unchecked: 0
|
|
6088
|
+
unchecked: 0,
|
|
6089
|
+
uncheckedIds: /* @__PURE__ */ new Set()
|
|
5847
6090
|
} : await staleReviews(client, [...due, ...overviewDue]);
|
|
5848
6091
|
const stale = checked.stale;
|
|
5849
6092
|
return toolResult({
|
|
5850
6093
|
...overview ? {
|
|
5851
6094
|
summary: summaryOf(overviewDue, stale),
|
|
5852
|
-
...overview.nextCursor ? { summaryNote: `The summary counts the first
|
|
6095
|
+
...overview.nextCursor ? { summaryNote: `The summary counts the first 1000 items` + (params.productId ? "." : "; productId narrows it to one product.") } : {}
|
|
5853
6096
|
} : {},
|
|
5854
6097
|
...page(due.map((row) => {
|
|
5855
6098
|
const item = itemRow(row, now, full === true, client.hasCapability("backlog:read"));
|
|
5856
6099
|
const why = stale.get(String(row["id"]));
|
|
5857
|
-
|
|
6100
|
+
if (why) return {
|
|
5858
6101
|
...item,
|
|
5859
6102
|
stale: why
|
|
6103
|
+
};
|
|
6104
|
+
return checked.uncheckedIds.has(String(row["id"])) ? {
|
|
6105
|
+
...item,
|
|
6106
|
+
staleChecked: false
|
|
5860
6107
|
} : item;
|
|
5861
6108
|
}), result.nextCursor),
|
|
5862
6109
|
...totalOf(result),
|
|
5863
|
-
...overview && typeof result.totalCount === "number" && (stale.size > 0 || overview.data.length > overviewDue.length || overview.nextCursor) ? { totalNote: "totalCount counts every item, stale and snoozed ones too; summary.open does not" + (overview.nextCursor ? `, and counts only the first
|
|
6110
|
+
...overview && typeof result.totalCount === "number" && (stale.size > 0 || overview.data.length > overviewDue.length || overview.nextCursor) ? { totalNote: "totalCount counts every item, stale and snoozed ones too; summary.open does not" + (overview.nextCursor ? `, and counts only the first 1000.` : ".") } : {},
|
|
5864
6111
|
...hidden > 0 ? { snoozedHidden: hidden } : {},
|
|
5865
|
-
...checked.unchecked > 0 ?
|
|
6112
|
+
...checked.unchecked > 0 ? (() => {
|
|
6113
|
+
const onPage = due.filter((row) => checked.uncheckedIds.has(String(row["id"]))).length;
|
|
6114
|
+
const beyond = checked.unchecked - onPage;
|
|
6115
|
+
return { staleNote: `${[...onPage > 0 ? [`${onPage} review${onPage === 1 ? "" : "s"} on this page could not be checked for whether the subject moved on: those rows say staleChecked: false, and their readWith read tells`] : [], ...beyond > 0 ? [`${beyond} review${beyond === 1 ? "" : "s"} counted in the summary but not on this page could not be checked either, so the summary counts them open`] : []].join("; ")}.` };
|
|
6116
|
+
})() : {}
|
|
5866
6117
|
});
|
|
5867
6118
|
}));
|
|
5868
6119
|
server.registerTool("taiga_list_setup_tasks", {
|
|
@@ -6227,13 +6478,10 @@ const CONTEXT_RUN_SAID = [
|
|
|
6227
6478
|
* the list's disagree (18 open here, 16 there), because this one counted stale reviews and put-off items as open.
|
|
6228
6479
|
*/
|
|
6229
6480
|
async function attentionPart(client, productId) {
|
|
6230
|
-
const result = await client
|
|
6231
|
-
productId,
|
|
6232
|
-
limit: 100
|
|
6233
|
-
});
|
|
6481
|
+
const result = await everyItem(client, { productId }, false);
|
|
6234
6482
|
return {
|
|
6235
6483
|
...await attentionCounts(client, result.data),
|
|
6236
|
-
...result.nextCursor ? { note: "These count the first
|
|
6484
|
+
...result.nextCursor ? { note: "These count the first 1000 items; taiga_list_action_required pages the rest." } : {}
|
|
6237
6485
|
};
|
|
6238
6486
|
}
|
|
6239
6487
|
/** The latest sweep and what it left open, as counts; taiga_list_findings lists them. */
|
|
@@ -6317,7 +6565,7 @@ function registerContextTools(server, client) {
|
|
|
6317
6565
|
if (!client.hasCapability("products:read")) return;
|
|
6318
6566
|
server.registerTool("taiga_get_product_context", {
|
|
6319
6567
|
title: "Get product context",
|
|
6320
|
-
description: `Read where a product stands in one call; the first read for any product question. Name it by ID, slug or git remote URL. Returns product (in setup, readiness and next move), factory with its instructions, specification and architecture outlines (section ids, sizes), policies with section counts, a count of initiatives per board column (done and canceled too) and up to ${BACKLOG_IN_CONTEXT} open ones, a person's turn first (taiga_list_initiatives lists
|
|
6568
|
+
description: `Read where a product stands in one call; the first read for any product question. Name it by ID, slug or git remote URL. Returns product (in setup, readiness and next move), factory with its instructions, specification and architecture outlines (section ids, sizes), policies with section counts, a count of initiatives per board column (done and canceled too) and up to ${BACKLOG_IN_CONTEXT} open ones, a person's turn first (taiga_list_initiatives lists them all, newest first), repositories, the latest five runs (taiga_list_runs has more), and counts for Action Required, open findings and policy violations. A part that failed is under unavailable, one the key cannot read under notGranted; neither shows as empty. documents: budget returns whole sections up to about ${DOCUMENT_BUDGET / 1e3}k characters each, full returns all.`,
|
|
6321
6569
|
inputSchema: z.object({
|
|
6322
6570
|
productId: productId.optional(),
|
|
6323
6571
|
slug: slug.optional(),
|
|
@@ -21990,6 +22238,8 @@ const MISSING_DOCUMENTS_HOW = "An imported product gets them from its import (ta
|
|
|
21990
22238
|
* allowDuplicateName says the person means it.
|
|
21991
22239
|
*/
|
|
21992
22240
|
const allowDuplicateName = z.boolean().optional().describe("Create it even when one with the same name exists (default false: refused, naming the existing one)");
|
|
22241
|
+
/** The same choice on a rename (PRO-2823). */
|
|
22242
|
+
const allowDuplicateRename = z.boolean().optional().describe("Rename it even when another has the new name (default false: refused, naming the existing one)");
|
|
21993
22243
|
/**
|
|
21994
22244
|
* A product name is refused when taken in its own factory only; the same name
|
|
21995
22245
|
* in another factory is allowed and the slug takes a suffix. A review read the
|
|
@@ -22072,13 +22322,25 @@ function registerSetupTools(server, client) {
|
|
|
22072
22322
|
}));
|
|
22073
22323
|
server.registerTool("taiga_rename_factory", {
|
|
22074
22324
|
title: "Rename a factory",
|
|
22075
|
-
description: "Rename a factory. Its slug follows the new name, so links to it change; products, members, knowledge and instructions stay as they are.",
|
|
22325
|
+
description: "Rename a factory. Its slug follows the new name, so links to it change; products, members, knowledge and instructions stay as they are. A name another factory has is refused, as on create.",
|
|
22076
22326
|
inputSchema: z.object({
|
|
22077
22327
|
factoryId,
|
|
22078
|
-
name: z.string().trim().min(1).max(255).describe("The new name")
|
|
22328
|
+
name: z.string().trim().min(1).max(255).describe("The new name"),
|
|
22329
|
+
allowDuplicateName: allowDuplicateRename
|
|
22079
22330
|
}),
|
|
22080
22331
|
annotations: OPERATE_SETTING
|
|
22081
|
-
}, (params) => runTool(async () =>
|
|
22332
|
+
}, (params) => runTool(async () => {
|
|
22333
|
+
try {
|
|
22334
|
+
return toolResult(pickFields([(await client.updateFactory(params.factoryId, {
|
|
22335
|
+
name: params.name,
|
|
22336
|
+
refuseDuplicateName: !params.allowDuplicateName
|
|
22337
|
+
})).data], FACTORY_FIELDS)[0]);
|
|
22338
|
+
} catch (error) {
|
|
22339
|
+
const refused = duplicateName("factory", error, "in this organization");
|
|
22340
|
+
if (refused) return refused;
|
|
22341
|
+
throw error;
|
|
22342
|
+
}
|
|
22343
|
+
}));
|
|
22082
22344
|
server.registerTool("taiga_preview_product_move", {
|
|
22083
22345
|
title: "Preview moving a product to another factory",
|
|
22084
22346
|
description: "Show who gains and who loses access if a product moves to another factory: the source factory's members lose it, the target's gain it; people in both, the product's own members, and owners and admins keep it, named with why. Show it to the person before taiga_move_product.",
|
|
@@ -22160,21 +22422,32 @@ function registerSetupTools(server, client) {
|
|
|
22160
22422
|
inputSchema: z.object({
|
|
22161
22423
|
productId,
|
|
22162
22424
|
name: z.string().trim().min(1).max(255).optional().describe("A new name"),
|
|
22425
|
+
allowDuplicateName: allowDuplicateRename,
|
|
22163
22426
|
...buildScope,
|
|
22164
22427
|
deploymentNotes: deploymentNotes.nullable().optional(),
|
|
22165
22428
|
emitDesignStandards: z.boolean().nullable().optional().describe("Inherit the organization's design standards; null follows the default")
|
|
22166
22429
|
}),
|
|
22167
22430
|
annotations: OPERATE_SETTING
|
|
22168
22431
|
}, (params) => runTool(async () => {
|
|
22169
|
-
const { productId: id, ...changes } = params;
|
|
22432
|
+
const { productId: id, allowDuplicateName: allowed, ...changes } = params;
|
|
22170
22433
|
if (Object.values(changes).every((value) => value === void 0)) return toolError("VALIDATION_ERROR", "Change at least one setting.");
|
|
22171
22434
|
const contradiction = notesWithNothingToDescribe(params);
|
|
22172
22435
|
if (contradiction) return contradiction;
|
|
22173
|
-
|
|
22436
|
+
let updated;
|
|
22437
|
+
try {
|
|
22438
|
+
updated = (await client.updateProduct(id, {
|
|
22439
|
+
...changes,
|
|
22440
|
+
...changes.name !== void 0 ? { refuseDuplicateName: !allowed } : {}
|
|
22441
|
+
})).data;
|
|
22442
|
+
} catch (error) {
|
|
22443
|
+
const refused = duplicateName("product", error, "in this factory");
|
|
22444
|
+
if (refused) return refused;
|
|
22445
|
+
throw error;
|
|
22446
|
+
}
|
|
22174
22447
|
const warnings = missingNotesWarning(updated);
|
|
22175
22448
|
return toolResult({
|
|
22176
|
-
...
|
|
22177
|
-
...
|
|
22449
|
+
...warnings.length > 0 ? { warnings } : {},
|
|
22450
|
+
...await productRow(client, updated)
|
|
22178
22451
|
});
|
|
22179
22452
|
}));
|
|
22180
22453
|
server.registerTool("taiga_refresh_reality_documents", {
|
|
@@ -22783,7 +23056,7 @@ function registerAllTools(server, client, options = {}) {
|
|
|
22783
23056
|
function registerWhoami(server, client, registered) {
|
|
22784
23057
|
server.registerTool("taiga_whoami", {
|
|
22785
23058
|
title: "Who am I",
|
|
22786
|
-
description: "Read who this API key acts as: the API it talks to (api
|
|
23059
|
+
description: "Read who this API key acts as: the API host it talks to (api), the person's email and organization role, the key's scopes (null means every capability the person has), its capabilities, and its tools split into reads, writes, and ask-first writes (paid agent work, changes others see). A tool the key cannot use is not offered at all.",
|
|
22787
23060
|
inputSchema: z.object({}),
|
|
22788
23061
|
annotations: READ_ONLY
|
|
22789
23062
|
}, () => runTool(async () => {
|
|
@@ -22822,7 +23095,7 @@ const SERVER_INSTRUCTIONS = [
|
|
|
22822
23095
|
"Taiga is a policy-aware software delivery platform. Use these tools (taiga_*) for anything about a Taiga",
|
|
22823
23096
|
"product: its initiatives, runs, policies, findings, deployments or setup.",
|
|
22824
23097
|
"",
|
|
22825
|
-
"**Which read.** Where a product stands (counts,
|
|
23098
|
+
"**Which read.** Where a product stands (counts, first rows): taiga_get_product_context, by remote URL, slug",
|
|
22826
23099
|
"or ID. What waits on people: taiga_list_action_required. A policy: taiga_get_organization_document (sections:",
|
|
22827
23100
|
"[], or a control id such as SEC-5.1), or the taiga://documents/{type} resources. Findings: taiga_list_findings.",
|
|
22828
23101
|
"One initiative or run: taiga_get_initiative, taiga_get_run. What this key may call: taiga_whoami.",
|
|
@@ -22830,10 +23103,10 @@ const SERVER_INSTRUCTIONS = [
|
|
|
22830
23103
|
"**Words.** An initiative is a unit of work, on a board of columns (backlog, todo, queue, plan, build, done,",
|
|
22831
23104
|
"canceled); plan and build are stations, and the line starts the next queued initiative when both are empty. A",
|
|
22832
23105
|
"run is one coding-agent attempt; hardening is its security pass; handed back means a person must act. A gate is",
|
|
22833
|
-
"a person's review; a sweep is a scan for findings; standing is how a product meets
|
|
22834
|
-
"up a new product, from a web conversation or an import.
|
|
23106
|
+
"a person's review; a sweep is a scan for findings; standing is how a product meets policy. Discovery sets",
|
|
23107
|
+
"up a new product, from a web conversation or an import. Factories own products.",
|
|
22835
23108
|
"",
|
|
22836
|
-
"**IDs** are UUIDs; the product, factory and initiative lists take query (product and factory misses name the",
|
|
23109
|
+
"**IDs** are UUIDs; the product, factory and initiative lists take query (product and factory misses may name the",
|
|
22837
23110
|
"closest). A NOT_FOUND names the tool that lists the right ones. Lists page with nextCursor. outcome, situation",
|
|
22838
23111
|
"and nextStep are one-sentence summaries; setup writes return readiness. Product documents have two timelines:",
|
|
22839
23112
|
"plan (intent) and reality (from the code).",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taiga-ai/mcp-server",
|
|
3
|
-
"version": "0.26.0-rc.
|
|
3
|
+
"version": "0.26.0-rc.504",
|
|
4
4
|
"description": "Taiga for AI coding assistants: read products, policies, initiatives and runs, and operate the line, over MCP",
|
|
5
5
|
"mcpName": "ga.tai/mcp-server",
|
|
6
6
|
"type": "module",
|