pipebrain-mcp 0.37.2 → 0.38.0
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 +219 -12
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -357,12 +357,35 @@ var require_main = __commonJS({
|
|
|
357
357
|
});
|
|
358
358
|
|
|
359
359
|
// ../../libs/agent-shared/dist/agent-config.js
|
|
360
|
+
import { createHash } from "node:crypto";
|
|
360
361
|
import { existsSync, readFileSync } from "node:fs";
|
|
361
362
|
import { homedir } from "node:os";
|
|
362
363
|
import { join } from "node:path";
|
|
363
364
|
|
|
364
365
|
// ../../libs/agent-shared/dist/agent-config-manifest.js
|
|
365
|
-
var
|
|
366
|
+
var AGENT_CONFIG_ENTRIES = [
|
|
367
|
+
{ target: "agents/blind-spot-pass.md", sha256: "564751a751f7896e89c67e0d20788aff873e599766a3c30360073ba74e4c6099" },
|
|
368
|
+
{ target: "agents/code-verifier.md", sha256: "5a09f0f9d6b4c4d75068bfc5e314b3d1250de9328806e709d0c32d1369394124" },
|
|
369
|
+
{ target: "agents/heavy-implementer.md", sha256: "7d3af78d4241aaa67c283db245dd4065af6e8f55efce54c2f8623c1f83879830" },
|
|
370
|
+
{ target: "agents/spec-reviewer.md", sha256: "a46dc637006d1f810081046c57edcd731406727810ccc5e0b1c7d7b9b98be5ef" },
|
|
371
|
+
{ target: "agents/surgical-editor.md", sha256: "c8b7fe2ec8d76c396c0c698f36b0523be430b31e4b73b902409479a52d42ea5c" },
|
|
372
|
+
{ target: "pipebrain/hooks/block-bash4-features.js", sha256: "383b38169a1795d9da34aa1c939349b6a1f76bba1359d0af7fd14c11ea5da25f" },
|
|
373
|
+
{ target: "pipebrain/hooks/block-browser-dialogs.js", sha256: "b4f16a249881e6bd3c8d7f270c955270047a0636ffa99b89ad409fefd44b2b2f" },
|
|
374
|
+
{ target: "pipebrain/hooks/block-credential-echo.js", sha256: "a22ad6746158df0b6cc80039b82ab9a9ba269aab535d5c01b6c917223b10c15c" },
|
|
375
|
+
{ target: "pipebrain/hooks/pre-commit-publication-fitness.js", sha256: "f2917cd369c03004a8d02410f695efa3400bcd4f2044d1d389e5ad2c7a9dae5f" },
|
|
376
|
+
{ target: "pipebrain/hooks/pre-commit-secret-scan.js", sha256: "1bc634774184e66748b3152016e806ef974449e976920fd710732170fdb4ad6b" },
|
|
377
|
+
{ target: "pipebrain/hooks/stop-orphaned-servers.js", sha256: "c3b847832c60605278e54ca1067c216bee073ab0a4bb046439b5f184b728792a" },
|
|
378
|
+
{ target: "pipebrain/team.md", sha256: "341c72843253f594632f3c0762e3ee045bdb8703189b80c71d0ab58cbf07eb82" },
|
|
379
|
+
{ target: "rules/react-tailwind.md", sha256: "80bb51b2d8fd5782b7c742526544e7841ee38aa575faf7a8c7b4217ba5731989" },
|
|
380
|
+
{ target: "rules/shell.md", sha256: "499aac60b6419acb0bb362c0267b9399139dcceafb0eaf67702e40f3118b5c04" },
|
|
381
|
+
{ target: "rules/typescript.md", sha256: "c55eab0c243e38cdf903ab50b1e9d6d621516b893e602e1abff9a999a14e72d4" },
|
|
382
|
+
{ target: "skills/capture-correction/SKILL.md", sha256: "ebd2ee680a623f54a2a35b2ac75471902f30bd17013c34557fd4ae4a9add3f68" },
|
|
383
|
+
{ target: "skills/pipebrain-secrets/SKILL.md", sha256: "1b873f0a12c1384fbb84d071c4f46554cbff22fc502223ad58acd29578912ca6" },
|
|
384
|
+
{ target: "skills/pipebrain-work-session/SKILL.md", sha256: "1051dd142f61b8b4a3c4f8be11874a3d2541fd905763350ff3ef05e586c9bc8d" },
|
|
385
|
+
{ target: "skills/pipebrain-writing/SKILL.md", sha256: "92422f4ab49a83f470625c62dce070d0cd1caf1212f1bb2668b1bbba5428c5b6" },
|
|
386
|
+
{ target: "skills/pipebrain/SKILL.md", sha256: "d30b133f64edb5fbd018eac4f8cd7316cb02fef39c9b5d2f275b5b9c1b20b6a2" },
|
|
387
|
+
{ target: "skills/requirements-altitude/SKILL.md", sha256: "05524877946cb1d25b32cac33f30617ba317207cd0862526e4cfbb944b18c181" }
|
|
388
|
+
];
|
|
366
389
|
|
|
367
390
|
// ../../libs/agent-shared/dist/agent-config.js
|
|
368
391
|
function agentConfigDir(env = process.env) {
|
|
@@ -388,11 +411,18 @@ function readInstalledStamp(dir) {
|
|
|
388
411
|
return null;
|
|
389
412
|
}
|
|
390
413
|
}
|
|
391
|
-
function agentConfigStatus(dir,
|
|
392
|
-
|
|
393
|
-
if (stamp === null)
|
|
414
|
+
function agentConfigStatus(dir, entries = AGENT_CONFIG_ENTRIES) {
|
|
415
|
+
if (readInstalledStamp(dir) === null)
|
|
394
416
|
return "absent";
|
|
395
|
-
|
|
417
|
+
for (const entry of entries) {
|
|
418
|
+
const file2 = join(dir, entry.target);
|
|
419
|
+
if (!existsSync(file2))
|
|
420
|
+
return "behind";
|
|
421
|
+
if (createHash("sha256").update(readFileSync(file2)).digest("hex") !== entry.sha256) {
|
|
422
|
+
return "behind";
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return "current";
|
|
396
426
|
}
|
|
397
427
|
var UPDATE_COMMAND = "ppb setup";
|
|
398
428
|
function agentConfigNotice(status) {
|
|
@@ -400,7 +430,7 @@ function agentConfigNotice(status) {
|
|
|
400
430
|
}
|
|
401
431
|
|
|
402
432
|
// ../../libs/agent-shared/dist/asset-transfer.js
|
|
403
|
-
import { createHash } from "node:crypto";
|
|
433
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
404
434
|
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
405
435
|
import { basename, extname } from "node:path";
|
|
406
436
|
var MIME_BY_EXT = {
|
|
@@ -429,7 +459,7 @@ async function uploadAssetFromPath(client2, apiBaseUrl, input) {
|
|
|
429
459
|
const bytes = readFileSync2(input.path);
|
|
430
460
|
const filename = basename(input.path);
|
|
431
461
|
const contentType = input.contentType ?? guessContentType(filename);
|
|
432
|
-
const sha256 =
|
|
462
|
+
const sha256 = createHash2("sha256").update(bytes).digest("hex");
|
|
433
463
|
const { asset, uploadUrl } = await client2.createAsset({
|
|
434
464
|
project: input.project,
|
|
435
465
|
filename,
|
|
@@ -459,7 +489,7 @@ async function downloadAssetToPath(client2, input) {
|
|
|
459
489
|
throw new Error(`asset ${input.asset} is ${asset.status}, not active \u2014 no bytes to download.`);
|
|
460
490
|
}
|
|
461
491
|
const buf = Buffer.from(await client2.downloadAsset(input.asset));
|
|
462
|
-
const got =
|
|
492
|
+
const got = createHash2("sha256").update(buf).digest("hex");
|
|
463
493
|
if (got !== asset.sha256) {
|
|
464
494
|
throw new Error(`integrity check failed: downloaded sha256 ${got} != recorded ${asset.sha256}.`);
|
|
465
495
|
}
|
|
@@ -15181,6 +15211,24 @@ function parseDeliverableDisplayId(value) {
|
|
|
15181
15211
|
}
|
|
15182
15212
|
|
|
15183
15213
|
// ../../libs/domain/dist/project-scope.js
|
|
15214
|
+
var SOURCE_TREE_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
15215
|
+
"apps",
|
|
15216
|
+
"components",
|
|
15217
|
+
"db",
|
|
15218
|
+
"dist",
|
|
15219
|
+
"docs",
|
|
15220
|
+
"e2e",
|
|
15221
|
+
"lib",
|
|
15222
|
+
"libs",
|
|
15223
|
+
"migrations",
|
|
15224
|
+
"modules",
|
|
15225
|
+
"node_modules",
|
|
15226
|
+
"packages",
|
|
15227
|
+
"plugins",
|
|
15228
|
+
"routes",
|
|
15229
|
+
"scripts",
|
|
15230
|
+
"src"
|
|
15231
|
+
]);
|
|
15184
15232
|
var CODE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
15185
15233
|
"cjs",
|
|
15186
15234
|
"css",
|
|
@@ -15209,6 +15257,83 @@ var SOURCE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
15209
15257
|
...CODE_FILE_EXTENSIONS,
|
|
15210
15258
|
...DOCUMENT_FILE_EXTENSIONS
|
|
15211
15259
|
]);
|
|
15260
|
+
var PATH_RE = /(?:\.{0,2}[/\\])?[\w.-]+(?:[/\\][\w.-]*[\w-][\w.-]*)+/g;
|
|
15261
|
+
var SEPARATOR_RE = /[/\\]/;
|
|
15262
|
+
var ROOTED_RE = /^\.{0,2}[/\\]/;
|
|
15263
|
+
function trimTrailingPunctuation(value) {
|
|
15264
|
+
return value.replace(/[.,;:!?)\]]+$/, "");
|
|
15265
|
+
}
|
|
15266
|
+
function namesAFile(candidate, extensions) {
|
|
15267
|
+
const segments = candidate.split(SEPARATOR_RE);
|
|
15268
|
+
const last = segments[segments.length - 1];
|
|
15269
|
+
const dot = last.lastIndexOf(".");
|
|
15270
|
+
return dot > 0 && extensions.has(last.slice(dot + 1).toLowerCase());
|
|
15271
|
+
}
|
|
15272
|
+
function namesASourceFile(candidate) {
|
|
15273
|
+
return namesAFile(candidate, SOURCE_FILE_EXTENSIONS);
|
|
15274
|
+
}
|
|
15275
|
+
function isRepositoryPath(candidate) {
|
|
15276
|
+
if (ROOTED_RE.test(candidate))
|
|
15277
|
+
return true;
|
|
15278
|
+
if (candidate.split(SEPARATOR_RE).some((segment) => SOURCE_TREE_DIRECTORIES.has(segment.toLowerCase()))) {
|
|
15279
|
+
return true;
|
|
15280
|
+
}
|
|
15281
|
+
return namesASourceFile(candidate);
|
|
15282
|
+
}
|
|
15283
|
+
function findPath(source, accepts) {
|
|
15284
|
+
for (const raw of source.match(PATH_RE) ?? []) {
|
|
15285
|
+
const candidate = trimTrailingPunctuation(raw);
|
|
15286
|
+
if (accepts(candidate))
|
|
15287
|
+
return candidate;
|
|
15288
|
+
}
|
|
15289
|
+
return null;
|
|
15290
|
+
}
|
|
15291
|
+
function findRepositoryPath(source) {
|
|
15292
|
+
return findPath(source, isRepositoryPath);
|
|
15293
|
+
}
|
|
15294
|
+
|
|
15295
|
+
// ../../libs/domain/dist/code-shape.js
|
|
15296
|
+
var LINK_RE = /\w+:\/\/\S+/g;
|
|
15297
|
+
var BARE_URL_RE = /\b[\w-]+(?:\.[\w-]+)*\.[A-Za-z]{2,}\/\S*/g;
|
|
15298
|
+
var LINE_SUFFIX_RE = /(?:\.{0,2}[/\\])?[\w.-]+(?:[/\\][\w.-]+)*:\d+/g;
|
|
15299
|
+
var LINE_IN_WORDS_RE = /\b(?:at|on|see|near|around|from)\s+lines?\s+\d+\b/i;
|
|
15300
|
+
var BACKTICKED_RE = /`[^`]+`/;
|
|
15301
|
+
var CALL_RE = /\b[A-Za-z_$][\w$]*[A-Z][\w$]*\(\)/;
|
|
15302
|
+
function findLineSuffix(source) {
|
|
15303
|
+
for (const candidate of source.match(LINE_SUFFIX_RE) ?? []) {
|
|
15304
|
+
const token = candidate.slice(0, candidate.lastIndexOf(":"));
|
|
15305
|
+
if (isRepositoryPath(token))
|
|
15306
|
+
return candidate;
|
|
15307
|
+
}
|
|
15308
|
+
return null;
|
|
15309
|
+
}
|
|
15310
|
+
function findCodeShape(text) {
|
|
15311
|
+
const prose = text.replace(LINK_RE, " ").replace(BARE_URL_RE, " ");
|
|
15312
|
+
const path = findRepositoryPath(prose);
|
|
15313
|
+
if (path !== null)
|
|
15314
|
+
return { kind: "repository_path", match: path };
|
|
15315
|
+
const suffixed = findLineSuffix(prose);
|
|
15316
|
+
if (suffixed !== null)
|
|
15317
|
+
return { kind: "line_number", match: suffixed };
|
|
15318
|
+
const spelled = LINE_IN_WORDS_RE.exec(prose);
|
|
15319
|
+
if (spelled !== null)
|
|
15320
|
+
return { kind: "line_number", match: spelled[0] };
|
|
15321
|
+
const backticked = BACKTICKED_RE.exec(prose);
|
|
15322
|
+
if (backticked !== null)
|
|
15323
|
+
return { kind: "symbol_name", match: backticked[0] };
|
|
15324
|
+
const called = CALL_RE.exec(prose);
|
|
15325
|
+
if (called !== null)
|
|
15326
|
+
return { kind: "symbol_name", match: called[0] };
|
|
15327
|
+
return null;
|
|
15328
|
+
}
|
|
15329
|
+
var CODE_SHAPE_NAMES = {
|
|
15330
|
+
repository_path: "repository path",
|
|
15331
|
+
line_number: "line number",
|
|
15332
|
+
symbol_name: "symbol name"
|
|
15333
|
+
};
|
|
15334
|
+
function codeShapeMessage(field, hit) {
|
|
15335
|
+
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`;
|
|
15336
|
+
}
|
|
15212
15337
|
|
|
15213
15338
|
// ../../libs/domain/dist/entities.js
|
|
15214
15339
|
var organizationSchema = external_exports.object({
|
|
@@ -15330,6 +15455,22 @@ var issueSchema = external_exports.object({
|
|
|
15330
15455
|
milestone: external_exports.string().nullable(),
|
|
15331
15456
|
title: external_exports.string().min(1),
|
|
15332
15457
|
body: markdownSchema,
|
|
15458
|
+
// The header (PIPE-373): what this work is, why it matters, and what it touches —
|
|
15459
|
+
// three fields written by `set_issue_header`, never by the authoring tools, and
|
|
15460
|
+
// carried on every surface that renders a display id.
|
|
15461
|
+
//
|
|
15462
|
+
// OPTIONAL RATHER THAN NULLABLE-REQUIRED, because a header is absent far more often
|
|
15463
|
+
// than it is empty: the columns arrived on a table already full of rows, and the wire
|
|
15464
|
+
// mappers drop an undefined field rather than sending an explicit null.
|
|
15465
|
+
summary: external_exports.string().nullable().optional(),
|
|
15466
|
+
why: external_exports.string().nullable().optional(),
|
|
15467
|
+
affectedSurfaces: external_exports.string().nullable().optional(),
|
|
15468
|
+
// DERIVED ON READ, never stored: true when the header was written from a body that
|
|
15469
|
+
// has since changed, and true as well when there is no header at all — one comparison
|
|
15470
|
+
// covers both, so "absent" and "drifted" cannot answer differently. The provenance
|
|
15471
|
+
// hash it is derived from (`header_body_sha`) is internal and never reaches the wire.
|
|
15472
|
+
headerStale: external_exports.boolean().optional(),
|
|
15473
|
+
headerDerivedAt: external_exports.iso.datetime({ offset: true }).nullable().optional(),
|
|
15333
15474
|
parentId: uuidSchema.nullable(),
|
|
15334
15475
|
claimedBy: uuidSchema.nullable(),
|
|
15335
15476
|
source: sourceSchema,
|
|
@@ -15346,6 +15487,7 @@ var criterionFiledBugSchema = external_exports.object({
|
|
|
15346
15487
|
projectKey: projectKeySchema,
|
|
15347
15488
|
displayId: external_exports.string().min(1),
|
|
15348
15489
|
title: external_exports.string().min(1),
|
|
15490
|
+
summary: external_exports.string().nullable().optional(),
|
|
15349
15491
|
status: issueStatusSchema
|
|
15350
15492
|
});
|
|
15351
15493
|
var criterionSchema = external_exports.object({
|
|
@@ -15779,7 +15921,19 @@ var criterionInputSchema = external_exports.strictObject({
|
|
|
15779
15921
|
message: "`howTo` applies to manual criteria only \u2014 an auto criterion is checked by a machine, so instructions on one have no reader",
|
|
15780
15922
|
path: ["howTo"]
|
|
15781
15923
|
});
|
|
15924
|
+
var refusedIssueHeaderFields = {
|
|
15925
|
+
summary: external_exports.never({
|
|
15926
|
+
error: "summary cannot be set here \u2014 create or update the issue first, then write its header with set_issue_header"
|
|
15927
|
+
}).optional().describe("Not writable here. The header summary is written by `set_issue_header`."),
|
|
15928
|
+
why: external_exports.never({
|
|
15929
|
+
error: "why cannot be set here \u2014 create or update the issue first, then write its header with set_issue_header"
|
|
15930
|
+
}).optional().describe("Not writable here. The header reason is written by `set_issue_header`."),
|
|
15931
|
+
affectedSurfaces: external_exports.never({
|
|
15932
|
+
error: "affected surfaces cannot be set here \u2014 create or update the issue first, then write its header with set_issue_header"
|
|
15933
|
+
}).optional().describe("Not writable here. The affected surfaces are written by `set_issue_header`.")
|
|
15934
|
+
};
|
|
15782
15935
|
var createIssueInputSchema = external_exports.strictObject({
|
|
15936
|
+
...refusedIssueHeaderFields,
|
|
15783
15937
|
project: projectRefSchema,
|
|
15784
15938
|
type: issueTypeSchema,
|
|
15785
15939
|
title: external_exports.string().min(1, { message: "issue title must not be empty" }),
|
|
@@ -15805,6 +15959,9 @@ var createIssueInputSchema = external_exports.strictObject({
|
|
|
15805
15959
|
criterionId: uuidSchema.optional()
|
|
15806
15960
|
});
|
|
15807
15961
|
var promoteFeedbackInputSchema = external_exports.strictObject({
|
|
15962
|
+
// Declared here as well as on create/update: this is a standalone strict object that
|
|
15963
|
+
// does NOT spread create's shape, and it funnels into `createIssue` (PIPE-373 AC-1).
|
|
15964
|
+
...refusedIssueHeaderFields,
|
|
15808
15965
|
type: external_exports.enum(["epic", "task"]),
|
|
15809
15966
|
title: external_exports.string().min(1, { message: "issue title must not be empty" }),
|
|
15810
15967
|
status: issueStatusSchema.optional(),
|
|
@@ -15828,6 +15985,7 @@ var decisionLinkChangesSchema = external_exports.strictObject({
|
|
|
15828
15985
|
remove: external_exports.array(decisionRefSchema).optional()
|
|
15829
15986
|
});
|
|
15830
15987
|
var updateIssueInputSchema = external_exports.strictObject({
|
|
15988
|
+
...refusedIssueHeaderFields,
|
|
15831
15989
|
issue: issueRefSchema,
|
|
15832
15990
|
title: external_exports.string().min(1, { message: "issue title must not be empty" }).optional(),
|
|
15833
15991
|
body: markdownSchema.optional(),
|
|
@@ -15840,6 +15998,23 @@ var updateIssueInputSchema = external_exports.strictObject({
|
|
|
15840
15998
|
deliverableLinks: deliverableLinkChangesSchema.optional(),
|
|
15841
15999
|
decisionLinks: decisionLinkChangesSchema.optional()
|
|
15842
16000
|
});
|
|
16001
|
+
var ISSUE_SUMMARY_MAX = 350;
|
|
16002
|
+
function inPlainWords(schema, field) {
|
|
16003
|
+
return schema.refine((value) => findCodeShape(value) === null, {
|
|
16004
|
+
error: (iss) => {
|
|
16005
|
+
const hit = findCodeShape(String(iss.input));
|
|
16006
|
+
return hit === null ? `${field} must be written in plain words` : codeShapeMessage(field, hit);
|
|
16007
|
+
}
|
|
16008
|
+
});
|
|
16009
|
+
}
|
|
16010
|
+
var setIssueHeaderInputSchema = external_exports.strictObject({
|
|
16011
|
+
issue: issueRefSchema,
|
|
16012
|
+
summary: inPlainWords(external_exports.string().min(1, { message: "summary must not be empty" }).max(ISSUE_SUMMARY_MAX, {
|
|
16013
|
+
error: (iss) => `summary must be at most ${ISSUE_SUMMARY_MAX} characters \u2014 received ${String(iss.input).length}`
|
|
16014
|
+
}), "summary"),
|
|
16015
|
+
why: inPlainWords(external_exports.string().min(1, { message: "why must not be empty \u2014 say why this matters" }), "why"),
|
|
16016
|
+
affectedSurfaces: inPlainWords(external_exports.string().min(1, { message: "affected surfaces must not be empty" }), "affected surfaces")
|
|
16017
|
+
});
|
|
15843
16018
|
var setStatusInputSchema = external_exports.strictObject({
|
|
15844
16019
|
issue: issueRefSchema,
|
|
15845
16020
|
status: issueStatusSchema
|
|
@@ -16034,6 +16209,12 @@ var listIssuesFilterSchema = external_exports.strictObject({
|
|
|
16034
16209
|
// query string and lets anything else fail HERE, out loud, because a filter the
|
|
16035
16210
|
// server quietly dropped returns everything and reads like one that matched.
|
|
16036
16211
|
blocked: external_exports.boolean().optional(),
|
|
16212
|
+
// Header-less or drifted work (PIPE-373 AC-9): `true` is everything whose header is
|
|
16213
|
+
// missing or no longer describes the body it was written from, which is the list
|
|
16214
|
+
// somebody sits down to write headers from. Omitted means both. A real boolean for
|
|
16215
|
+
// the same reason `blocked` is one — a filter the server quietly dropped returns
|
|
16216
|
+
// everything and reads exactly like one that matched everything.
|
|
16217
|
+
needsHeader: external_exports.boolean().optional(),
|
|
16037
16218
|
// Pagination — server default 200, max 500 (§9); default applied server-side.
|
|
16038
16219
|
limit: external_exports.number().int().min(1, { message: "limit must be at least 1" }).max(500, { message: "limit must be at most 500" }).optional(),
|
|
16039
16220
|
offset: external_exports.number().int().nonnegative().optional()
|
|
@@ -16519,6 +16700,22 @@ var ApiClient = class _ApiClient {
|
|
|
16519
16700
|
addComment(ref, body) {
|
|
16520
16701
|
return this.request("POST", `/api/v1/issues/${enc(ref)}/comments`, { body: { body } });
|
|
16521
16702
|
}
|
|
16703
|
+
/**
|
|
16704
|
+
* Writes the structured header onto an issue that already exists (PIPE-373).
|
|
16705
|
+
*
|
|
16706
|
+
* WHOLE VALUE, all three fields required — a stale header is replaced outright,
|
|
16707
|
+
* so there is nothing to merge and nothing to pin. The server stamps a hash of
|
|
16708
|
+
* the body the three texts were derived from at the same time, which is what
|
|
16709
|
+
* later reads compare against to say whether the header still describes the
|
|
16710
|
+
* issue; a partial write would leave that hash vouching for words nobody
|
|
16711
|
+
* rewrote.
|
|
16712
|
+
*
|
|
16713
|
+
* `issue` is dropped from the body because the ref rides the path, the way it
|
|
16714
|
+
* does on every other whole-value write here.
|
|
16715
|
+
*/
|
|
16716
|
+
setIssueHeader(ref, input) {
|
|
16717
|
+
return this.request("PUT", `/api/v1/issues/${enc(ref)}/header`, { body: input });
|
|
16718
|
+
}
|
|
16522
16719
|
/** Records the issue as blocked with a required reason (PIPE-99). */
|
|
16523
16720
|
setBlocked(ref, reason) {
|
|
16524
16721
|
return this.request("PUT", `/api/v1/issues/${enc(ref)}/blocked`, {
|
|
@@ -17487,7 +17684,7 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
|
|
|
17487
17684
|
server2.registerTool(
|
|
17488
17685
|
"list_issues",
|
|
17489
17686
|
{
|
|
17490
|
-
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
|
|
17687
|
+
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.`,
|
|
17491
17688
|
inputSchema: listIssuesFilterSchema.shape
|
|
17492
17689
|
},
|
|
17493
17690
|
// The default lives HERE, not in the API. PIPE-R15 r4 rule 5 makes the default
|
|
@@ -17501,7 +17698,7 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
|
|
|
17501
17698
|
(args) => run(() => client2.listIssues(args.status === void 0 ? { ...args, status: [...OPEN_STATUSES] } : args), GUIDANCE_POINTER)
|
|
17502
17699
|
);
|
|
17503
17700
|
server2.registerTool("get_issue", {
|
|
17504
|
-
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.",
|
|
17701
|
+
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.",
|
|
17505
17702
|
inputSchema: { issue: issueRefSchema }
|
|
17506
17703
|
}, (args) => run(() => client2.getIssue(args.issue), GUIDANCE_POINTER));
|
|
17507
17704
|
server2.registerTool("create_issue", {
|
|
@@ -17527,6 +17724,10 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
|
|
|
17527
17724
|
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.",
|
|
17528
17725
|
inputSchema: updateIssueInputSchema.shape
|
|
17529
17726
|
}, ({ issue: issue2, ...fields }) => run(() => client2.updateIssue(issue2, fields)));
|
|
17727
|
+
server2.registerTool("set_issue_header", {
|
|
17728
|
+
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.",
|
|
17729
|
+
inputSchema: setIssueHeaderInputSchema.shape
|
|
17730
|
+
}, ({ issue: issue2, ...fields }) => run(() => client2.setIssueHeader(issue2, fields)));
|
|
17530
17731
|
server2.registerTool("set_status", {
|
|
17531
17732
|
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.",
|
|
17532
17733
|
inputSchema: setStatusInputSchema.shape
|
|
@@ -17735,7 +17936,13 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
|
|
|
17735
17936
|
title: external_exports.string().min(1),
|
|
17736
17937
|
type: issueTypeSchema,
|
|
17737
17938
|
status: issueStatusSchema.optional(),
|
|
17738
|
-
body: markdownSchema.optional()
|
|
17939
|
+
body: markdownSchema.optional(),
|
|
17940
|
+
// This tool CREATES an issue, so it is a door the header refusal has to cover
|
|
17941
|
+
// (PIPE-373 AC-1). The fields must be DECLARED here rather than left off: the
|
|
17942
|
+
// SDK rebuilds this raw shape as a non-strict object, so an undeclared key is
|
|
17943
|
+
// STRIPPED rather than refused — the call would succeed, the issue would be
|
|
17944
|
+
// created, and the author's three lines would be dropped with nothing said.
|
|
17945
|
+
...refusedIssueHeaderFields
|
|
17739
17946
|
}
|
|
17740
17947
|
}, ({ deliverable, ...fields }) => run(() => client2.promoteDeliverable(deliverable, fields)));
|
|
17741
17948
|
server2.registerTool("list_assets", {
|
|
@@ -17894,7 +18101,7 @@ function loadMcpConfig() {
|
|
|
17894
18101
|
}
|
|
17895
18102
|
|
|
17896
18103
|
// src/version.ts
|
|
17897
|
-
var VERSION = "0.
|
|
18104
|
+
var VERSION = "0.38.0";
|
|
17898
18105
|
|
|
17899
18106
|
// src/main.ts
|
|
17900
18107
|
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.0",
|
|
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",
|