pipebrain-mcp 0.37.3 → 0.38.1
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/main.js +185 -6
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -367,11 +367,12 @@ var AGENT_CONFIG_ENTRIES = [
|
|
|
367
367
|
{ target: "agents/blind-spot-pass.md", sha256: "564751a751f7896e89c67e0d20788aff873e599766a3c30360073ba74e4c6099" },
|
|
368
368
|
{ target: "agents/code-verifier.md", sha256: "5a09f0f9d6b4c4d75068bfc5e314b3d1250de9328806e709d0c32d1369394124" },
|
|
369
369
|
{ target: "agents/heavy-implementer.md", sha256: "7d3af78d4241aaa67c283db245dd4065af6e8f55efce54c2f8623c1f83879830" },
|
|
370
|
+
{ target: "agents/issue-header-writer.md", sha256: "8809b2913c71d021e8fa6c72fe70e2d1db8019b90ed93d85786f5236552c7da6" },
|
|
370
371
|
{ target: "agents/spec-reviewer.md", sha256: "a46dc637006d1f810081046c57edcd731406727810ccc5e0b1c7d7b9b98be5ef" },
|
|
371
372
|
{ target: "agents/surgical-editor.md", sha256: "c8b7fe2ec8d76c396c0c698f36b0523be430b31e4b73b902409479a52d42ea5c" },
|
|
372
373
|
{ target: "pipebrain/hooks/block-bash4-features.js", sha256: "383b38169a1795d9da34aa1c939349b6a1f76bba1359d0af7fd14c11ea5da25f" },
|
|
373
374
|
{ target: "pipebrain/hooks/block-browser-dialogs.js", sha256: "b4f16a249881e6bd3c8d7f270c955270047a0636ffa99b89ad409fefd44b2b2f" },
|
|
374
|
-
{ target: "pipebrain/hooks/block-credential-echo.js", sha256: "
|
|
375
|
+
{ target: "pipebrain/hooks/block-credential-echo.js", sha256: "80f95808715744c72347ddcfda7a858b193f5c2b113a3b5aac93435552193437" },
|
|
375
376
|
{ target: "pipebrain/hooks/pre-commit-publication-fitness.js", sha256: "f2917cd369c03004a8d02410f695efa3400bcd4f2044d1d389e5ad2c7a9dae5f" },
|
|
376
377
|
{ target: "pipebrain/hooks/pre-commit-secret-scan.js", sha256: "1bc634774184e66748b3152016e806ef974449e976920fd710732170fdb4ad6b" },
|
|
377
378
|
{ target: "pipebrain/hooks/stop-orphaned-servers.js", sha256: "c3b847832c60605278e54ca1067c216bee073ab0a4bb046439b5f184b728792a" },
|
|
@@ -380,9 +381,10 @@ var AGENT_CONFIG_ENTRIES = [
|
|
|
380
381
|
{ target: "rules/shell.md", sha256: "499aac60b6419acb0bb362c0267b9399139dcceafb0eaf67702e40f3118b5c04" },
|
|
381
382
|
{ target: "rules/typescript.md", sha256: "c55eab0c243e38cdf903ab50b1e9d6d621516b893e602e1abff9a999a14e72d4" },
|
|
382
383
|
{ target: "skills/capture-correction/SKILL.md", sha256: "ebd2ee680a623f54a2a35b2ac75471902f30bd17013c34557fd4ae4a9add3f68" },
|
|
384
|
+
{ target: "skills/pipebrain-header-pass/SKILL.md", sha256: "10545e3c1c5215b031e1c76281e65cd2ecf7420b3f07a4221824591a8263c5fc" },
|
|
383
385
|
{ target: "skills/pipebrain-secrets/SKILL.md", sha256: "1b873f0a12c1384fbb84d071c4f46554cbff22fc502223ad58acd29578912ca6" },
|
|
384
386
|
{ target: "skills/pipebrain-work-session/SKILL.md", sha256: "1051dd142f61b8b4a3c4f8be11874a3d2541fd905763350ff3ef05e586c9bc8d" },
|
|
385
|
-
{ target: "skills/pipebrain-writing/SKILL.md", sha256: "
|
|
387
|
+
{ target: "skills/pipebrain-writing/SKILL.md", sha256: "9ab1a188215716907fbb54e03cfdef43bb378800713b000f498bff4ea75462f0" },
|
|
386
388
|
{ target: "skills/pipebrain/SKILL.md", sha256: "d30b133f64edb5fbd018eac4f8cd7316cb02fef39c9b5d2f275b5b9c1b20b6a2" },
|
|
387
389
|
{ target: "skills/requirements-altitude/SKILL.md", sha256: "05524877946cb1d25b32cac33f30617ba317207cd0862526e4cfbb944b18c181" }
|
|
388
390
|
];
|
|
@@ -15211,6 +15213,24 @@ function parseDeliverableDisplayId(value) {
|
|
|
15211
15213
|
}
|
|
15212
15214
|
|
|
15213
15215
|
// ../../libs/domain/dist/project-scope.js
|
|
15216
|
+
var SOURCE_TREE_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
15217
|
+
"apps",
|
|
15218
|
+
"components",
|
|
15219
|
+
"db",
|
|
15220
|
+
"dist",
|
|
15221
|
+
"docs",
|
|
15222
|
+
"e2e",
|
|
15223
|
+
"lib",
|
|
15224
|
+
"libs",
|
|
15225
|
+
"migrations",
|
|
15226
|
+
"modules",
|
|
15227
|
+
"node_modules",
|
|
15228
|
+
"packages",
|
|
15229
|
+
"plugins",
|
|
15230
|
+
"routes",
|
|
15231
|
+
"scripts",
|
|
15232
|
+
"src"
|
|
15233
|
+
]);
|
|
15214
15234
|
var CODE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
15215
15235
|
"cjs",
|
|
15216
15236
|
"css",
|
|
@@ -15239,6 +15259,83 @@ var SOURCE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
15239
15259
|
...CODE_FILE_EXTENSIONS,
|
|
15240
15260
|
...DOCUMENT_FILE_EXTENSIONS
|
|
15241
15261
|
]);
|
|
15262
|
+
var PATH_RE = /(?:\.{0,2}[/\\])?[\w.-]+(?:[/\\][\w.-]*[\w-][\w.-]*)+/g;
|
|
15263
|
+
var SEPARATOR_RE = /[/\\]/;
|
|
15264
|
+
var ROOTED_RE = /^\.{0,2}[/\\]/;
|
|
15265
|
+
function trimTrailingPunctuation(value) {
|
|
15266
|
+
return value.replace(/[.,;:!?)\]]+$/, "");
|
|
15267
|
+
}
|
|
15268
|
+
function namesAFile(candidate, extensions) {
|
|
15269
|
+
const segments = candidate.split(SEPARATOR_RE);
|
|
15270
|
+
const last = segments[segments.length - 1];
|
|
15271
|
+
const dot = last.lastIndexOf(".");
|
|
15272
|
+
return dot > 0 && extensions.has(last.slice(dot + 1).toLowerCase());
|
|
15273
|
+
}
|
|
15274
|
+
function namesASourceFile(candidate) {
|
|
15275
|
+
return namesAFile(candidate, SOURCE_FILE_EXTENSIONS);
|
|
15276
|
+
}
|
|
15277
|
+
function isRepositoryPath(candidate) {
|
|
15278
|
+
if (ROOTED_RE.test(candidate))
|
|
15279
|
+
return true;
|
|
15280
|
+
if (candidate.split(SEPARATOR_RE).some((segment) => SOURCE_TREE_DIRECTORIES.has(segment.toLowerCase()))) {
|
|
15281
|
+
return true;
|
|
15282
|
+
}
|
|
15283
|
+
return namesASourceFile(candidate);
|
|
15284
|
+
}
|
|
15285
|
+
function findPath(source, accepts) {
|
|
15286
|
+
for (const raw of source.match(PATH_RE) ?? []) {
|
|
15287
|
+
const candidate = trimTrailingPunctuation(raw);
|
|
15288
|
+
if (accepts(candidate))
|
|
15289
|
+
return candidate;
|
|
15290
|
+
}
|
|
15291
|
+
return null;
|
|
15292
|
+
}
|
|
15293
|
+
function findRepositoryPath(source) {
|
|
15294
|
+
return findPath(source, isRepositoryPath);
|
|
15295
|
+
}
|
|
15296
|
+
|
|
15297
|
+
// ../../libs/domain/dist/code-shape.js
|
|
15298
|
+
var LINK_RE = /\w+:\/\/\S+/g;
|
|
15299
|
+
var BARE_URL_RE = /\b[\w-]+(?:\.[\w-]+)*\.[A-Za-z]{2,}\/\S*/g;
|
|
15300
|
+
var LINE_SUFFIX_RE = /(?:\.{0,2}[/\\])?[\w.-]+(?:[/\\][\w.-]+)*:\d+/g;
|
|
15301
|
+
var LINE_IN_WORDS_RE = /\b(?:at|on|see|near|around|from)\s+lines?\s+\d+\b/i;
|
|
15302
|
+
var BACKTICKED_RE = /`[^`]+`/;
|
|
15303
|
+
var CALL_RE = /\b[A-Za-z_$][\w$]*[A-Z][\w$]*\(\)/;
|
|
15304
|
+
function findLineSuffix(source) {
|
|
15305
|
+
for (const candidate of source.match(LINE_SUFFIX_RE) ?? []) {
|
|
15306
|
+
const token = candidate.slice(0, candidate.lastIndexOf(":"));
|
|
15307
|
+
if (isRepositoryPath(token))
|
|
15308
|
+
return candidate;
|
|
15309
|
+
}
|
|
15310
|
+
return null;
|
|
15311
|
+
}
|
|
15312
|
+
function findCodeShape(text) {
|
|
15313
|
+
const prose = text.replace(LINK_RE, " ").replace(BARE_URL_RE, " ");
|
|
15314
|
+
const path = findRepositoryPath(prose);
|
|
15315
|
+
if (path !== null)
|
|
15316
|
+
return { kind: "repository_path", match: path };
|
|
15317
|
+
const suffixed = findLineSuffix(prose);
|
|
15318
|
+
if (suffixed !== null)
|
|
15319
|
+
return { kind: "line_number", match: suffixed };
|
|
15320
|
+
const spelled = LINE_IN_WORDS_RE.exec(prose);
|
|
15321
|
+
if (spelled !== null)
|
|
15322
|
+
return { kind: "line_number", match: spelled[0] };
|
|
15323
|
+
const backticked = BACKTICKED_RE.exec(prose);
|
|
15324
|
+
if (backticked !== null)
|
|
15325
|
+
return { kind: "symbol_name", match: backticked[0] };
|
|
15326
|
+
const called = CALL_RE.exec(prose);
|
|
15327
|
+
if (called !== null)
|
|
15328
|
+
return { kind: "symbol_name", match: called[0] };
|
|
15329
|
+
return null;
|
|
15330
|
+
}
|
|
15331
|
+
var CODE_SHAPE_NAMES = {
|
|
15332
|
+
repository_path: "repository path",
|
|
15333
|
+
line_number: "line number",
|
|
15334
|
+
symbol_name: "symbol name"
|
|
15335
|
+
};
|
|
15336
|
+
function codeShapeMessage(field, hit) {
|
|
15337
|
+
return `${field} is read by someone without the code open \u2014 take out the ${CODE_SHAPE_NAMES[hit.kind]} \u201C${hit.match}\u201D and say it in plain words`;
|
|
15338
|
+
}
|
|
15242
15339
|
|
|
15243
15340
|
// ../../libs/domain/dist/entities.js
|
|
15244
15341
|
var organizationSchema = external_exports.object({
|
|
@@ -15360,6 +15457,22 @@ var issueSchema = external_exports.object({
|
|
|
15360
15457
|
milestone: external_exports.string().nullable(),
|
|
15361
15458
|
title: external_exports.string().min(1),
|
|
15362
15459
|
body: markdownSchema,
|
|
15460
|
+
// The header (PIPE-373): what this work is, why it matters, and what it touches —
|
|
15461
|
+
// three fields written by `set_issue_header`, never by the authoring tools, and
|
|
15462
|
+
// carried on every surface that renders a display id.
|
|
15463
|
+
//
|
|
15464
|
+
// OPTIONAL RATHER THAN NULLABLE-REQUIRED, because a header is absent far more often
|
|
15465
|
+
// than it is empty: the columns arrived on a table already full of rows, and the wire
|
|
15466
|
+
// mappers drop an undefined field rather than sending an explicit null.
|
|
15467
|
+
summary: external_exports.string().nullable().optional(),
|
|
15468
|
+
why: external_exports.string().nullable().optional(),
|
|
15469
|
+
affectedSurfaces: external_exports.string().nullable().optional(),
|
|
15470
|
+
// DERIVED ON READ, never stored: true when the header was written from a body that
|
|
15471
|
+
// has since changed, and true as well when there is no header at all — one comparison
|
|
15472
|
+
// covers both, so "absent" and "drifted" cannot answer differently. The provenance
|
|
15473
|
+
// hash it is derived from (`header_body_sha`) is internal and never reaches the wire.
|
|
15474
|
+
headerStale: external_exports.boolean().optional(),
|
|
15475
|
+
headerDerivedAt: external_exports.iso.datetime({ offset: true }).nullable().optional(),
|
|
15363
15476
|
parentId: uuidSchema.nullable(),
|
|
15364
15477
|
claimedBy: uuidSchema.nullable(),
|
|
15365
15478
|
source: sourceSchema,
|
|
@@ -15376,6 +15489,7 @@ var criterionFiledBugSchema = external_exports.object({
|
|
|
15376
15489
|
projectKey: projectKeySchema,
|
|
15377
15490
|
displayId: external_exports.string().min(1),
|
|
15378
15491
|
title: external_exports.string().min(1),
|
|
15492
|
+
summary: external_exports.string().nullable().optional(),
|
|
15379
15493
|
status: issueStatusSchema
|
|
15380
15494
|
});
|
|
15381
15495
|
var criterionSchema = external_exports.object({
|
|
@@ -15809,7 +15923,19 @@ var criterionInputSchema = external_exports.strictObject({
|
|
|
15809
15923
|
message: "`howTo` applies to manual criteria only \u2014 an auto criterion is checked by a machine, so instructions on one have no reader",
|
|
15810
15924
|
path: ["howTo"]
|
|
15811
15925
|
});
|
|
15926
|
+
var refusedIssueHeaderFields = {
|
|
15927
|
+
summary: external_exports.never({
|
|
15928
|
+
error: "summary cannot be set here \u2014 create or update the issue first, then write its header with set_issue_header"
|
|
15929
|
+
}).optional().describe("Not writable here. The header summary is written by `set_issue_header`."),
|
|
15930
|
+
why: external_exports.never({
|
|
15931
|
+
error: "why cannot be set here \u2014 create or update the issue first, then write its header with set_issue_header"
|
|
15932
|
+
}).optional().describe("Not writable here. The header reason is written by `set_issue_header`."),
|
|
15933
|
+
affectedSurfaces: external_exports.never({
|
|
15934
|
+
error: "affected surfaces cannot be set here \u2014 create or update the issue first, then write its header with set_issue_header"
|
|
15935
|
+
}).optional().describe("Not writable here. The affected surfaces are written by `set_issue_header`.")
|
|
15936
|
+
};
|
|
15812
15937
|
var createIssueInputSchema = external_exports.strictObject({
|
|
15938
|
+
...refusedIssueHeaderFields,
|
|
15813
15939
|
project: projectRefSchema,
|
|
15814
15940
|
type: issueTypeSchema,
|
|
15815
15941
|
title: external_exports.string().min(1, { message: "issue title must not be empty" }),
|
|
@@ -15835,6 +15961,9 @@ var createIssueInputSchema = external_exports.strictObject({
|
|
|
15835
15961
|
criterionId: uuidSchema.optional()
|
|
15836
15962
|
});
|
|
15837
15963
|
var promoteFeedbackInputSchema = external_exports.strictObject({
|
|
15964
|
+
// Declared here as well as on create/update: this is a standalone strict object that
|
|
15965
|
+
// does NOT spread create's shape, and it funnels into `createIssue` (PIPE-373 AC-1).
|
|
15966
|
+
...refusedIssueHeaderFields,
|
|
15838
15967
|
type: external_exports.enum(["epic", "task"]),
|
|
15839
15968
|
title: external_exports.string().min(1, { message: "issue title must not be empty" }),
|
|
15840
15969
|
status: issueStatusSchema.optional(),
|
|
@@ -15858,6 +15987,7 @@ var decisionLinkChangesSchema = external_exports.strictObject({
|
|
|
15858
15987
|
remove: external_exports.array(decisionRefSchema).optional()
|
|
15859
15988
|
});
|
|
15860
15989
|
var updateIssueInputSchema = external_exports.strictObject({
|
|
15990
|
+
...refusedIssueHeaderFields,
|
|
15861
15991
|
issue: issueRefSchema,
|
|
15862
15992
|
title: external_exports.string().min(1, { message: "issue title must not be empty" }).optional(),
|
|
15863
15993
|
body: markdownSchema.optional(),
|
|
@@ -15870,6 +16000,23 @@ var updateIssueInputSchema = external_exports.strictObject({
|
|
|
15870
16000
|
deliverableLinks: deliverableLinkChangesSchema.optional(),
|
|
15871
16001
|
decisionLinks: decisionLinkChangesSchema.optional()
|
|
15872
16002
|
});
|
|
16003
|
+
var ISSUE_SUMMARY_MAX = 350;
|
|
16004
|
+
function inPlainWords(schema, field) {
|
|
16005
|
+
return schema.refine((value) => findCodeShape(value) === null, {
|
|
16006
|
+
error: (iss) => {
|
|
16007
|
+
const hit = findCodeShape(String(iss.input));
|
|
16008
|
+
return hit === null ? `${field} must be written in plain words` : codeShapeMessage(field, hit);
|
|
16009
|
+
}
|
|
16010
|
+
});
|
|
16011
|
+
}
|
|
16012
|
+
var setIssueHeaderInputSchema = external_exports.strictObject({
|
|
16013
|
+
issue: issueRefSchema,
|
|
16014
|
+
summary: inPlainWords(external_exports.string().min(1, { message: "summary must not be empty" }).max(ISSUE_SUMMARY_MAX, {
|
|
16015
|
+
error: (iss) => `summary must be at most ${ISSUE_SUMMARY_MAX} characters \u2014 received ${String(iss.input).length}`
|
|
16016
|
+
}), "summary"),
|
|
16017
|
+
why: inPlainWords(external_exports.string().min(1, { message: "why must not be empty \u2014 say why this matters" }), "why"),
|
|
16018
|
+
affectedSurfaces: inPlainWords(external_exports.string().min(1, { message: "affected surfaces must not be empty" }), "affected surfaces")
|
|
16019
|
+
});
|
|
15873
16020
|
var setStatusInputSchema = external_exports.strictObject({
|
|
15874
16021
|
issue: issueRefSchema,
|
|
15875
16022
|
status: issueStatusSchema
|
|
@@ -16064,6 +16211,12 @@ var listIssuesFilterSchema = external_exports.strictObject({
|
|
|
16064
16211
|
// query string and lets anything else fail HERE, out loud, because a filter the
|
|
16065
16212
|
// server quietly dropped returns everything and reads like one that matched.
|
|
16066
16213
|
blocked: external_exports.boolean().optional(),
|
|
16214
|
+
// Header-less or drifted work (PIPE-373 AC-9): `true` is everything whose header is
|
|
16215
|
+
// missing or no longer describes the body it was written from, which is the list
|
|
16216
|
+
// somebody sits down to write headers from. Omitted means both. A real boolean for
|
|
16217
|
+
// the same reason `blocked` is one — a filter the server quietly dropped returns
|
|
16218
|
+
// everything and reads exactly like one that matched everything.
|
|
16219
|
+
needsHeader: external_exports.boolean().optional(),
|
|
16067
16220
|
// Pagination — server default 200, max 500 (§9); default applied server-side.
|
|
16068
16221
|
limit: external_exports.number().int().min(1, { message: "limit must be at least 1" }).max(500, { message: "limit must be at most 500" }).optional(),
|
|
16069
16222
|
offset: external_exports.number().int().nonnegative().optional()
|
|
@@ -16549,6 +16702,22 @@ var ApiClient = class _ApiClient {
|
|
|
16549
16702
|
addComment(ref, body) {
|
|
16550
16703
|
return this.request("POST", `/api/v1/issues/${enc(ref)}/comments`, { body: { body } });
|
|
16551
16704
|
}
|
|
16705
|
+
/**
|
|
16706
|
+
* Writes the structured header onto an issue that already exists (PIPE-373).
|
|
16707
|
+
*
|
|
16708
|
+
* WHOLE VALUE, all three fields required — a stale header is replaced outright,
|
|
16709
|
+
* so there is nothing to merge and nothing to pin. The server stamps a hash of
|
|
16710
|
+
* the body the three texts were derived from at the same time, which is what
|
|
16711
|
+
* later reads compare against to say whether the header still describes the
|
|
16712
|
+
* issue; a partial write would leave that hash vouching for words nobody
|
|
16713
|
+
* rewrote.
|
|
16714
|
+
*
|
|
16715
|
+
* `issue` is dropped from the body because the ref rides the path, the way it
|
|
16716
|
+
* does on every other whole-value write here.
|
|
16717
|
+
*/
|
|
16718
|
+
setIssueHeader(ref, input) {
|
|
16719
|
+
return this.request("PUT", `/api/v1/issues/${enc(ref)}/header`, { body: input });
|
|
16720
|
+
}
|
|
16552
16721
|
/** Records the issue as blocked with a required reason (PIPE-99). */
|
|
16553
16722
|
setBlocked(ref, reason) {
|
|
16554
16723
|
return this.request("PUT", `/api/v1/issues/${enc(ref)}/blocked`, {
|
|
@@ -17517,7 +17686,7 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
|
|
|
17517
17686
|
server2.registerTool(
|
|
17518
17687
|
"list_issues",
|
|
17519
17688
|
{
|
|
17520
|
-
description: `List issue summaries (display id, title, type, status, terminal, blocked, priority, milestone, claimer, criteria progress, updated_at) filtered by project, environment, status[], type[], claimedBy (me/unclaimed/uuid), priority[], milestone, text (title+body), parent, and
|
|
17689
|
+
description: `List issue summaries (display id, title, type, status, terminal, blocked, priority, milestone, claimer, criteria progress, updated_at, and the header \u2014 \`summary\`, \`why\`, \`affectedSurfaces\`, \`headerStale\`, \`headerDerivedAt\` \u2014 so a list says what each issue is about without opening it) filtered by project, environment, status[], type[], claimedBy (me/unclaimed/uuid), priority[], milestone, text (title+body), parent, blocked, and needsHeader (\xA77). DEFAULT: with no status filter this returns BUILDABLE work only \u2014 ${OPEN_STATUSES.join(", ")}. Terminal work (done, killed) is excluded unless you ask for it by name: pass status explicitly, e.g. status: ["done","killed"] for finished and abandoned work, or list every value to see everything. An explicit status filter is used verbatim and the default is not merged into it. Every row also carries \`terminal\` (PIPE-R15 r4 rule 5). blocked: false returns only work with nothing blocking it, and every issue it returns can be moved to ready without refusal; blocked: true returns only blocked work. needsHeader: true returns exactly the issues whose header is missing or no longer describes the body it was derived from \u2014 the set \`set_issue_header\` still has to be called on \u2014 and needsHeader: false returns only those whose header still holds. Each row\u2019s \`headerStale\` is the same expression this filter selects on, so a row cannot disagree with the filter that returned it. The status default above is not lifted for it: name done and killed explicitly when the sweep is meant to reach finished work as well.`,
|
|
17521
17690
|
inputSchema: listIssuesFilterSchema.shape
|
|
17522
17691
|
},
|
|
17523
17692
|
// The default lives HERE, not in the API. PIPE-R15 r4 rule 5 makes the default
|
|
@@ -17531,7 +17700,7 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
|
|
|
17531
17700
|
(args) => run(() => client2.listIssues(args.status === void 0 ? { ...args, status: [...OPEN_STATUSES] } : args), GUIDANCE_POINTER)
|
|
17532
17701
|
);
|
|
17533
17702
|
server2.registerTool("get_issue", {
|
|
17534
|
-
description: "Full issue detail by display id or uuid: body, criteria (states, modes, evidence), explicit requirement links (pinned revision + staleness), the project\u2019s cross-cutting requirements at their latest approved revision, this organization\u2019s approved conventions \u2014 standing guidance that BINDS work in every project here, delivered in full at the revision a person approved \u2014 children, and comments (\xA77). Carries `terminal`: true when the issue is finished (`done`) or deliberately abandoned (`killed`) and is therefore NOT buildable work \u2014 check it before acting on an issue you were handed by key. Reading criterion state: `state_evidence` is required only when a TOKEN sets one, so a `manual` criterion \u2014 settable only from a web session \u2014 carries none by design, and the attribution the server already stores (who set it, and when) IS its record. What this payload tells you about ANOTHER issue goes stale. Reference that issue by key and say what you need from it; never restate its status, its criteria, or whether those criteria passed \u2014 such a sentence is true when written and wrong later, and a terminal issue is never revisited to correct it, so the stale reading is what the next reader inherits.",
|
|
17703
|
+
description: "Full issue detail by display id or uuid: the header \u2014 `summary`, `why`, `affectedSurfaces`, with `headerDerivedAt` and `headerStale`, true when nobody has written a header or the body has moved since one was written, and `set_issue_header` is what writes it \u2014 body, criteria (states, modes, evidence), explicit requirement links (pinned revision + staleness), the project\u2019s cross-cutting requirements at their latest approved revision, this organization\u2019s approved conventions \u2014 standing guidance that BINDS work in every project here, delivered in full at the revision a person approved \u2014 children, and comments (\xA77). Carries `terminal`: true when the issue is finished (`done`) or deliberately abandoned (`killed`) and is therefore NOT buildable work \u2014 check it before acting on an issue you were handed by key. Reading criterion state: `state_evidence` is required only when a TOKEN sets one, so a `manual` criterion \u2014 settable only from a web session \u2014 carries none by design, and the attribution the server already stores (who set it, and when) IS its record. What this payload tells you about ANOTHER issue goes stale. Reference that issue by key and say what you need from it; never restate its status, its criteria, or whether those criteria passed \u2014 such a sentence is true when written and wrong later, and a terminal issue is never revisited to correct it, so the stale reading is what the next reader inherits.",
|
|
17535
17704
|
inputSchema: { issue: issueRefSchema }
|
|
17536
17705
|
}, (args) => run(() => client2.getIssue(args.issue), GUIDANCE_POINTER));
|
|
17537
17706
|
server2.registerTool("create_issue", {
|
|
@@ -17557,6 +17726,10 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
|
|
|
17557
17726
|
description: "Update any subset of an issue\u2019s title, body, type, priority, milestone, environment, parent (self/cycle rejected), requirement_links (add/remove; adding a link to an unapproved requirement is rejected), deliverable_links (add/remove content-deliverable links, add pins at the current revision), or decision_links (add/remove decision records this work sits under \u2014 `KEY-D{n}`, from `list_decisions`). Changing type to `feedback` is rejected while the issue has criteria (\xA77), since feedback carries none; epic, task and bug interchange freely and the criteria ride along, keeping their AC-{n} numbers and their states. Clearing `milestone` (passing null) is rejected while the issue sits past `triage` \u2014 work already scheduled keeps a milestone (PIPE-R10 rule 9); swap it for a different one, or move the issue back to `triage` first. Writing guidance for this tool lives in the product doctrine, which `get_work_context` returns \u2014 see \u201CLink by key; never paste another record\u2019s text\u201D and \u201CA decision you need is filed as a question, never written into a body\u201D.",
|
|
17558
17727
|
inputSchema: updateIssueInputSchema.shape
|
|
17559
17728
|
}, ({ issue: issue2, ...fields }) => run(() => client2.updateIssue(issue2, fields)));
|
|
17729
|
+
server2.registerTool("set_issue_header", {
|
|
17730
|
+
description: "Write an issue\u2019s HEADER \u2014 the three lines an operator reads before deciding anything: `summary` (the change, in one sentence), `why` (the user, business, cost or operational reason rather than the technical one; when nothing is user-visible, the cost of leaving it alone) and `affectedSurfaces` (which parts of the product this touches, and who feels it). All three are REQUIRED and the write is whole-value: the header is replaced entire and never merged, so a partial write cannot leave the stored provenance vouching for lines it did not touch. Returns the full issue detail. WRITE ALL THREE IN PLAIN WORDS: no identifiers, no file paths, no type names, no jargon. Their reader is deciding, not building, and the depth belongs in the body underneath them. A `summary` shaped like code \u2014 a repository path, a line number, a symbol written as a call or in backticks \u2014 is refused (400) naming what was found. `summary` is capped at 350 characters; over it the write is refused naming both the cap and the length received. THE HEADER CAN BE WRITTEN NO OTHER WAY: `summary`, `why` or `affectedSurfaces` passed to `create_issue`, `update_issue` or `promote_feedback` is refused (400) naming the field, rather than silently dropped \u2014 create the issue first, then write its header here. Every read of the issue carries `headerStale`, true when the body has changed since these three lines were derived from it and true when no header was ever written; writing here clears it. Writing guidance for these three lines is stated above; the organization\u2019s own writing rules, which `get_work_context` returns, govern the body beneath them.",
|
|
17731
|
+
inputSchema: setIssueHeaderInputSchema.shape
|
|
17732
|
+
}, ({ issue: issue2, ...fields }) => run(() => client2.setIssueHeader(issue2, fields)));
|
|
17560
17733
|
server2.registerTool("set_status", {
|
|
17561
17734
|
description: "Set an issue\u2019s status; the \xA76 guards apply and a guard failure returns the specific reason, naming the rows it refused on (\xA77). The criteria gates read whatever criteria the issue carries, whatever its type: verify needs every auto criterion passed, done needs every criterion passed. ONE guard remains EPIC-ONLY, and the asymmetry is the rule rather than an oversight \u2014 an epic that has stated NO criteria is refused at both gates, because an empty list would clear them having committed to nothing, while a task or a bug carrying none moves through both untouched (most work is stated adequately by its body). `done` additionally requires every child of the issue being closed to be terminal \u2014 an epic, a task and a bug alike \u2014 read from each child\u2019s STATUS and never from its criteria. A BLOCKED issue \u2014 one carrying an explicit mark or waiting on a blocker that is not yet `done`/`killed` \u2014 cannot move to `ready` or `in_progress`, and the refusal names the reason and every unresolved blocker, so clear the mark (`clear_blocked`) or finish, kill or `unlink_blocker` each blocker first; every other target still moves while blocked. An issue with NO milestone cannot move to any status but `triage` and `killed` (PIPE-R10 rule 9) \u2014 set one with `update_issue` first; `killed` is exempt, so work nobody scheduled can still be abandoned.",
|
|
17562
17735
|
inputSchema: setStatusInputSchema.shape
|
|
@@ -17765,7 +17938,13 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
|
|
|
17765
17938
|
title: external_exports.string().min(1),
|
|
17766
17939
|
type: issueTypeSchema,
|
|
17767
17940
|
status: issueStatusSchema.optional(),
|
|
17768
|
-
body: markdownSchema.optional()
|
|
17941
|
+
body: markdownSchema.optional(),
|
|
17942
|
+
// This tool CREATES an issue, so it is a door the header refusal has to cover
|
|
17943
|
+
// (PIPE-373 AC-1). The fields must be DECLARED here rather than left off: the
|
|
17944
|
+
// SDK rebuilds this raw shape as a non-strict object, so an undeclared key is
|
|
17945
|
+
// STRIPPED rather than refused — the call would succeed, the issue would be
|
|
17946
|
+
// created, and the author's three lines would be dropped with nothing said.
|
|
17947
|
+
...refusedIssueHeaderFields
|
|
17769
17948
|
}
|
|
17770
17949
|
}, ({ deliverable, ...fields }) => run(() => client2.promoteDeliverable(deliverable, fields)));
|
|
17771
17950
|
server2.registerTool("list_assets", {
|
|
@@ -17924,7 +18103,7 @@ function loadMcpConfig() {
|
|
|
17924
18103
|
}
|
|
17925
18104
|
|
|
17926
18105
|
// src/version.ts
|
|
17927
|
-
var VERSION = "0.
|
|
18106
|
+
var VERSION = "0.38.1";
|
|
17928
18107
|
|
|
17929
18108
|
// src/main.ts
|
|
17930
18109
|
var config2 = loadMcpConfig();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pipebrain-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.1",
|
|
4
4
|
"description": "Pipebrain MCP server for Claude Code and Claude Desktop — issues, requirements, content, assets, and metadata-only secrets over the Pipebrain API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|