@productbrain/mcp 0.0.1-beta.11701 → 0.0.1-beta.11709

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.
@@ -36,7 +36,7 @@ import {
36
36
  runWithToolContext,
37
37
  setSessionOriented,
38
38
  startAgentSession
39
- } from "./chunk-SFMHXRAR.js";
39
+ } from "./chunk-H2SLL2NX.js";
40
40
 
41
41
  // src/server.ts
42
42
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -2430,7 +2430,7 @@ var PRODUCT_NAME = "Product Brain";
2430
2430
  var PLATFORM_TERM_LABEL = "Platform";
2431
2431
  var PLATFORM_GLOSSARY_TERMS = [
2432
2432
  { name: "Team", definition: "A group of people and agents working toward one purpose; its roles own its decisions, rules and work." },
2433
- { name: "Team Lead", definition: "The role accountable for a team's purpose; it decides anything that belongs to a role nobody holds." },
2433
+ { name: "Team Lead", definition: "The role accountable for a team's purpose; it holds every role in its team with no one seated, covering it." },
2434
2434
  { name: "Role", definition: "A named set of responsibilities a person holds; whoever sits in it decides and accepts the entries it owns." },
2435
2435
  { name: "Tension", definition: "A gap someone notices between how things are and how they should be; linking one to an entry is how you say you disagree with it." }
2436
2436
  ];
@@ -2744,7 +2744,7 @@ async function handleGet(entryId) {
2744
2744
  if (e.sourceExcerpt) lines.push(`**Source excerpt:** ${e.sourceExcerpt}`);
2745
2745
  const parsedOwner = parseOwnerOutput(e.owner);
2746
2746
  if (parsedOwner) lines.push(formatOwnerLine2(parsedOwner));
2747
- for (const line of [e.pendingMove, e.qualityOverride, e.spineLine]) if (typeof line === "string") lines.push(sanitizeSingleLineField(line));
2747
+ for (const line of [e.heldLine, e.pendingMove, e.qualityOverride, e.spineLine]) if (typeof line === "string") lines.push(sanitizeSingleLineField(line));
2748
2748
  const restsOnChanged = Array.isArray(e.restsOnChanged) ? e.restsOnChanged : [];
2749
2749
  for (const item of restsOnChanged) lines.push(sanitizeSingleLineField(restsOnLine(item)));
2750
2750
  if (epistemic) lines.push(formatEpistemicLine(epistemic));
@@ -2793,7 +2793,9 @@ async function handleGet(entryId) {
2793
2793
  ...e.sourceExcerpt ? { sourceExcerpt: String(e.sourceExcerpt) } : {},
2794
2794
  ...parsedOwner ? { owner: parsedOwner } : {},
2795
2795
  // TASK-954, TASK-1027: the kernel's lines as served
2796
- ...Object.fromEntries(["pendingMove", "qualityOverride", "spineLine"].filter((k) => typeof e[k] === "string").map((k) => [k, e[k]])),
2796
+ ...Object.fromEntries(["heldLine", "pendingMove", "qualityOverride", "spineLine"].filter((k) => typeof e[k] === "string").map((k) => [k, e[k]])),
2797
+ ...e.coveredBy !== void 0 ? { coveredBy: e.coveredBy } : {},
2798
+ // DEC-2257: the Team Lead holding a role with no one seated
2797
2799
  ...restsOnChanged.length > 0 ? { restsOnChanged } : {},
2798
2800
  // TEN-2191: surface the WHY and its quality in the structured payload too.
2799
2801
  ...why.length > 0 ? { why } : {},
@@ -3644,7 +3646,8 @@ var ORGANISATION_VALIDATION_REFUSALS = /* @__PURE__ */ new Set([
3644
3646
  "move_not_needed",
3645
3647
  "no_pending_move",
3646
3648
  "why_required",
3647
- "role_unplaced"
3649
+ "role_unplaced",
3650
+ "role_work_target_invalid"
3648
3651
  ]);
3649
3652
 
3650
3653
  // src/tools/people.ts
@@ -4037,13 +4040,13 @@ function registerOutcomeTools(server) {
4037
4040
 
4038
4041
  // src/tools/role.ts
4039
4042
  import { z as z18 } from "zod/v3";
4040
- var ROLE_ACTIONS = ["seat", "unseat", "release_move", "accept_move", "refuse_move"];
4043
+ var ROLE_ACTIONS = ["seat", "unseat", "release_move", "accept_move", "refuse_move", "move_work"];
4041
4044
  var ROLE_READS_MOVED = {
4042
4045
  accountable: "role only writes. Read what a role owns and who holds it with the read-only context tool: context action=accountable roleId=..."
4043
4046
  };
4044
4047
  var roleSchema = z18.object({
4045
4048
  action: actionEnum(ROLE_ACTIONS, ROLE_READS_MOVED).describe(
4046
- "'seat': put a person in this role. 'unseat': take this role from a person. 'release_move' / 'accept_move' / 'refuse_move': move a role or team to a team with another Team Lead."
4049
+ "'seat': put a person in this role. 'unseat': take this role from a person. 'release_move' / 'accept_move' / 'refuse_move': move a role or team to a team with another Team Lead. 'move_work': move every open item a role owns to another role."
4047
4050
  ),
4048
4051
  roleId: z18.string().max(200).optional().describe("The role, by the human chain id `chain.getEntry` or `pb get` would print for it, or a raw entry id."),
4049
4052
  person: z18.string().max(200).optional().describe(
@@ -4059,7 +4062,10 @@ var roleSchema = z18.object({
4059
4062
  "'seat', Team Lead only: as the team's Team Lead, seat a second Team Lead who holds the seat on your authority until you unseat them."
4060
4063
  ),
4061
4064
  childId: z18.string().max(200).optional().describe("'release_move' / 'accept_move' / 'refuse_move': the role or team that moves, by chain id or entry id."),
4062
- why: z18.string().max(1e3).optional().describe("'refuse_move': why the move is refused; it becomes a tension for the old team's Team Lead.")
4065
+ why: z18.string().max(1e3).optional().describe("'refuse_move': why the move is refused; it becomes a tension for the old team's Team Lead."),
4066
+ fromRoleId: z18.string().max(200).optional().describe("'move_work': the role whose open work moves, by chain id or entry id."),
4067
+ toRoleId: z18.string().max(200).optional().describe("'move_work': the receiving role; without it, the nearest Team Lead of the old role's home team."),
4068
+ dryRun: z18.boolean().optional().describe("'move_work': return the receipt and change nothing.")
4063
4069
  });
4064
4070
  var seatVariant = z18.object({
4065
4071
  action: z18.literal("seat"),
@@ -4078,12 +4084,14 @@ var unseatVariant = z18.object({
4078
4084
  var releaseMoveVariant = z18.object({ action: z18.literal("release_move"), childId: z18.string().max(200), teamId: z18.string().max(200) });
4079
4085
  var acceptMoveVariant = z18.object({ action: z18.literal("accept_move"), childId: z18.string().max(200) });
4080
4086
  var refuseMoveVariant = z18.object({ action: z18.literal("refuse_move"), childId: z18.string().max(200), why: z18.string().max(1e3) });
4087
+ var moveWorkVariant = z18.object({ action: z18.literal("move_work"), fromRoleId: z18.string().max(200), toRoleId: z18.string().max(200).optional(), dryRun: z18.boolean().optional() });
4081
4088
  var roleActionUnion = z18.discriminatedUnion("action", [
4082
4089
  seatVariant,
4083
4090
  unseatVariant,
4084
4091
  releaseMoveVariant,
4085
4092
  acceptMoveVariant,
4086
- refuseMoveVariant
4093
+ refuseMoveVariant,
4094
+ moveWorkVariant
4087
4095
  ]);
4088
4096
  var ROLE_ACTION_SPECS = {
4089
4097
  seat: {
@@ -4093,18 +4101,12 @@ var ROLE_ACTION_SPECS = {
4093
4101
  unseat: { params: ["roleId", "person", "teamId"], description: "roleId and person are required; teamId only for Team Lead." },
4094
4102
  release_move: { params: ["childId", "teamId"], description: "childId and teamId (the team it moves to) are required." },
4095
4103
  accept_move: { params: ["childId"], description: "childId is required." },
4096
- refuse_move: { params: ["childId", "why"], description: "childId and why are required." }
4104
+ refuse_move: { params: ["childId", "why"], description: "childId and why are required." },
4105
+ move_work: { params: ["fromRoleId", "toRoleId", "dryRun"], description: "fromRoleId is required; toRoleId and dryRun are optional." }
4097
4106
  };
4098
- function formatHolders(result2) {
4099
- if (result2.holders.length === 0) return [` holders: none${result2.holdersTruncated ? " found, but the membership scan is incomplete" : ""}`];
4107
+ function formatHeld(result2) {
4100
4108
  return [
4101
- " holders:",
4102
- ...result2.holders.map((h) => {
4103
- const word = personStatusTag(h.status);
4104
- const tag = word ? ` (${word})` : "";
4105
- const team = h.teamName ? ` in ${sanitizeSingleLineField(h.teamName)}` : "";
4106
- return ` ${sanitizeSingleLineField(h.displayName)}${tag}${team}`;
4107
- }),
4109
+ ...result2.heldLine ? [` ${sanitizeSingleLineField(result2.heldLine)}`] : [],
4108
4110
  ...result2.holdersTruncated ? [" (membership scan cut at 500 rows, more holders may exist)"] : []
4109
4111
  ];
4110
4112
  }
@@ -4125,10 +4127,9 @@ function formatRoleAccountable(result2) {
4125
4127
  const explicitCursorHint = result2.nextCursor !== null ? `raise limit or continue with cursor ${result2.nextCursor}` : "raise limit";
4126
4128
  return [
4127
4129
  `${roleLabel2} \u2014 accountability:`,
4128
- ...formatHolders(result2),
4130
+ ...formatHeld(result2),
4129
4131
  ...formatSection("explicit (owns)", result2.explicit, result2.explicitTruncated, explicitCursorHint),
4130
- ...formatSection("reporting (reports on)", result2.reporting, result2.reportingTruncated),
4131
- ` inherited: not-computed \u2014 ${result2.inheritedNotComputedReason}`
4132
+ ...formatSection("reporting (reports on)", result2.reporting, result2.reportingTruncated)
4132
4133
  ].join("\n");
4133
4134
  }
4134
4135
  async function handleRoleAccountable(roleId, canonicalKey, limit, cursor) {
@@ -4199,6 +4200,27 @@ async function handleMove(action, params) {
4199
4200
  return roleErrorResult(err, action, params);
4200
4201
  }
4201
4202
  }
4203
+ async function handleMoveWork(params) {
4204
+ try {
4205
+ requireWriteAccess();
4206
+ const raw = await kernelMutation("organisation.setOwnership", {
4207
+ fromRoleRef: params.fromRoleId,
4208
+ roleRef: params.toRoleId,
4209
+ ...params.dryRun ? { dryRun: true } : {},
4210
+ sessionId: getAgentSessionId()
4211
+ });
4212
+ if (raw.outcome === "refused") {
4213
+ const recovery = authorityRecovery(raw.refusal) ?? raw.refusal.requiredAction ?? "Ask the Team Lead the refusal names.";
4214
+ const message2 = `Refused: ${sanitizeSingleLineField(raw.refusal.reason)} ${recovery}`;
4215
+ const code = ORGANISATION_VALIDATION_REFUSALS.has(raw.refusal.code) ? "VALIDATION_ERROR" : "PERMISSION_DENIED";
4216
+ return failureResult(message2, code, message2, recovery, void 0, raw);
4217
+ }
4218
+ const message = raw.text;
4219
+ return successResult(message, message, raw);
4220
+ } catch (err) {
4221
+ return roleErrorResult(err, "move_work", params);
4222
+ }
4223
+ }
4202
4224
  function roleErrorResult(err, action, params = {}) {
4203
4225
  const classified = classifyError(err);
4204
4226
  const definedParams = Object.fromEntries(Object.entries(params).filter(([, v]) => v !== void 0));
@@ -4218,13 +4240,14 @@ function registerRoleTools(server) {
4218
4240
  unseat: (data) => handleRoleUnseat(data.roleId, data.person, data.teamId),
4219
4241
  release_move: (data) => handleMove("release_move", data),
4220
4242
  accept_move: (data) => handleMove("accept_move", data),
4221
- refuse_move: (data) => handleMove("refuse_move", data)
4243
+ refuse_move: (data) => handleMove("refuse_move", data),
4244
+ move_work: (data) => handleMoveWork(data)
4222
4245
  };
4223
4246
  server.registerTool(
4224
4247
  "role",
4225
4248
  {
4226
4249
  title: "Role",
4227
- description: "Workspace roles \u2014 who holds a role and where it sits. Five actions:\n\n- **seat**: put a person in this role, by person id or email, or by `newPersonName` for someone with no login yet (a placeholder person, created once per exact name). The role's home team is used; name `teamId` only for Team Lead. A held role refuses a second holder and names the holder; the person keeps their other roles in the team. Needs the team's Team Lead. With `onMyAuthority`, the team's Team Lead seats a second Team Lead on their own authority; unseating them revokes it.\n- **unseat**: take this role from a person; their other roles in the team stay. The last active Team Lead of a team is refused. Invite a person, or take them out of a team, with the `people` tool.\n- **release_move** / **accept_move** / **refuse_move**: move a role or team (`childId`) to a team whose Team Lead is another seat. Acting as Team Lead of its current team, release it toward `teamId`; acting as Team Lead of that team, accept it, or refuse it with `why` (it stays, and a tension goes to its current team's Team Lead).\n\nRead what a role owns with `context action=accountable`.",
4250
+ description: "Workspace roles \u2014 who holds a role, where it sits and what it owns. Six actions:\n\n- **seat**: put a person in this role, by person id or email, or by `newPersonName` for someone with no login yet (a placeholder person, created once per exact name). The role's home team is used; name `teamId` only for Team Lead. A held role refuses a second holder and names the holder; the person keeps their other roles in the team. Needs the team's Team Lead. With `onMyAuthority`, the team's Team Lead seats a second Team Lead on their own authority; unseating them revokes it.\n- **unseat**: take this role from a person; their other roles in the team stay. The last active Team Lead of a team is refused. Invite a person, or take them out of a team, with the `people` tool.\n- **release_move** / **accept_move** / **refuse_move**: move a role or team (`childId`) to a team whose Team Lead is another seat. Acting as Team Lead of its current team, release it toward `teamId`; acting as Team Lead of that team, accept it, or refuse it with `why` (it stays, and a tension goes to its current team's Team Lead).\n- **move_work**: move every open item `fromRoleId` owns (drafts, current entries, open questions) to `toRoleId`, each checked as a one-entry ownership change; an item the receiver may not own goes to the nearest Team Lead of the old role's home team, and past items stay as history. Each call moves a capped number and says how many remain; call again to finish. `dryRun` returns the receipt and changes nothing.\n\nRead what a role owns with `context action=accountable`.",
4228
4251
  inputSchema: roleSchema,
4229
4252
  // The hints describe the whole tool, so they say the most it can do: unseat removes a role and can
4230
4253
  // delete a membership row (destructive), and a repeated seat still writes history and audit rows
@@ -11848,4 +11871,4 @@ export {
11848
11871
  SERVER_VERSION,
11849
11872
  createProductBrainServer
11850
11873
  };
11851
- //# sourceMappingURL=chunk-CMRO7CUV.js.map
11874
+ //# sourceMappingURL=chunk-VW2VE3H6.js.map