pipebrain-mcp 0.40.0 → 0.41.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.
Files changed (3) hide show
  1. package/README.md +10 -6
  2. package/dist/main.js +25 -17
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # pipebrain-mcp
2
2
 
3
- The [Pipebrain](https://pipebrain.dev) MCP server: issues, requirements, content drafts
4
- and deliverables, assets, milestones, and metadata-only secrets, exposed to Claude Code
5
- and Claude Desktop over stdio.
3
+ The [Pipebrain](https://pipebrain.dev) MCP server: issues, requirements, decision records,
4
+ conventions, prompts, content drafts and deliverables, assets, milestones, metadata-only
5
+ secrets, and more of the platform's modules, exposed to Claude Code and Claude Desktop
6
+ over stdio.
6
7
 
7
8
  This package is the **server only**. The command line tool is a separate package,
8
9
  [`pipebrain-cli`](https://www.npmjs.com/package/pipebrain-cli), which provides `ppb`.
@@ -35,8 +36,11 @@ at each launch.
35
36
  | `PIPEBRAIN_TOKEN` | Your `pbt_…` API token (Settings → Organization → API tokens). |
36
37
  | `PIPEBRAIN_API_URL` | API base URL, e.g. `https://api.pipebrain.dev`. Required — there is no default, so a session can always tell which environment it is bound to. |
37
38
 
38
- Secret VALUES never cross this server: the secrets tools return metadata only, and there
39
- is deliberately no value-returning tool. Use `ppb secrets` (from `pipebrain-cli`) for the
40
- reveal plane.
39
+ No tool here RETURNS a secret value: the secrets tools answer with metadata only, a
40
+ response never echoes a value, and there is deliberately no value-returning tool — no get,
41
+ no reveal, no export. Use `ppb secrets` (from `pipebrain-cli`) for the reveal plane.
42
+ Values do travel INBOUND on `set_secret` and `rotate_secret`, so for anything sensitive
43
+ set it with `ppb secrets set` instead (stdin or `--from-file`): a value typed into a tool
44
+ call is in the chat transcript before the server can encrypt it.
41
45
 
42
46
  Sign in at [app.pipebrain.dev](https://app.pipebrain.dev).
package/dist/main.js CHANGED
@@ -365,7 +365,7 @@ import { join } from "node:path";
365
365
  // ../../libs/agent-shared/dist/agent-config-manifest.js
366
366
  var AGENT_CONFIG_ENTRIES = [
367
367
  { target: "agents/blind-spot-pass.md", sha256: "564751a751f7896e89c67e0d20788aff873e599766a3c30360073ba74e4c6099" },
368
- { target: "agents/code-verifier.md", sha256: "5a09f0f9d6b4c4d75068bfc5e314b3d1250de9328806e709d0c32d1369394124" },
368
+ { target: "agents/code-verifier.md", sha256: "c3f0a3b7397f0c903056971e71f2640a979a52e6efd18b624b3d29716d1e43e0" },
369
369
  { target: "agents/heavy-implementer.md", sha256: "7d3af78d4241aaa67c283db245dd4065af6e8f55efce54c2f8623c1f83879830" },
370
370
  { target: "agents/issue-header-writer.md", sha256: "8809b2913c71d021e8fa6c72fe70e2d1db8019b90ed93d85786f5236552c7da6" },
371
371
  { target: "agents/spec-reviewer.md", sha256: "a46dc637006d1f810081046c57edcd731406727810ccc5e0b1c7d7b9b98be5ef" },
@@ -382,10 +382,10 @@ var AGENT_CONFIG_ENTRIES = [
382
382
  { target: "rules/typescript.md", sha256: "c55eab0c243e38cdf903ab50b1e9d6d621516b893e602e1abff9a999a14e72d4" },
383
383
  { target: "skills/capture-correction/SKILL.md", sha256: "907ee8fc8e7ea15c8296acd23a816e1f9c6454edcf37a93f18af79b1c542fb75" },
384
384
  { target: "skills/pipebrain-header-pass/SKILL.md", sha256: "10545e3c1c5215b031e1c76281e65cd2ecf7420b3f07a4221824591a8263c5fc" },
385
- { target: "skills/pipebrain-secrets/SKILL.md", sha256: "1b873f0a12c1384fbb84d071c4f46554cbff22fc502223ad58acd29578912ca6" },
386
- { target: "skills/pipebrain-work-session/SKILL.md", sha256: "ed4a9046704b32a69d5146e4eebb2a729e39c92dbcae5cfd6c01fbcaf17caa47" },
385
+ { target: "skills/pipebrain-secrets/SKILL.md", sha256: "52c2136a3cca5612981d50b926195eae3e804911e1e9723dd5eba76e66b52d6f" },
386
+ { target: "skills/pipebrain-work-session/SKILL.md", sha256: "09d5741761b43fc13a12a04117edd8b194aac18974bfedb42dfc76c14030ba87" },
387
387
  { target: "skills/pipebrain-writing/SKILL.md", sha256: "9ab1a188215716907fbb54e03cfdef43bb378800713b000f498bff4ea75462f0" },
388
- { target: "skills/pipebrain/SKILL.md", sha256: "ee35514459d1b2d6de8cba4ad1162b2269adb8f8c2dd6cdccfa4774b8743fde5" },
388
+ { target: "skills/pipebrain/SKILL.md", sha256: "e53a1cb13d4e2e396c638d8f99055f4082edef030a40ffc8806b1f52f0824b57" },
389
389
  { target: "skills/requirements-altitude/SKILL.md", sha256: "60d1916da22a52138c046fcf4e5cd4cc420b7a5679a6652eb76573b7a69a1cf4" }
390
390
  ];
391
391
 
@@ -505,7 +505,7 @@ function apiBindingLabel(apiBaseUrl) {
505
505
  }
506
506
 
507
507
  // ../../libs/agent-shared/dist/doctrine.js
508
- var DOCTRINE = "# Pipebrain doctrine\n\nHow an agent works inside Pipebrain. These rules ship with the product and read the same in every organization, because each exists where disagreeing breaks something the product guarantees rather than merely changing house style.\n\nHow your organization *writes* is its own text, edited in Settings, and follows this document under a heading naming each scope.\n\n## Decisions are yours; escalate on consequence\n\nInside an approved requirement you choose the design, the libraries, the structure, and the sequencing. Do not ask, report, or seek ratification for the choice. Escalate on consequence, not uncertainty: a choice that changes what a user sees, costs money, or contradicts an approved requirement is filed as a question with the options you propose; everything else you decide and carry on. A technical choice worth keeping is a decision record, never a paragraph.\n\n## Every loose end has one destination, and none of them is prose\n\nAnything you notice that is not the work in front of you goes to exactly one of three places: a question, when the operator has to choose; a new issue at triage, when it is real work outside this item's scope; or nowhere. Nowhere is the default, and the right outcome whenever knowing the thing would change nothing anyone does. Never open a section called notes, caveats, observations, things to know, or next steps in a body or a comment. Filing is the report: an item recorded and then repeated in prose asks the operator to triage it twice.\n\n## A decision you need is filed as a question, never written into a body\n\nAn open decision belongs in a question on the issue, filed with `ask_questions`, carrying the options you propose. It does not belong in a body \u2014 not under a heading, and not in a field. A decision written into prose prompts nobody and records no answer, so the operator has no place to make it and a later session cannot tell whether it ever was. Retract one your own work has since settled, so nothing asks the operator to decide something nobody waits on.\n\n## Search before you create\n\nLook for the existing record before adding one. A duplicate splits one thing's history across two identifiers, and the split is permanent \u2014 nothing merges them afterward.\n\n## Link by key; never paste another record's text\n\nReference a requirement, a decision, or an issue by its key, with its title on first mention. Never paste its text into a body. The source changes and the copy does not, and the stale copy is what gets built.\n\n## Cross-cutting requirements arrive on their own\n\nThey bind every epic automatically and come back with a work-item read, at their latest approved revision. Never restate them in a body and never add them as explicit requirement links. An explicit link pins a document at an approved revision, which is a different act.\n\n## Conventions arrive in force; yours is a proposal\n\nApproved conventions arrive with the same work-item read, at their latest approved revision, and they bind work in every project in the organization, not this one alone. Neither restate one in a body nor add one as an explicit link: delivery is already automatic, so a copy is a second version waiting to drift.\n\nOne you create or amend over the agent surface records a proposal. It is not in force until a person approves it in a browser, and only an approved revision reaches any session \u2014 so in the interval, do not work from your own unapproved wording, because adopting a rule for the organization is a person's act, not yours.\n\nThe tier test is a statement's lifetime: one that stops being true when this project ends is a cross-cutting requirement, and one that would still be true on the next project is a convention. The moments worth capturing are an evaluator rejecting work, a criterion moving to failed, and a reason recorded on blocked work. File at moments like those and rarely otherwise: every approved convention is delivered on every read, and it spends a person's review attention before it gets there.\n\n## Verification modes follow what can be driven\n\nBefore assigning a mode, establish what this project can drive end to end: a harness, and the command that runs it. A criterion is `auto` when an evaluator can drive the input and read the result against the running system, including behavior shown in an interface wherever such a harness exists. What qualifies it is the evaluator's own run, never the existence of a test the implementer wrote.\n\nMark `manual` only when the verdict needs a person's judgment. A criterion you cannot describe as drivable is usually worded too vaguely: reword it against the observable rather than fall back to a person. A `manual` criterion carries instructions for whoever checks it by hand, written for someone who has not seen the code or the conversation that produced it.\n\nA manual criterion carrying no recorded artifact is the human gate working as designed rather than a coverage gap, so it is neither a finding nor weaker than one carrying evidence.\n\n## The bar for filing a bug\n\nFile a bug only when the observed behavior breaks an approved requirement or a defined acceptance criterion, and name which one before you file. Failing that, it is not a bug: code you find unclear, an edge case nobody specified, a performance number nobody set, and anything you would phrase as \"this could be better\" are opinions about the product, so file each as feedback or not at all. The bar exists because a queue holding both real defects and unshared taste has to be read entirely before any of it can be trusted.\n\n## A decision record is written once\n\nIt states what was decided and why, at the time it was decided, and is not edited as understanding moves. A superseding record replaces it, and the chain of the two is the account of how the thinking changed \u2014 which an edited record destroys. Read the existing records before re-opening a settled question.\n\n## A secret value never travels in a tool call\n\nNo tool here returns one, and that is deliberate rather than an omission to be fixed. Nor should you put one in: a value typed into a tool call is in the transcript before the server can encrypt it, so it is already disclosed and the only remedy left is rotation. Set values with the command-line tool, from a file or on standard input, and inject them at run time rather than reading them into context.\n\n## Setting an instruction replaces the whole body\n\nThere is no partial update. Writing an instruction stores exactly the body you supply and discards everything that was there, so a partial write destroys the rest of the text. The procedure is: read the live value, compose the complete replacement, write it, then read the live value again to confirm what landed.\n\nWhat you are reading now is a snapshot taken when this session started, and so are the tool descriptions. An edit reaches the next session, never this one, so reading the text back after a write shows the pre-edit value. An agent that reports a diff of what it just wrote has read the snapshot instead of the record.\n";
508
+ var DOCTRINE = "# Pipebrain doctrine\n\nHow an agent works inside Pipebrain. These rules ship with the product and read the same in every organization, because each exists where disagreeing breaks something the product guarantees rather than merely changing house style.\n\nHow your organization *writes* is its own text, edited in Settings, and follows this document under a heading naming each scope.\n\n## Decisions are yours; escalate on consequence\n\nInside an approved requirement you choose the design, the libraries, the structure, and the sequencing. Do not ask, report, or seek ratification for the choice. Escalate on consequence, not uncertainty: a choice that changes what a user sees, costs money, or contradicts an approved requirement is filed as a question with the options you propose; everything else you decide and carry on. A technical choice worth keeping is a decision record, never a paragraph.\n\n## Every loose end has one destination, and none of them is prose\n\nAnything you notice that is not the work in front of you goes to exactly one of three places: a question, when the operator has to choose; a new issue at triage, when it is real work outside this item's scope; or nowhere. Nowhere is the default, and the right outcome whenever knowing the thing would change nothing anyone does. Never open a section called notes, caveats, observations, things to know, or next steps in a body or a comment. Filing is the report: an item recorded and then repeated in prose asks the operator to triage it twice.\n\n## A decision you need is filed as a question, never written into a body\n\nAn open decision belongs in a question on the issue, filed with `ask_questions`, carrying the options you propose. It does not belong in a body \u2014 not under a heading, and not in a field. A decision written into prose prompts nobody and records no answer, so the operator has no place to make it and a later session cannot tell whether it ever was. Retract one your own work has since settled, so nothing asks the operator to decide something nobody waits on.\n\n## Search before you create\n\nLook for the existing record before adding one. A duplicate splits one thing's history across two identifiers, and the split is permanent \u2014 nothing merges them afterward.\n\n## Link by key; never paste another record's text\n\nReference a requirement, a decision, or an issue by its key, with its title on first mention. Never paste its text into a body. The source changes and the copy does not, and the stale copy is what gets built.\n\n## Cross-cutting requirements arrive on their own\n\nThey bind every epic automatically and come back with a work-item read, at their latest approved revision. Never restate them in a body and never add them as explicit requirement links. An explicit link pins a document at an approved revision, which is a different act.\n\n## Conventions arrive in force; yours is a proposal\n\nApproved conventions arrive with the same work-item read, at their latest approved revision, and they bind work in every project in the organization, not this one alone. Neither restate one in a body nor add one as an explicit link: delivery is already automatic, so a copy is a second version waiting to drift.\n\nOne you create or amend over the agent surface records a proposal. It is not in force until a person approves it in a browser, and only an approved revision reaches any session \u2014 so in the interval, do not work from your own unapproved wording, because adopting a rule for the organization is a person's act, not yours.\n\nThe tier test is a statement's lifetime: one that stops being true when this project ends is a cross-cutting requirement, and one that would still be true on the next project is a convention. The moments worth capturing are an evaluator rejecting work, a criterion moving to failed, and a reason recorded on blocked work. File at moments like those and rarely otherwise: every approved convention is delivered on every read, and it spends a person's review attention before it gets there.\n\n## Verification modes follow what can be driven\n\nBefore assigning a mode, establish what this project can drive end to end: a harness, and the command that runs it. A criterion is `auto` when an evaluator can drive the input and read the result against the running system, including behavior shown in an interface wherever such a harness exists. What qualifies it is the evaluator's own run, never the existence of a test the implementer wrote.\n\nMark `manual` only when the verdict needs a person's judgment. A criterion you cannot describe as drivable is usually worded too vaguely: reword it against the observable rather than fall back to a person. A `manual` criterion carries instructions for whoever checks it by hand, written for someone who has not seen the code or the conversation that produced it.\n\nA manual criterion carrying no recorded artifact is the human gate working as designed rather than a coverage gap, so it is neither a finding nor weaker than one carrying evidence.\n\n## The bar for filing a bug\n\nFile a bug only when the observed behavior breaks an approved requirement or a defined acceptance criterion, and name which one before you file. Failing that, it is not a bug: code you find unclear, an edge case nobody specified, a performance number nobody set, and anything you would phrase as \"this could be better\" are opinions about the product, so file each as feedback or not at all. The bar exists because a queue holding both real defects and unshared taste has to be read entirely before any of it can be trusted.\n\n## A decision record is written once\n\nIt states what was decided and why, at the time it was decided, and is not edited as understanding moves. A superseding record replaces it, and the chain of the two is the account of how the thinking changed \u2014 which an edited record destroys. Read the existing records before re-opening a settled question.\n\n## A secret value never travels in a tool call\n\nNo tool here returns one, and that is deliberate rather than an omission to be fixed. Nor should you put one in: a value typed into a tool call is in the transcript before the server can encrypt it, so it is already disclosed and the only remedy left is rotation. Set values with the command-line tool, from a file or on standard input, and inject them at run time rather than reading them into context.\n\n## Setting an instruction replaces the whole body\n\nThere is no partial update. Writing an instruction stores exactly the body you supply and discards everything that was there, so a partial write destroys the rest of the text. The procedure is: read the live value, compose the complete replacement, write it, then read the live value again to confirm what landed.\n\nWhat you are reading now \u2014 the composed guidance \u2014 is a snapshot taken when this session started, and so are the tool descriptions. An edit reaches the next session's guidance, never this one's, so this document re-read after a write still shows the pre-edit text. The stored rows are a different read: `list_org_instructions` returns the current saved values mid-session, which is what makes the confirmation read above possible. An agent that reports its write as absent has re-read the snapshot instead of the record.\n";
509
509
 
510
510
  // ../../libs/agent-shared/dist/resolve-agent-env.js
511
511
  function pick(env, name) {
@@ -16024,6 +16024,7 @@ var setStatusInputSchema = external_exports.strictObject({
16024
16024
  issue: issueRefSchema,
16025
16025
  status: issueStatusSchema
16026
16026
  });
16027
+ var WORK_CONTEXT_COMMENT_LIMIT = 20;
16027
16028
  var ISSUE_BODY_CHAR_LIMIT = 1e5;
16028
16029
  var ISSUE_COMMENT_LIMIT = 50;
16029
16030
  var issueReadWindowSchema = external_exports.strictObject({
@@ -17573,7 +17574,7 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
17573
17574
  );
17574
17575
  }
17575
17576
  registerStrict("list_projects", {
17576
- description: "List all projects with their key, environments, milestones (token, label, active), and open-issue counts \u2014 the project switcher and workflow entry point (\xA77).",
17577
+ description: "List all projects with their key, environments, milestones (token, label, active), deliverable targets (slug, label, active), and open-issue counts \u2014 the project switcher and workflow entry point (\xA77).",
17577
17578
  // Optional pagination (§9) — bounds mirror list_issues/list_requirements; the
17578
17579
  // server defaults limit to 200 (max 500) and passes limit/offset as query params.
17579
17580
  inputSchema: {
@@ -17593,7 +17594,7 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
17593
17594
  }
17594
17595
  }, (args) => run(() => client2.createProject(args)));
17595
17596
  registerStrict("list_waiting", {
17596
- description: "Every gate awaiting a PERSON across ALL projects in the token\u2019s org, longest wait first: a requirement revision awaiting approval, a question awaiting an answer, and work in `verify` holding at least one criterion not yet passed. Each entry names its project, the ref the action is taken on (`KEY-R{n}` for a revision, the ISSUE\u2019s `KEY-{n}` for a question or for work at verify), and `waitingSince` \u2014 the instant the wait began, not a duration. Nothing else is a gate: work in `triage` never appears, and neither does work in verify whose criteria have ALL passed. READ-ONLY, and there is no tool on this surface that clears any of the three \u2014 approving a revision, answering a question and marking a manual criterion are all decisions a person takes in the web app. Use it to see what your work is blocked on before asking, rather than to act on it."
17597
+ description: "Every gate awaiting a PERSON across ALL projects in the token\u2019s org, longest wait first: a requirement revision awaiting approval, a question awaiting an answer, and work in `verify` holding at least one criterion not yet passed. Each entry names its project, the ref the action is taken on (`KEY-R{n}` for a revision, the ISSUE\u2019s `KEY-{n}` for a question or for work at verify), and `waitingSince` \u2014 the instant the wait began, not a duration. Nothing else is a gate: work in `triage` never appears, and neither does work in verify whose criteria have ALL passed. THIS TOOL is READ-ONLY and clears nothing; what clears each gate differs. Answering a question has no tool on this surface, by design. A requirement revision is approved with `approve_requirement`. A verify gate clears as its criteria pass \u2014 an `auto` one through `set_criterion_state`, a `manual` one only ever by a person in the web app. Use it to see what your work is blocked on before asking, rather than to act on it."
17597
17598
  // NO arguments — not even an org. Org scope arrives from the token, exactly as
17598
17599
  // it does for `list_projects`, and an org parameter would open a cross-org read
17599
17600
  // path that has no precedent anywhere on this surface. The viewer narrowing the
@@ -17620,7 +17621,7 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
17620
17621
  // one description loaded into EVERY session by `anthropic/alwaysLoad`. The clauses
17621
17622
  // that went were the ones arguing for the rule rather than stating it; what a caller
17622
17623
  // needs to predict a refusal all stayed.
17623
- description: "Everything a session needs before it starts work, in ONE read \u2014 assembled by the API and returned verbatim, with the BINDING GUIDANCE appended after it: the product doctrine and this organization\u2019s own writing rules, in full. This is the one call that returns them; every other read only names it. CALL IT FIRST, before you write anything. Addressed by `issue` OR by `project`, never both and never neither: an issue names its own project, so the pair is refused rather than resolved for you (\xA77). Given an ISSUE: that issue\u2019s full detail \u2014 the same payload `get_issue` returns \u2014 PLUS the BODY TEXT of every requirement it links, at the revision the link is pinned at. That text is the round trip this removes: `get_issue` names which documents bind the work and at which revision, never what they SAY. Comments come CAPPED at the newest `commentLimit`, with `commentCount` \u2014 the issue\u2019s TRUE total \u2014 beside them; read the issue directly for the rest. Given a PROJECT: `available`, the work a session could START right now. It EXCLUDES finished and abandoned work (`done`, `killed`), work another person has claimed, and anything blocked \u2014 the status guard would refuse to start that. Your OWN claims stay, and so does unscheduled work carrying no milestone. `available` comes CAPPED at `availableLimit` rows, with `availableCount` \u2014 the project\u2019s TRUE total of startable work \u2014 beside it. The item form carries no `available`; the project form carries no item. BOTH forms also carry the project half: the release currently being worked (the active milestone) and the project\u2019s cross-cutting requirements WITH their text \u2014 they bind every epic automatically \u2014 and this organization\u2019s approved conventions in full, standing guidance that binds work in EVERY project here, not just this one. READ-ONLY \u2014 this claims nothing, moves nothing and writes nothing.",
17624
+ description: `Everything a session needs before it starts work, in ONE read \u2014 assembled by the API and returned verbatim, with the BINDING GUIDANCE appended after it: the product doctrine and this organization\u2019s own writing rules, in full. This is the one call that returns them; every other read only names it. CALL IT FIRST, before you write anything. Addressed by \`issue\` OR by \`project\`, never both and never neither: an issue names its own project, so the pair is refused rather than resolved for you (\xA77). Given an ISSUE: that issue\u2019s full detail \u2014 the same payload \`get_issue\` returns \u2014 PLUS the BODY TEXT of every requirement it links, at the revision the link is pinned at. That text is the round trip this removes: \`get_issue\` names which documents bind the work and at which revision, never what they SAY. Comments come CAPPED at the newest \`commentLimit\` (default ${WORK_CONTEXT_COMMENT_LIMIT}), with \`commentCount\` \u2014 the issue\u2019s TRUE total \u2014 beside them; read the issue directly for the rest. Given a PROJECT: \`available\`, the work a session could START right now. It EXCLUDES finished and abandoned work (\`done\`, \`killed\`), work another person has claimed, and anything blocked \u2014 the status guard would refuse to start that. Your OWN claims stay, and so does unscheduled work carrying no milestone. \`available\` comes CAPPED at \`availableLimit\` rows, with \`availableCount\` \u2014 the project\u2019s TRUE total of startable work \u2014 beside it. The item form carries no \`available\`; the project form carries no item. BOTH forms also carry the project half: the release currently being worked (the active milestone) and the project\u2019s cross-cutting requirements WITH their text \u2014 they bind every epic automatically \u2014 and this organization\u2019s approved conventions in full, standing guidance that binds work in EVERY project here, not just this one. READ-ONLY \u2014 this claims nothing, moves nothing and writes nothing.`,
17624
17625
  // The WHOLE domain schema rather than its `.shape`: the exactly-one-of rule is a
17625
17626
  // refinement on the OBJECT, and a raw shape drops it — the SDK validates against
17626
17627
  // what it is handed, so the shape alone would quietly accept both arguments and
@@ -17738,7 +17739,7 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
17738
17739
  (args) => run(() => client2.listIssues(args), GUIDANCE_POINTER)
17739
17740
  );
17740
17741
  registerStrict("get_issue", {
17741
- 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. Long records read in PARTS: \`bodyOffset\`/\`bodyLimit\` window the body (UTF-16 offsets; parts concatenate back exactly), \`commentOffset\`/\`commentLimit\` window the oldest-first comment list. Unpaged, the body is capped at ${ISSUE_BODY_CHAR_LIMIT} chars and comments at the newest ${ISSUE_COMMENT_LIMIT}; a response that left anything out says so in \`omitted\` \u2014 the true totals, the omitted comment count, and in \`next\` the exact arguments for the adjacent part (body walks forward, comments toward older) \u2014 follow \`next\` until null to retrieve everything. An issue under both caps reads whole in ONE call, no notice.`,
17742
+ 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. Long records read in PARTS: \`bodyOffset\`/\`bodyLimit\` window the body (UTF-16 offsets; parts concatenate back exactly), \`commentOffset\`/\`commentLimit\` window the oldest-first comment list. Unpaged, the body is capped at ${ISSUE_BODY_CHAR_LIMIT} chars and comments at the newest ${ISSUE_COMMENT_LIMIT}; a response that left anything out says so in \`omitted\` \u2014 the true totals, the omitted comment count, and in \`next\` the exact arguments for the adjacent part (body walks forward, comments toward older) \u2014 follow \`next\` until null to retrieve everything. An issue under both caps reads whole in ONE call, no notice.`,
17742
17743
  inputSchema: getIssueInputSchema.shape,
17743
17744
  // The same result-threshold annotation `get_work_context` declares, and the same
17744
17745
  // key spelled out for the same reason (an unknown `_meta` key is silently
@@ -17766,7 +17767,7 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
17766
17767
  })
17767
17768
  ), GUIDANCE_POINTER));
17768
17769
  registerStrict("create_issue", {
17769
- description: "Create an issue (epic/task/bug/feedback). Omitted status defaults to triage (still subject to \xA76 guards); omitted environment resolves explicit \u2192 parent\u2019s environment \u2192 project default (prod). Optional parent (self/cycle rejected), priority, milestone, requirement_links (pins each at its approved revision; a target with no approved revision is rejected), deliverable_links (content deliverables like DWB-C3/bsky \u2014 pins each at its current revision; the promotion path), and ordered {text, verification, howTo} criteria (\xA77). A `status` other than `triage` or `killed` REQUIRES `milestone` (PIPE-R10 rule 9): work past triage belongs to a release, and the create is refused (`milestone_required`) without one. A `manual` criterion must carry `howTo` \u2014 one or two sentences saying where to look, what to do, and what counts as passing; writing or changing one without it is refused (`how_to_required`), and so is supplying it on an `auto` criterion, which a machine checks. Work of any kind carries criteria \u2014 an epic, a task, a bug alike \u2014 with feedback the one exception, refused with `criteria_not_supported` because it is a report rather than agreed work; `promote_feedback` retypes it into work before anything is built. When a bug is filed against a defined acceptance criterion, pass that criterion\u2019s uuid as `criterionId` and the bug records which criterion it was filed against \u2014 the criterion then names what has already been reported (it arrives as `filedBugs` on every criteria read) beside the action that files again, so a duplicate is prevented by information rather than by removal. Filing a second bug against one criterion is a supported situation, not a mistake: one row can fail for two different reasons at once. Bugs only: any other type naming a criterion is refused (`criterion_requires_bug`). Writing guidance for this tool lives in the product doctrine, which `get_work_context` returns \u2014 see \u201CSearch before you create\u201D and \u201CThe bar for filing a bug\u201D.",
17770
+ description: "Create an issue (epic/task/bug/feedback). Omitted status defaults to triage (still subject to \xA76 guards); omitted environment resolves explicit \u2192 parent\u2019s environment \u2192 project default (prod). Optional parent (self/cycle rejected), priority, milestone, requirement_links (pins each at its approved revision; a target with no approved revision is rejected), deliverable_links (content deliverables like DWB-C3/bsky \u2014 pins each at its current revision; the promotion path), and ordered {text, verification, howTo} criteria (\xA77). A `status` other than `triage` or `killed` REQUIRES `milestone` (PIPE-R10 rule 9): work past triage belongs to a release, and the create is refused (`milestone_required`) without one. A `manual` criterion must carry `howTo` \u2014 one or two sentences saying where to look, what to do, and what counts as passing; writing or changing one without it is refused (`how_to_required`), and so is supplying it on an `auto` criterion, which a machine checks. Work of any kind carries criteria \u2014 an epic, a task, a bug alike \u2014 with feedback the one exception, refused with `criteria_not_supported` because it is a report rather than agreed work; `promote_feedback` retypes it into work before anything is built. When a bug is filed against a defined acceptance criterion, pass that criterion\u2019s uuid as `criterionId` and the bug records which criterion it was filed against \u2014 what has already been reported arrives as `filedBugs` on every criteria read. Filing a second bug against one criterion is supported: one row can fail for two different reasons at once. Bugs only: any other type naming a criterion is refused (`criterion_requires_bug`). Writing guidance for this tool lives in the product doctrine, which `get_work_context` returns \u2014 see \u201CSearch before you create\u201D and \u201CThe bar for filing a bug\u201D.",
17770
17771
  inputSchema: createIssueInputSchema.shape
17771
17772
  }, (args) => run(() => client2.createIssue(args)));
17772
17773
  registerStrict("promote_feedback", {
@@ -17777,7 +17778,7 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
17777
17778
  }
17778
17779
  }, ({ issue: issue2, ...fields }) => run(() => client2.promoteFeedback(issue2, fields)));
17779
17780
  registerStrict("open_review", {
17780
- description: "Open the COMPLETION REVIEW \u2014 the reading of an epic\u2019s WHOLE accumulated change as one body of work, by a third fresh session that neither built it nor evaluated it. It runs at one moment: once the epic\u2019s contract is green and nothing is left to check, and before anyone sets it `done`. It creates one ORDINARY `task` at `ready` carrying the review checklist as its body \u2014 no new record type. The epic form (`epic`) titles it `Completion review \u2014 <KEY-n> <epic title>`, files it as a CHILD of the epic and inherits the epic\u2019s milestone; the milestone form (`project` + `milestone`) titles it `Completion review \u2014 milestone <token>`, carries that token and has no parent \u2014 nothing here models a milestone as complete. Pass EXACTLY ONE of `epic` or `project` + `milestone`: both, or neither, is refused. One review at a time per scope \u2014 a second is refused (`review_already_open`) NAMING the standing one, so read, finish or kill that task instead. The scopes overlap ONE way, by design: the epic form\u2019s review also stands for that same project and milestone, so a later milestone-form open is refused; a milestone-form review blocks no epic-form open. A review opens only on a non-terminal epic \u2014 `review_target_not_epic` otherwise, `review_target_terminal` on a `done`/`killed` epic (a re-opened epic reviews again). NO NEW GATE was added to `done`, and the whole mechanism depends on that: file each finding as ordinary work \u2014 a CHILD OF THE EPIC, a sibling of this review \u2014 and the children clause already on `done` holds the epic open until every finding is done or killed. A finding filed UNDER the review gates nothing, because that gate reads one level deep. Killing the review, or any finding, is the recorded waiver: deliberately ungated, and written to the status history with who did it and through which token. An epic that never opened a review still closes exactly as it did before. Writing guidance for the checklist and for each finding lives in the product doctrine, which `get_work_context` returns.",
17781
+ description: "Open the COMPLETION REVIEW \u2014 the reading of an epic\u2019s WHOLE accumulated change as one body of work, by a third fresh session that neither built it nor evaluated it. It runs at one moment: once the epic\u2019s contract is green and before anyone sets it `done`. It creates one ORDINARY `task` at `ready` carrying the review checklist as its body. The epic form (`epic`) titles it `Completion review \u2014 <KEY-n> <epic title>`, files it as a CHILD of the epic and inherits the epic\u2019s milestone; the milestone form (`project` + `milestone`) titles it `Completion review \u2014 milestone <token>`, carries that token and has no parent. Pass EXACTLY ONE of `epic` or `project` + `milestone`: both, or neither, is refused. One review at a time per scope \u2014 a second is refused (`review_already_open`) NAMING the standing one, so read, finish or kill that task instead. The scopes overlap ONE way: the epic form\u2019s review also stands for that same project and milestone, so a later milestone-form open is refused; a milestone-form review blocks no epic-form open. A review opens only on a non-terminal epic \u2014 `review_target_not_epic` otherwise, `review_target_terminal` on a `done`/`killed` epic (a re-opened epic reviews again). NO NEW GATE was added to `done`: file each finding as ordinary work \u2014 a CHILD OF THE EPIC, a sibling of this review \u2014 and the children clause already on `done` holds the epic open until every finding is done or killed. A finding filed UNDER the review gates nothing, because that gate reads one level deep. Killing the review, or any finding, is the recorded waiver: deliberately ungated, and written to the status history with who did it and through which token. Writing guidance for the checklist and for each finding lives in the product doctrine, which `get_work_context` returns.",
17781
17782
  // The WHOLE domain schema rather than its `.shape`, for `get_work_context`'s
17782
17783
  // reason: the exactly-one-of rule is a refinement on the OBJECT, and a raw
17783
17784
  // shape drops it — the refusal naming the two forms would never reach the
@@ -17912,7 +17913,7 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
17912
17913
  inputSchema: createConventionInputSchema.shape
17913
17914
  }, (args) => run(() => client2.createConvention(args)));
17914
17915
  registerStrict("update_convention", {
17915
- description: "Amend a convention: `title` and/or `body` APPEND a new revision \u2014 nothing is ever edited in place \u2014 and optional `note` records why. Supplying neither is an empty edit (400); a note alone amends nothing. The same project-scope refusal applies as on create, over the amended text. THE AMENDMENT BINDS NOBODY UNTIL IT IS ITSELF APPROVED: until then agents keep receiving the approved revision, so amending is not a way to change what is being delivered \u2014 a signed-in person approving the new revision is.",
17916
+ description: "Amend a convention: `title` and/or `body` APPEND a new revision \u2014 nothing is ever edited in place \u2014 and optional `note` records why. Supplying neither is an empty edit (400); a note alone amends nothing. The same project-scope refusal applies as on create, over the amended text. THE AMENDMENT BINDS NOBODY UNTIL IT IS ITSELF APPROVED: until then agents keep receiving the approved revision, so amending is not a way to change what is being delivered \u2014 a signed-in person approving the new revision is. Writing guidance for this tool lives in the product doctrine, which `get_work_context` returns \u2014 see \u201CConventions arrive in force; yours is a proposal\u201D.",
17916
17917
  // Inline shape (updateConventionInputSchema is a refine, so it has no `.shape`);
17917
17918
  // the empty-edit rule is enforced by the API, not by this shape.
17918
17919
  inputSchema: {
@@ -17935,7 +17936,7 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
17935
17936
  inputSchema: createPromptInputSchema.shape
17936
17937
  }, (args) => run(() => client2.createPrompt(args)));
17937
17938
  registerStrict("update_prompt", {
17938
- description: "Amend a prompt: `title` and/or `body` APPEND a new revision \u2014 nothing is ever edited in place \u2014 and optional `note` records why. Amending is therefore safe rather than destructive: the earlier text stays readable, the revision chain is the record of how this prompt got to where it is, and the new wording answers immediately because there is nothing to approve. `project` moves the attachment and `project: null` DETACHES it, leaving a prompt the whole organization still has; either way NO revision is written, because the revision chain is the history of the TEXT and moving the attachment changes no text. Supplying none of `title`, `body` or `project` is an empty edit (400) \u2014 a note alone amends nothing.",
17939
+ description: "Amend a prompt: `title` and/or `body` APPEND a new revision \u2014 nothing is ever edited in place \u2014 and optional `note` records why. Amending is therefore safe rather than destructive: the earlier text stays readable, the revision chain is the record of how this prompt got to where it is, and the new wording answers immediately because there is nothing to approve. `project` moves the attachment and `project: null` DETACHES it, leaving a prompt the whole organization still has; either way NO revision is written, because the revision chain is the history of the TEXT and moving the attachment changes no text. Supplying none of `title`, `body` or `project` is an empty edit (400) \u2014 a note alone amends nothing. Writing guidance for this tool lives in the product doctrine, which `get_work_context` returns.",
17939
17940
  // Inline shape (updatePromptInputSchema is a refine, so it has no `.shape`),
17940
17941
  // mirroring that schema's fields — it is the wording this copy follows, `.min(1)`
17941
17942
  // bounds included, so a caller learns a blank body is refused from the advertised
@@ -17994,12 +17995,19 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
17994
17995
  inputSchema: recordPostInputSchema.shape
17995
17996
  }, ({ deliverable, ...fields }) => run(() => client2.recordPost(deliverable, fields)));
17996
17997
  registerStrict("promote_deliverable", {
17997
- description: "Promote a deliverable (DRAFT-KEY/platform, e.g. DWB-C3/bsky) to a NEW issue: creates the issue (`title` + `type` required; optional `status` defaults to triage; optional `body` seeds it \u2014 typically the current post text) AND a PROTECTED link pinning the deliverable\u2019s current revision. That link CANNOT be unlinked \u2014 the issue exists to ship this deliverable. (To link an EXISTING issue instead, use update_issue.deliverable_links, which stays removable.) Returns the created issue. Seeding the body with the deliverable\u2019s own text is the exception to linking rather than pasting \u2014 it is the point of this tool, and the protected link pins the revision that text came from. Writing guidance for this tool lives in the product doctrine, which `get_work_context` returns \u2014 see \u201CSearch before you create\u201D and \u201CLink by key; never paste another record\u2019s text\u201D.",
17998
+ description: "Promote a deliverable (DRAFT-KEY/platform, e.g. DWB-C3/bsky) to a NEW issue: creates the issue (`title` + `type` required; optional `status` defaults to triage, optional `milestone` schedules it, optional `body` seeds it \u2014 typically the current post text) AND a PROTECTED link pinning the deliverable\u2019s current revision. That link CANNOT be unlinked \u2014 the issue exists to ship this deliverable. (To link an EXISTING issue instead, use update_issue.deliverable_links, which stays removable.) Returns the created issue. A `status` other than `triage` or `killed` REQUIRES `milestone` (PIPE-R10 rule 9), exactly as in create_issue: work past triage belongs to a release, and the promotion is refused (`milestone_required`) without one. Seeding the body with the deliverable\u2019s own text is the exception to linking rather than pasting \u2014 it is the point of this tool, and the protected link pins the revision that text came from. Writing guidance for this tool lives in the product doctrine, which `get_work_context` returns \u2014 see \u201CSearch before you create\u201D and \u201CLink by key; never paste another record\u2019s text\u201D.",
17998
17999
  inputSchema: {
17999
18000
  deliverable: external_exports.string().min(1),
18000
18001
  title: external_exports.string().min(1),
18001
18002
  type: issueTypeSchema,
18002
18003
  status: issueStatusSchema.optional(),
18004
+ // Rule 9 (PIPE-227) reaches this door too, so the argument that satisfies it has
18005
+ // to be declared here. The API route, `promoteDeliverable` and the api-client
18006
+ // have all carried `milestone` since that item; only this shape omitted it, which
18007
+ // left the `status` beside it a trap — a caller naming any status past triage
18008
+ // could only be refused (`milestone_required`) with no argument to fix it.
18009
+ // Mirrors `promoteFeedbackInputSchema`'s field, message included.
18010
+ milestone: external_exports.string().min(1, { message: "milestone must not be empty \u2014 omit it to leave the issue unassigned" }).optional(),
18003
18011
  body: markdownSchema.optional(),
18004
18012
  // This tool CREATES an issue, so it is a door the header refusal has to cover
18005
18013
  // (PIPE-373 AC-1). Left undeclared, the strict wrap (PIPE-384) would refuse
@@ -18037,7 +18045,7 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
18037
18045
  inputSchema: unlinkAssetInputSchema.shape
18038
18046
  }, (args) => run(() => client2.unlinkAsset(args.linkId)));
18039
18047
  registerStrict("set_secret", {
18040
- description: "Create a secret or add a new version under a project + environment (metadata only \u2014 the response NEVER echoes the value). `name` is UPPER_SNAKE_CASE (e.g. API_KEY); `environment` is the environment slug (dev/prod); optional `description`. Writing guidance for that description lives in the product doctrine, which `get_work_context` returns.",
18048
+ description: "Create a secret or add a new version under a project + environment. THE VALUE CROSSES INBOUND ONLY \u2014 the response NEVER echoes it \u2014 but inbound is a direction, not a safe one: for a sensitive value prefer the CLI (`ppb secrets set` via stdin/`--from-file`), because a value typed here lands in the chat transcript before the server can encrypt it. `name` is UPPER_SNAKE_CASE (e.g. API_KEY); `environment` is the environment slug (dev/prod); optional `description`. Writing guidance for that description lives in the product doctrine, which `get_work_context` returns.",
18041
18049
  inputSchema: {
18042
18050
  project: projectKeySchema,
18043
18051
  environment: external_exports.string().min(1),
@@ -18062,7 +18070,7 @@ function createMcpServer(client2, apiBaseUrl, orgInstructions2 = /* @__PURE__ */
18062
18070
  }
18063
18071
  }, ({ project, environment, name }) => run(() => client2.describeSecret(project, name, { env: environment }), GUIDANCE_POINTER));
18064
18072
  registerStrict("rotate_secret", {
18065
- description: "Rotate an EXISTING secret: store a NEW version and advance the pointer (metadata only \u2014 the response NEVER echoes the value).",
18073
+ description: "Rotate an EXISTING secret: store a NEW version and advance the pointer. Same value rules as `set_secret`: the value crosses INBOUND ONLY and the response NEVER echoes it, and for a sensitive value prefer the CLI (`ppb secrets set` adds a new version from stdin/`--from-file`), because a value typed here lands in the chat transcript before the server can encrypt it.",
18066
18074
  inputSchema: {
18067
18075
  project: projectKeySchema,
18068
18076
  environment: external_exports.string().min(1),
@@ -18166,7 +18174,7 @@ function loadMcpConfig() {
18166
18174
  }
18167
18175
 
18168
18176
  // src/version.ts
18169
- var VERSION = "0.40.0";
18177
+ var VERSION = "0.41.0";
18170
18178
 
18171
18179
  // src/main.ts
18172
18180
  var config2 = loadMcpConfig();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pipebrain-mcp",
3
- "version": "0.40.0",
4
- "description": "Pipebrain MCP server for Claude Code and Claude Desktop — issues, requirements, content, assets, and metadata-only secrets over the Pipebrain API.",
3
+ "version": "0.41.0",
4
+ "description": "Pipebrain MCP server for Claude Code and Claude Desktop — issues, requirements, decision records, conventions, prompts, content, assets, metadata-only secrets, and more of the platform's modules over the Pipebrain API.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "main": "./dist/main.js",