@productbrain/mcp 0.0.1-beta.9616 → 0.0.1-beta.9621

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.
@@ -47,7 +47,7 @@ import {
47
47
 
48
48
  // src/server.ts
49
49
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
50
- import { z as z35 } from "zod/v3";
50
+ import { z as z36 } from "zod/v3";
51
51
 
52
52
  // src/lib/mcpGatewayClientMessages.ts
53
53
  function agentSessionNotActiveMessage(status2) {
@@ -12245,22 +12245,21 @@ function formatInviteSentence(reply, clean) {
12245
12245
  }
12246
12246
 
12247
12247
  // src/tools/role.ts
12248
- var ROLE_ACTIONS = ["accountable", "seat", "unseat", "invite"];
12248
+ var ROLE_ACTIONS = ["accountable", "seat", "unseat"];
12249
12249
  var roleSchema = z32.object({
12250
12250
  action: z32.enum(ROLE_ACTIONS).describe(
12251
- "'accountable': what this role owns (explicit), reports on (reporting), and \u2014 not yet computed \u2014 inherits via Team-Lead vacancy coverage. 'seat': put a person in this role. 'unseat': take this role from a person. 'invite': send a login link by email; a placeholder person (seated by name) is invited in place and keeps every role it holds."
12251
+ "'accountable': what this role owns (explicit), reports on (reporting), and \u2014 not yet computed \u2014 inherits via Team-Lead vacancy coverage. 'seat': put a person in this role. 'unseat': take this role from a person."
12252
12252
  ),
12253
- roleId: z32.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. Required for every action except 'invite'."),
12253
+ roleId: z32.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."),
12254
12254
  canonicalKey: z32.string().max(200).optional().describe("'accountable': narrow to one kind (e.g. objective, key_result, tension)."),
12255
12255
  limit: z32.number().int().positive().max(200).optional().describe("'accountable': max explicit-list rows per page (default and maximum 200)."),
12256
12256
  cursor: z32.number().optional().describe("'accountable': continue the explicit list from an earlier truncated page's nextCursor."),
12257
12257
  person: z32.string().max(200).optional().describe(
12258
- "'seat' / 'unseat': an existing person, by person id or exact email. For 'seat', give this or newPersonName, not both. 'invite': the placeholder's person id."
12258
+ "'seat' / 'unseat': an existing person, by person id or exact email. For 'seat', give this or newPersonName, not both."
12259
12259
  ),
12260
12260
  newPersonName: z32.string().max(120).optional().describe(
12261
- "'seat': the name of a person who has no login yet; a placeholder person is created once and reused for the same exact name. Refused when the name belongs to someone with an account. 'invite': the name of a new person to invite, instead of person."
12261
+ "'seat': the name of a person who has no login yet; a placeholder person is created once and reused for the same exact name. Refused when the name belongs to someone with an account."
12262
12262
  ),
12263
- email: z32.string().max(320).optional().describe("'invite': where the login link goes. Required for 'invite'."),
12264
12263
  teamId: z32.string().max(200).optional().describe(
12265
12264
  "'seat' / 'unseat': the team, by chain id or entry id. Only needed for Team Lead, which lives in every team; any other role is seated in its home team."
12266
12265
  )
@@ -12285,18 +12284,11 @@ var unseatVariant = z32.object({
12285
12284
  person: z32.string().max(200),
12286
12285
  teamId: z32.string().max(200).optional()
12287
12286
  });
12288
- var inviteVariant = z32.object({
12289
- action: z32.literal("invite"),
12290
- email: z32.string().max(320),
12291
- person: z32.string().max(200).optional(),
12292
- newPersonName: z32.string().max(120).optional()
12293
- });
12294
- var roleActionUnion = z32.discriminatedUnion("action", [accountableVariant, seatVariant, unseatVariant, inviteVariant]);
12287
+ var roleActionUnion = z32.discriminatedUnion("action", [accountableVariant, seatVariant, unseatVariant]);
12295
12288
  var ROLE_ACTION_SPECS = {
12296
12289
  accountable: { params: ["roleId", "canonicalKey", "limit", "cursor"], description: "roleId is required; canonicalKey/limit/cursor are optional." },
12297
12290
  seat: { params: ["roleId", "person", "newPersonName", "teamId"], description: "roleId is required, plus exactly one of person or newPersonName; teamId only for Team Lead." },
12298
- unseat: { params: ["roleId", "person", "teamId"], description: "roleId and person are required; teamId only for Team Lead." },
12299
- invite: { params: ["email", "person", "newPersonName"], description: "email is required, plus exactly one of person (a placeholder's id) or newPersonName." }
12291
+ unseat: { params: ["roleId", "person", "teamId"], description: "roleId and person are required; teamId only for Team Lead." }
12300
12292
  };
12301
12293
  function formatHolders(result2) {
12302
12294
  if (result2.holders.length === 0) return [` holders: none${result2.holdersTruncated ? " found, but the membership scan is incomplete" : ""}`];
@@ -12376,22 +12368,6 @@ function handleRoleSeat(roleId, person, newPersonName, teamId) {
12376
12368
  function handleRoleUnseat(roleId, person, teamId) {
12377
12369
  return runSeatAction("unseat", "organisation.unseatFromRole", { roleId, person, teamId });
12378
12370
  }
12379
- async function handleRoleInvite(email, person, newPersonName) {
12380
- const params = { email, person, newPersonName };
12381
- try {
12382
- requireWriteAccess();
12383
- const result2 = await kernelMutation("people.invite", {
12384
- email,
12385
- targetPersonId: person,
12386
- displayName: newPersonName,
12387
- sessionId: getAgentSessionId()
12388
- });
12389
- const message = formatInviteSentence(result2, sanitizeSingleLineField);
12390
- return successResult(message, message, result2);
12391
- } catch (err) {
12392
- return roleErrorResult(err, "invite", params);
12393
- }
12394
- }
12395
12371
  function roleErrorResult(err, action, params = {}) {
12396
12372
  const classified = classifyError(err);
12397
12373
  const definedParams = Object.fromEntries(Object.entries(params).filter(([, v]) => v !== void 0));
@@ -12409,18 +12385,18 @@ function registerRoleTools(server) {
12409
12385
  const roleHandlers = {
12410
12386
  accountable: (data) => handleRoleAccountable(data.roleId, data.canonicalKey, data.limit, data.cursor),
12411
12387
  seat: (data) => handleRoleSeat(data.roleId, data.person, data.newPersonName, data.teamId),
12412
- unseat: (data) => handleRoleUnseat(data.roleId, data.person, data.teamId),
12413
- invite: (data) => handleRoleInvite(data.email, data.person, data.newPersonName)
12388
+ unseat: (data) => handleRoleUnseat(data.roleId, data.person, data.teamId)
12414
12389
  };
12415
12390
  server.registerTool(
12416
12391
  "role",
12417
12392
  {
12418
12393
  title: "Role",
12419
- description: "Workspace roles \u2014 who is accountable for what, and who holds it. Four actions:\n\n- **accountable**: what this role owns (`explicit` \u2014 the canonical ownership row for objective/key_result/tension, data.ownerRoleId for domain), reports on (`reporting`, data.reporterRoleId \u2014 key results and tensions only, a separate fact from ownership), and \u2014 always `not-computed` today, never faked as empty \u2014 inherits via Team-Lead vacancy coverage. Also lists `holders`.\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.\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.\n- **invite**: send a login link to `email`. Give `person` (a placeholder's id, as `seat` printed it) to invite that person in place: they keep every role they hold. Or give `newPersonName` to invite someone new. A person who already has a login or an invite is refused. Needs a workspace owner or admin.",
12394
+ description: "Workspace roles \u2014 who is accountable for what, and who holds it. Three actions:\n\n- **accountable**: what this role owns (`explicit` \u2014 the canonical ownership row for objective/key_result/tension, data.ownerRoleId for domain), reports on (`reporting`, data.reporterRoleId \u2014 key results and tensions only, a separate fact from ownership), and \u2014 always `not-computed` today, never faked as empty \u2014 inherits via Team-Lead vacancy coverage. Also lists `holders`.\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.\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.",
12420
12395
  inputSchema: roleSchema,
12421
12396
  // The hints describe the whole tool, so they say the most it can do: unseat removes a role and can
12422
12397
  // delete a membership row (destructive), and a repeated seat still writes history and audit rows
12423
- // (not idempotent), even though accountable is a pure read (PR #1508 review).
12398
+ // (not idempotent), even though accountable is a pure read (PR #1508 review). Closed world: nothing here
12399
+ // reaches outside the workspace since invite moved to `people` (TASK-677, DEC-1981).
12424
12400
  annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true, openWorldHint: false }
12425
12401
  },
12426
12402
  thinWrapper(async (args) => {
@@ -12435,50 +12411,161 @@ function registerRoleTools(server) {
12435
12411
  );
12436
12412
  }
12437
12413
 
12438
- // src/tools/outcome.ts
12414
+ // src/tools/people.ts
12439
12415
  import { z as z33 } from "zod/v3";
12416
+ var PEOPLE_ACTIONS = ["invite", "remove"];
12417
+ var peopleSchema = z33.object({
12418
+ action: z33.enum(PEOPLE_ACTIONS).describe("'invite': send a login link by email. 'remove': take a person out of a team."),
12419
+ email: z33.string().max(320).optional().describe("'invite': where the login link goes. Required for 'invite'."),
12420
+ person: z33.string().max(320).optional().describe(
12421
+ "'invite': a placeholder's person id, instead of newPersonName. 'remove': the person, by person id or exact email."
12422
+ ),
12423
+ newPersonName: z33.string().max(120).optional().describe("'invite': the name of a new person to invite, instead of person."),
12424
+ teamId: z33.string().max(200).optional().describe("'remove': the team, by chain id or entry id. Required for 'remove'.")
12425
+ });
12426
+ var inviteVariant = z33.object({
12427
+ action: z33.literal("invite"),
12428
+ email: z33.string().max(320),
12429
+ person: z33.string().max(320).optional(),
12430
+ newPersonName: z33.string().max(120).optional()
12431
+ });
12432
+ var removeVariant = z33.object({
12433
+ action: z33.literal("remove"),
12434
+ person: z33.string().max(320),
12435
+ teamId: z33.string().max(200)
12436
+ });
12437
+ var peopleActionUnion = z33.discriminatedUnion("action", [inviteVariant, removeVariant]);
12438
+ var PEOPLE_ACTION_SPECS = {
12439
+ invite: { params: ["email", "person", "newPersonName"], description: "email is required, plus exactly one of person (a placeholder's id) or newPersonName." },
12440
+ remove: { params: ["person", "teamId"], description: "person and teamId are required." }
12441
+ };
12442
+ async function handlePeopleInvite(email, person, newPersonName) {
12443
+ const params = { email, person, newPersonName };
12444
+ try {
12445
+ requireWriteAccess();
12446
+ const result2 = await kernelMutation("people.invite", {
12447
+ email,
12448
+ targetPersonId: person,
12449
+ displayName: newPersonName,
12450
+ sessionId: getAgentSessionId()
12451
+ });
12452
+ const message = formatInviteSentence(result2, sanitizeSingleLineField);
12453
+ return successResult(message, message, result2);
12454
+ } catch (err) {
12455
+ return peopleErrorResult(err, "invite", params);
12456
+ }
12457
+ }
12458
+ async function handlePeopleRemove(person, teamId) {
12459
+ try {
12460
+ requireWriteAccess();
12461
+ const result2 = await kernelMutation("organisation.removeMembership", {
12462
+ targetPersonRef: person,
12463
+ teamRef: teamId,
12464
+ sessionId: getAgentSessionId()
12465
+ });
12466
+ if (result2.outcome === "refused") {
12467
+ const { reason, requiredAction } = result2.refusal;
12468
+ const message2 = `Refused: ${reason}${requiredAction ? ` ${requiredAction}` : ""}`;
12469
+ return failureResult(
12470
+ message2,
12471
+ "PERMISSION_DENIED",
12472
+ message2,
12473
+ requiredAction ?? "Ask the team's Team Lead to remove them.",
12474
+ void 0,
12475
+ result2
12476
+ );
12477
+ }
12478
+ const who = sanitizeSingleLineField(result2.person);
12479
+ const message = result2.removed ? `Removed ${who} from ${result2.team}.` : `${who} was not assigned to ${result2.team}; nothing changed.`;
12480
+ return successResult(message, message, result2);
12481
+ } catch (err) {
12482
+ return peopleErrorResult(err, "remove", { person, teamId });
12483
+ }
12484
+ }
12485
+ function peopleErrorResult(err, action, params) {
12486
+ const classified = classifyError(err);
12487
+ const definedParams = Object.fromEntries(Object.entries(params).filter(([, v]) => v !== void 0));
12488
+ const fallbackActions = retryIsFutile(classified.code) ? void 0 : [{ tool: "people", description: `Retry ${action}`, parameters: { action, ...definedParams } }];
12489
+ return failureResult(
12490
+ classified.message,
12491
+ classified.code,
12492
+ classified.message,
12493
+ classified.recovery ?? `Fix the input the message names and call action='${action}' again.`,
12494
+ classified.availableActions ?? fallbackActions,
12495
+ classified.diagnostics
12496
+ );
12497
+ }
12498
+ function registerPeopleTools(server) {
12499
+ const peopleHandlers = {
12500
+ invite: (data) => handlePeopleInvite(data.email, data.person, data.newPersonName),
12501
+ remove: (data) => handlePeopleRemove(data.person, data.teamId)
12502
+ };
12503
+ server.registerTool(
12504
+ "people",
12505
+ {
12506
+ title: "People",
12507
+ description: "People in the workspace. Two actions:\n\n- **invite**: send a login link to `email`. Give `person` (a placeholder's id, as `role action=seat` printed it) to invite that person in place: they keep every role they hold. Or give `newPersonName` to invite someone new. A person who already has a login or an invite is refused. Needs a workspace owner or admin.\n- **remove**: take `person` out of the team `teamId`; every role they hold there goes with it. To take one role, use `role action=unseat`. Needs the team's Team Lead.",
12508
+ inputSchema: peopleSchema,
12509
+ // TASK-677 (DEC-1981, DEC-74): invite emails an address outside the workspace (open world);
12510
+ // remove deletes a membership row and every role on it (destructive); neither is idempotent.
12511
+ annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true, openWorldHint: true }
12512
+ },
12513
+ thinWrapper(async (args) => {
12514
+ const parsed = parseOrFail(peopleSchema, args);
12515
+ if (!parsed.ok) return parsed.result;
12516
+ const { action } = parsed.data;
12517
+ return runWithToolContext(
12518
+ { tool: "people", action },
12519
+ () => dispatchDiscriminated("people", peopleActionUnion, parsed.data, PEOPLE_ACTION_SPECS, peopleHandlers)
12520
+ );
12521
+ })
12522
+ );
12523
+ }
12524
+
12525
+ // src/tools/outcome.ts
12526
+ import { z as z34 } from "zod/v3";
12440
12527
  var OUTCOME_ACTIONS = ["record", "list", "show"];
12441
12528
  var READING_KINDS = ["measurement", "estimate", "correction"];
12442
- var outcomeSchema = z33.object({
12443
- action: z33.enum(OUTCOME_ACTIONS).describe(
12529
+ var outcomeSchema = z34.object({
12530
+ action: z34.enum(OUTCOME_ACTIONS).describe(
12444
12531
  "'record': record a reading (evidence, not a verdict). 'list': list readings, newest first. 'show': the derived view \u2014 current, progress, pace, staleness, recorded completion."
12445
12532
  ),
12446
- entryId: z33.string().max(200).optional().describe("The key result (or other outcome-bearing) entry, by its chain id. Required for all three actions."),
12533
+ entryId: z34.string().max(200).optional().describe("The key result (or other outcome-bearing) entry, by its chain id. Required for all three actions."),
12447
12534
  // PR #858 review round 3 (Codex P2, PRRT_kwDORS4ils6gXHj9): a JSON number that overflows to
12448
12535
  // `Infinity` (e.g. `1e400`) parses fine in JS and passes a plain `z.number()`, but
12449
12536
  // `callGateway()` serializes it with `JSON.stringify`, which turns `Infinity` into `null` —
12450
12537
  // the backend's `v.number()` validator then rejects a call this tool's own schema accepted.
12451
12538
  // `.finite()` rejects it here, matching the explicit finite-value checks the CLI/other outcome
12452
12539
  // entry surfaces already use.
12453
- value: z33.number().finite().optional().describe("'record': the measured value. Required for 'record'."),
12454
- valueText: z33.string().max(500).optional().describe("'record': verbatim as-reported form when the source was not clean, e.g. '~62%'. Display only."),
12455
- asOf: z33.string().max(64).optional().describe("'record': when the world was in this state, as a parseable date (default: now). Back-dating a backfill is legitimate \u2014 it shows up as asOf earlier than recordedAt."),
12456
- kind: z33.enum(READING_KINDS).optional().describe("'record': measurement | estimate | correction (default: measurement)."),
12457
- correctsReadingId: z33.string().max(200).optional().describe("'record': required with kind='correction' \u2014 the reading id this replaces. Refused if that row is already superseded."),
12458
- recordedByRoleId: z33.string().max(200).optional().describe("'record': the accountable role (default: the entry's reporterRoleId, then its owning role)."),
12459
- note: z33.string().max(2e3).optional().describe("'record': free-text note recorded with the reading."),
12460
- source: z33.string().max(500).optional().describe("'record': where the number came from \u2014 a dashboard name, a query, a URL."),
12461
- limit: z33.number().int().positive().max(200).optional().describe("'list': max rows to return (default and maximum 200).")
12540
+ value: z34.number().finite().optional().describe("'record': the measured value. Required for 'record'."),
12541
+ valueText: z34.string().max(500).optional().describe("'record': verbatim as-reported form when the source was not clean, e.g. '~62%'. Display only."),
12542
+ asOf: z34.string().max(64).optional().describe("'record': when the world was in this state, as a parseable date (default: now). Back-dating a backfill is legitimate \u2014 it shows up as asOf earlier than recordedAt."),
12543
+ kind: z34.enum(READING_KINDS).optional().describe("'record': measurement | estimate | correction (default: measurement)."),
12544
+ correctsReadingId: z34.string().max(200).optional().describe("'record': required with kind='correction' \u2014 the reading id this replaces. Refused if that row is already superseded."),
12545
+ recordedByRoleId: z34.string().max(200).optional().describe("'record': the accountable role (default: the entry's reporterRoleId, then its owning role)."),
12546
+ note: z34.string().max(2e3).optional().describe("'record': free-text note recorded with the reading."),
12547
+ source: z34.string().max(500).optional().describe("'record': where the number came from \u2014 a dashboard name, a query, a URL."),
12548
+ limit: z34.number().int().positive().max(200).optional().describe("'list': max rows to return (default and maximum 200).")
12462
12549
  });
12463
- var recordVariant = z33.object({
12464
- action: z33.literal("record"),
12465
- entryId: z33.string().max(200),
12466
- value: z33.number().finite(),
12467
- valueText: z33.string().max(500).optional(),
12468
- asOf: z33.string().max(64).optional(),
12469
- kind: z33.enum(READING_KINDS).optional(),
12470
- correctsReadingId: z33.string().max(200).optional(),
12471
- recordedByRoleId: z33.string().max(200).optional(),
12472
- note: z33.string().max(2e3).optional(),
12473
- source: z33.string().max(500).optional()
12550
+ var recordVariant = z34.object({
12551
+ action: z34.literal("record"),
12552
+ entryId: z34.string().max(200),
12553
+ value: z34.number().finite(),
12554
+ valueText: z34.string().max(500).optional(),
12555
+ asOf: z34.string().max(64).optional(),
12556
+ kind: z34.enum(READING_KINDS).optional(),
12557
+ correctsReadingId: z34.string().max(200).optional(),
12558
+ recordedByRoleId: z34.string().max(200).optional(),
12559
+ note: z34.string().max(2e3).optional(),
12560
+ source: z34.string().max(500).optional()
12474
12561
  });
12475
- var listVariant2 = z33.object({
12476
- action: z33.literal("list"),
12477
- entryId: z33.string().max(200),
12478
- limit: z33.number().int().positive().max(200).optional()
12562
+ var listVariant2 = z34.object({
12563
+ action: z34.literal("list"),
12564
+ entryId: z34.string().max(200),
12565
+ limit: z34.number().int().positive().max(200).optional()
12479
12566
  });
12480
- var showVariant = z33.object({ action: z33.literal("show"), entryId: z33.string().max(200) });
12481
- var outcomeActionUnion = z33.discriminatedUnion("action", [recordVariant, listVariant2, showVariant]);
12567
+ var showVariant = z34.object({ action: z34.literal("show"), entryId: z34.string().max(200) });
12568
+ var outcomeActionUnion = z34.discriminatedUnion("action", [recordVariant, listVariant2, showVariant]);
12482
12569
  var OUTCOME_ACTION_SPECS = {
12483
12570
  record: {
12484
12571
  params: ["entryId", "value", "valueText", "asOf", "kind", "correctsReadingId", "recordedByRoleId", "note", "source"],
@@ -12762,7 +12849,7 @@ var ALLOWED_RELATION_TYPES = new Set(ALLOWED_RELATION_TYPES_LIST);
12762
12849
  var DATA_HANDLING_SECTION = "### How PB Handles Your Data\n- **The reply is the source of truth**: every reply carries `ok`; on failure it carries `code` and guidance. Read the reply, never an expectation.\n- **Empty-workspace safe**: the orientation resource and this cheatsheet render on a workspace with no entries and no collections.\n- **Workspace-agnostic**: PB is one product used by many teams. Nothing in these instructions is specific to your workspace; your own rules arrive as the rule lists that session start and orient serve you.\n- **Served, not installed**: these instructions, `productbrain://orientation` and `productbrain://agent-cheatsheet` are served over MCP.";
12763
12850
  var AGENT_CHEATSHEET = `# Product Brain \u2014 Agent Cheatsheet
12764
12851
 
12765
- ## Core Tools (Serves 15 compound tools, always)
12852
+ ## Core Tools (Serves 16 compound tools, always)
12766
12853
  | Tool | Purpose | Actions |
12767
12854
  |---|---|---|
12768
12855
  | \`orient\` | Workspace context, governance, active work packages, session start | \`action\`: start, task |
@@ -12778,7 +12865,8 @@ var AGENT_CHEATSHEET = `# Product Brain \u2014 Agent Cheatsheet
12778
12865
  | \`feedback\` | Submit product feedback (any key, no session required); \`list\` reads back your own workspace's feedback (no gate); vendor triage: queue / note / group / status | \`action\`: submit, list, queue, note, group, status |
12779
12866
  | \`shape\` | Review / disposition write-shape (single-concern split) advisories | \`action\`: list, show, agree, dismiss |
12780
12867
  | \`question\` | Routed questions \u2014 create, reassign, answer, force-close, list | \`action\`: create, assign, answer, force-close, list |
12781
- | \`role\` | What a role owns and who holds it; seat (by id, email or new name), unseat, or invite a placeholder in place | \`action\`: accountable, seat, unseat, invite |
12868
+ | \`role\` | What a role owns and who holds it; seat (by id, email or new name), unseat | \`action\`: accountable, seat, unseat |
12869
+ | \`people\` | Invite a person by email, a placeholder in place or someone new; take a person out of a team | \`action\`: invite, remove |
12782
12870
  | \`outcome\` | Record an outcome measurement and read the ones already recorded | \`action\`: record, list, show |
12783
12871
 
12784
12872
  This table lists actions only. See \`tools/list\` for each tool's input schema; a missing or invalid parameter is named in the call's own reply.
@@ -13247,7 +13335,7 @@ ${entry.labels.map((l) => `- ${l.name ?? l.slug}`).join("\n")}`);
13247
13335
  }
13248
13336
 
13249
13337
  // src/prompts/index.ts
13250
- import { z as z34 } from "zod/v3";
13338
+ import { z as z35 } from "zod/v3";
13251
13339
  async function buildReviewAgainstRulesPrompt(domain) {
13252
13340
  const entries = await kernelQuery("chain.listEntries", { collectionSlug: "rules" });
13253
13341
  const rules = entries.slice(0, 50);
@@ -13329,27 +13417,27 @@ function registerPrompts(server) {
13329
13417
  server.prompt(
13330
13418
  "review-against-rules",
13331
13419
  "Review code or a design decision against the rules, focused on a given area. Fetches the rules and asks you to do a structured compliance review.",
13332
- { domain: z34.string().describe("Business rule domain (e.g. 'Identity & Access', 'Governance & Decision-Making')") },
13420
+ { domain: z35.string().describe("Business rule domain (e.g. 'Identity & Access', 'Governance & Decision-Making')") },
13333
13421
  async ({ domain }) => buildReviewAgainstRulesPrompt(domain)
13334
13422
  );
13335
13423
  server.prompt(
13336
13424
  "name-check",
13337
13425
  "Check variable names, field names, or API names against the glossary for terminology alignment. Flags drift from canonical terms.",
13338
- { names: z34.string().describe("Comma-separated list of names to check (e.g. 'user_id, workspace_id, entry_status')") },
13426
+ { names: z35.string().describe("Comma-separated list of names to check (e.g. 'user_id, workspace_id, entry_status')") },
13339
13427
  async ({ names }) => buildNameCheckPrompt(names)
13340
13428
  );
13341
13429
  server.prompt(
13342
13430
  "draft-decision-record",
13343
13431
  "Draft a structured decision record from a description of what was decided. Includes context from recent decisions and relevant rules.",
13344
- { context: z34.string().describe("Description of the decision (e.g. 'We decided to keep one body field per entry because...')") },
13432
+ { context: z35.string().describe("Description of the decision (e.g. 'We decided to keep one body field per entry because...')") },
13345
13433
  async ({ context }) => buildDraftDecisionRecordPrompt(context)
13346
13434
  );
13347
13435
  server.prompt(
13348
13436
  "draft-rule-from-context",
13349
13437
  "Draft a new business rule from an observation or discovery made while coding. Fetches existing rules for the domain to ensure consistency.",
13350
13438
  {
13351
- observation: z34.string().describe("What you observed or discovered (e.g. 'A workspace can have more than one owner role')"),
13352
- domain: z34.string().describe("Which domain this rule belongs to (e.g. 'Governance & Decision-Making')")
13439
+ observation: z35.string().describe("What you observed or discovered (e.g. 'A workspace can have more than one owner role')"),
13440
+ domain: z35.string().describe("Which domain this rule belongs to (e.g. 'Governance & Decision-Making')")
13353
13441
  },
13354
13442
  async ({ observation, domain }) => buildDraftRuleFromContextPrompt(observation, domain)
13355
13443
  );
@@ -13447,7 +13535,7 @@ var INSTRUCTIONS = [
13447
13535
  "",
13448
13536
  "## Writing",
13449
13537
  "",
13450
- "Write actions (capture, entries update and commit, relations create, batch-create and delete) need an active session and a readwrite key.",
13538
+ "Write actions (capture, entries update and commit, relations create, batch-create and delete, people invite and remove, role seat and unseat) need an active session and a readwrite key.",
13451
13539
  "Pass the `sessionId` that `session action=start` returned on every write: the connection does not remember it.",
13452
13540
  "Use `capture` to create an entry and read its reply: an entry that came back accepted needs no `entries action=commit`.",
13453
13541
  "Link entries with `relations action=create` \u2014 a relation is written only when you ask, with exactly the verb you send.",
@@ -13458,7 +13546,7 @@ var INSTRUCTIONS = [
13458
13546
  "",
13459
13547
  "Every tool and action: `productbrain://agent-cheatsheet`."
13460
13548
  ].join("\n");
13461
- var sessionIdArg = z35.string().max(200).optional().describe(
13549
+ var sessionIdArg = z36.string().max(200).optional().describe(
13462
13550
  "The sessionId `session action=start` returned. Pass it on every write: the connection does not remember your session between calls."
13463
13551
  );
13464
13552
  function acceptSessionIdOnEveryTool(server) {
@@ -13514,6 +13602,7 @@ function createProductBrainServer() {
13514
13602
  registerShapeTools(server);
13515
13603
  registerQuestionTools(server);
13516
13604
  registerRoleTools(server);
13605
+ registerPeopleTools(server);
13517
13606
  registerOutcomeTools(server);
13518
13607
  registerResources(server);
13519
13608
  registerPrompts(server);
@@ -13550,4 +13639,4 @@ export {
13550
13639
  createProductBrainServer,
13551
13640
  initFeatureFlags
13552
13641
  };
13553
- //# sourceMappingURL=chunk-UPTW4MKK.js.map
13642
+ //# sourceMappingURL=chunk-U6BVSTXX.js.map