@productbrain/mcp 0.0.1-beta.7317 → 0.0.1-beta.7330

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.
@@ -41,7 +41,7 @@ import {
41
41
  trackSessionCaptureRate,
42
42
  trackWriteBackHintServed,
43
43
  trackZeroCaptureAuditFired
44
- } from "./chunk-2RHVPN24.js";
44
+ } from "./chunk-SU5XEDCF.js";
45
45
 
46
46
  // src/server.ts
47
47
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -6312,6 +6312,8 @@ var COLLECTIONS_ACTIONS = [
6312
6312
  "label-apply",
6313
6313
  "label-remove"
6314
6314
  ];
6315
+ var STATUS_CATEGORIES = ["draft", "current", "past", "discarded"];
6316
+ var statusListEntrySchema = z16.object({ label: z16.string().max(200), category: z16.enum(STATUS_CATEGORIES) });
6315
6317
  var qualityCriterionSchema = z16.object({
6316
6318
  field: z16.string().max(200).describe("Entry data field key this criterion applies to, e.g. 'description', 'owner'"),
6317
6319
  // WP-480 S1: `max_length` — must mirror the Convex rule union, or the tool rejects a
@@ -6329,17 +6331,12 @@ var fieldSchema = z16.object({
6329
6331
  displayHint: z16.enum(["hero", "badge", "meta", "section", "hidden", "inline-meta"]).optional().describe("V2 rendering hint: how the field should be displayed in Cortex"),
6330
6332
  zone: z16.enum(["header", "body", "meta"]).optional().describe("V2 layout zone: where the field appears in the entry view"),
6331
6333
  colorMap: z16.record(z16.string().max(50)).optional().describe("V2 value-to-semantic-color mapping, e.g. { critical: 'danger', low: 'success' }"),
6332
- // ENT-61
6333
6334
  accentSource: z16.boolean().optional().describe("When true, this field's colorMap value drives the card-level accent styling"),
6334
- // ENT-61
6335
6335
  iconMap: z16.record(z16.string(), z16.string().max(50)).optional().describe("Maps field values to icons (emoji/symbol), prepended to badge text"),
6336
6336
  helpText: z16.string().max(2e3).optional().describe("Help text shown in editors and describe output"),
6337
6337
  optionDescriptions: z16.record(z16.string().max(500)).optional().describe("Per-option guidance for select fields"),
6338
- // BET-136
6339
6338
  semanticRole: z16.enum(["problem", "appetite", "elements", "architecture", "done_when", "risks", "exclusions"]).optional().describe("Semantic role for schema-driven consumers \u2014 enables field-key-independent validation and rendering"),
6340
- // BET-196
6341
6339
  maxLength: z16.number().optional().describe("Maximum character length for field values. Three-tier resolution: explicit > displayHint > type fallback."),
6342
- // BET-196
6343
6340
  minLength: z16.number().optional().describe("Minimum character length for field values. Only explicit \u2014 no defaults.")
6344
6341
  });
6345
6342
  var collectionsSchema = z16.object({
@@ -6355,20 +6352,15 @@ var collectionsSchema = z16.object({
6355
6352
  fields: z16.array(fieldSchema).max(200).optional().describe("Field definitions for create, or replacement schema for update (replaces all fields)"),
6356
6353
  axis: z16.enum(["work", "none", "pending"]).optional().describe("REQUIRED for create: 'work' (needs stageLabelMap + validWorkflowStatuses), 'none', or 'pending'. For update: only settable on a non-system-stratum (custom) collection \u2014 the repair path for a legacy collection that predates the axis contract."),
6357
6354
  stageLabelMap: z16.record(z16.string(), z16.enum(["open", "in_progress", "done", "stopped", "tag"])).optional().describe("Required when axis is 'work': maps each valid workflowStatus to a canonical stage."),
6358
- // ENT-62
6359
6355
  defaultCanonicalKey: z16.string().max(200).optional().describe("The canonical_key entries in this collection default to (e.g. 'decision', 'insight'). Consumers read from collection doc; code map is fallback."),
6360
- // ENT-67
6361
6356
  defaultWorkflowStatus: z16.string().max(200).optional().describe("Default workflowStatus for new entries. Must be in validWorkflowStatuses when set (e.g. 'hypothesis' for insights)."),
6362
- // ENT-65
6363
6357
  validWorkflowStatuses: z16.array(z16.string().max(200)).max(50).optional().describe("The allowed workflowStatus values for entries in this collection. New entries are validated against this list. Empty array means no constraint."),
6364
6358
  // ENT-65, FEAT-200
6365
6359
  classificationCheck: z16.string().max(500).optional().describe("LLM decision-tree check for this collection (3\u2013500 chars). Guides the classifier in routing entries here."),
6366
- // ENT-65, FEAT-200
6367
6360
  classificationPriority: z16.number().optional().describe("Classifier priority (1\u20139, lower = higher priority). Used with classificationCheck to order the decision tree."),
6368
- // FEAT-301 Slice 2: quality gate criteria and usage guidance.
6369
- // FEAT-257
6370
6361
  qualityCriteria: z16.array(qualityCriterionSchema).max(50).optional().describe("Per-collection accept gate rules. 'required' rule hard-blocks accepts on empty fields; 'min_length'/'pattern' rules warn. Pass an empty array to clear all criteria."),
6371
6362
  usageGuidance: z16.string().max(2e4).optional().describe("Plain-text guidance shown to agents and users: when to use this collection, when not to, and what makes a good entry."),
6363
+ statusList: z16.array(statusListEntrySchema).max(50).optional().describe(`Ordered status vocabulary: {label, category}[] (draft/current/past/discarded); draft first, exactly one 'discarded' labeled "discarded", labels unique. Re-validated server-side.`),
6372
6364
  // For label-*
6373
6365
  color: z16.string().max(50).optional().describe("For label-create/label-update: hex color, e.g. '#ef4444'."),
6374
6366
  parentSlug: z16.string().max(200).optional().describe("For label-create: parent group slug for label hierarchy."),
@@ -6396,7 +6388,8 @@ var collectionsUpdateVariant = z16.object({
6396
6388
  classificationCheck: z16.string().max(500).optional(),
6397
6389
  classificationPriority: z16.number().optional(),
6398
6390
  qualityCriteria: z16.array(qualityCriterionSchema).optional(),
6399
- usageGuidance: z16.string().max(2e4).optional()
6391
+ usageGuidance: z16.string().max(2e4).optional(),
6392
+ statusList: z16.array(statusListEntrySchema).optional()
6400
6393
  });
6401
6394
  var collectionsAuditVariant = z16.object({ action: z16.literal("audit") });
6402
6395
  var collectionsExportVariant = z16.object({ action: z16.literal("export") });
@@ -6439,7 +6432,7 @@ var collectionsActionUnion = z16.discriminatedUnion("action", [
6439
6432
  var COLLECTIONS_ACTION_SPECS = {
6440
6433
  list: { params: [], description: "Browse all collections; no params." },
6441
6434
  describe: { params: ["slug"], description: "slug is required." },
6442
- update: { params: ["slug", "name", "description", "purpose", "icon", "navGroup", "fields", "axis", "stageLabelMap", "defaultCanonicalKey", "defaultWorkflowStatus", "validWorkflowStatuses", "classificationCheck", "classificationPriority", "qualityCriteria", "usageGuidance"], description: "slug is required; at least one other field must be provided. axis/stageLabelMap are only settable on a non-system-stratum collection (the repair path for a legacy custom collection missing its axis declaration)." },
6435
+ update: { params: ["slug", "name", "description", "purpose", "icon", "navGroup", "fields", "axis", "stageLabelMap", "defaultCanonicalKey", "defaultWorkflowStatus", "validWorkflowStatuses", "classificationCheck", "classificationPriority", "qualityCriteria", "usageGuidance", "statusList"], description: "slug is required; at least one other field must be provided. axis/stageLabelMap are only settable on a non-system-stratum collection (the repair path for a legacy custom collection missing its axis declaration)." },
6443
6436
  audit: { params: [], description: "Health report for all collections; no params." },
6444
6437
  export: { params: [], description: "Full system_collection_definitions export; no params. Admin only." },
6445
6438
  "label-list": { params: [], description: "List all labels; no params." },
@@ -6454,9 +6447,9 @@ function registerCollectionsTools(server) {
6454
6447
  list: () => handleList2(),
6455
6448
  describe: (data) => handleDescribe(data.slug),
6456
6449
  update: (data) => {
6457
- const hasChanges = data.name || data.description || data.purpose || data.icon || data.navGroup || data.fields || data.axis !== void 0 || data.stageLabelMap !== void 0 || data.defaultCanonicalKey !== void 0 || data.defaultWorkflowStatus !== void 0 || data.validWorkflowStatuses !== void 0 || data.classificationCheck !== void 0 || data.classificationPriority !== void 0 || data.qualityCriteria !== void 0 || data.usageGuidance !== void 0;
6450
+ const hasChanges = data.name || data.description || data.purpose || data.icon || data.navGroup || data.fields || data.axis !== void 0 || data.stageLabelMap !== void 0 || data.defaultCanonicalKey !== void 0 || data.defaultWorkflowStatus !== void 0 || data.validWorkflowStatuses !== void 0 || data.classificationCheck !== void 0 || data.classificationPriority !== void 0 || data.qualityCriteria !== void 0 || data.usageGuidance !== void 0 || data.statusList;
6458
6451
  if (!hasChanges) {
6459
- return Promise.resolve(validationResult("Provide at least one field to update (name, description, purpose, icon, navGroup, fields, axis, stageLabelMap, defaultCanonicalKey, defaultWorkflowStatus, validWorkflowStatuses, classificationCheck, classificationPriority, qualityCriteria, or usageGuidance)."));
6452
+ return Promise.resolve(validationResult("Provide at least one field to update (name, description, purpose, icon, navGroup, fields, axis, stageLabelMap, defaultCanonicalKey, defaultWorkflowStatus, validWorkflowStatuses, classificationCheck, classificationPriority, qualityCriteria, usageGuidance, or statusList)."));
6460
6453
  }
6461
6454
  return handleUpdate(
6462
6455
  data.slug,
@@ -6466,7 +6459,7 @@ function registerCollectionsTools(server) {
6466
6459
  data.icon,
6467
6460
  data.navGroup,
6468
6461
  data.fields,
6469
- { axis: data.axis, stageLabelMap: data.stageLabelMap, defaultCanonicalKey: data.defaultCanonicalKey, defaultWorkflowStatus: data.defaultWorkflowStatus, validWorkflowStatuses: data.validWorkflowStatuses, classificationCheck: data.classificationCheck, classificationPriority: data.classificationPriority, qualityCriteria: data.qualityCriteria, usageGuidance: data.usageGuidance }
6462
+ { axis: data.axis, stageLabelMap: data.stageLabelMap, defaultCanonicalKey: data.defaultCanonicalKey, defaultWorkflowStatus: data.defaultWorkflowStatus, validWorkflowStatuses: data.validWorkflowStatuses, classificationCheck: data.classificationCheck, classificationPriority: data.classificationPriority, qualityCriteria: data.qualityCriteria, usageGuidance: data.usageGuidance, statusList: data.statusList }
6470
6463
  );
6471
6464
  },
6472
6465
  audit: () => handleAudit(),
@@ -6482,7 +6475,7 @@ function registerCollectionsTools(server) {
6482
6475
  "collections",
6483
6476
  {
6484
6477
  title: "Collections",
6485
- description: "Manage knowledge collections and workspace labels. Eleven actions:\n\n- **list**: Browse all collections \u2014 glossary, business rules, tracking events, etc. Returns slug, name, description, and field schema. Use before capture to see what exists.\n- **describe**: Full documentation for a single collection. Returns field help text, option decision guides, usage guidance, examples, and cross-references. Use when you need to understand a collection's purpose, field semantics, or option values.\n- **update**: Update an existing collection's name, description, purpose, icon, navGroup, fields, or (non-system collections only) axis/stageLabelMap. Only provide the fields you want to change. Creating a NEW collection is not offered here \u2014 ask a platform admin to provision it, or enable the module ontology pack that owns the drawer.\n- **audit**: Health report for all workspace collections. Checks classification metadata, icon presence, displayHint coverage per field, and field schema gaps vs system definitions. Returns total collections, count with issues, and per-collection issue list.\n- **export**: Full system_collection_definitions export as JSON. Includes all classification metadata (thinkingLayer, classificationPriority, classificationCheck, timelineRole, governanceRole, governanceFunction, etc.). Admin only.\n- **label-list/label-create/label-update/label-delete/label-apply/label-remove**: manage workspace labels (absorbs the `labels` tool) \u2014 list all labels, create/update/delete a label, or apply/remove a label on an entry. Labels can be applied to any entry across any collection for cross-domain filtering.",
6478
+ description: "Manage knowledge collections and workspace labels. Eleven actions:\n\n- **list**: Browse all collections \u2014 glossary, business rules, tracking events, etc. Returns slug, name, description, and field schema. Use before capture to see what exists.\n- **describe**: Full documentation for a single collection. Returns field help text, option decision guides, usage guidance, examples, and cross-references. Use when you need to understand a collection's purpose, field semantics, or option values.\n- **update**: Update an existing collection's name, description, purpose, icon, navGroup, fields, statusList, or (non-system collections only) axis/stageLabelMap. Only provide the fields you want to change. Creating a NEW collection is not offered here \u2014 ask a platform admin to provision it, or enable the module ontology pack that owns the drawer.\n- **audit**: Health report for all workspace collections. Checks classification metadata, icon presence, displayHint coverage per field, and field schema gaps vs system definitions. Returns total collections, count with issues, and per-collection issue list.\n- **export**: Full system_collection_definitions export as JSON. Includes all classification metadata (thinkingLayer, classificationPriority, classificationCheck, timelineRole, governanceRole, governanceFunction, etc.). Admin only.\n- **label-list/label-create/label-update/label-delete/label-apply/label-remove**: manage workspace labels (absorbs the `labels` tool) \u2014 list all labels, create/update/delete a label, or apply/remove a label on an entry. Labels can be applied to any entry across any collection for cross-domain filtering.",
6486
6479
  inputSchema: collectionsSchema,
6487
6480
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
6488
6481
  },
@@ -6537,6 +6530,12 @@ Valid \`workflowStatus\` values for entries in this collection:
6537
6530
  ${col.validWorkflowStatuses.map((s) => `- ${s}`).join("\n")}`
6538
6531
  );
6539
6532
  }
6533
+ if (col.statusList && col.statusList.length > 0) {
6534
+ sections.push(`
6535
+ ## Status Vocabulary
6536
+
6537
+ ${col.statusList.map((s) => `- \`${s.label}\` \u2192 ${s.category}`).join("\n")}`);
6538
+ }
6540
6539
  if (col.fields.length > 0) {
6541
6540
  const fieldDocs = col.fields.map((f) => {
6542
6541
  const parts = [];
@@ -6586,6 +6585,8 @@ ${refList}`);
6586
6585
  usageGuidance: col.usageGuidance ?? null,
6587
6586
  // WP-480 S2: null = unconstrained by design (STD-34), not "unknown".
6588
6587
  validWorkflowStatuses: col.validWorkflowStatuses && col.validWorkflowStatuses.length > 0 ? col.validWorkflowStatuses : null,
6588
+ statusList: col.statusList && col.statusList.length > 0 ? col.statusList : null,
6589
+ // T2: same null-means-undeclared posture
6589
6590
  fieldCount: col.fields.length,
6590
6591
  fields: col.fields.map((f) => ({
6591
6592
  key: f.key,
@@ -6659,7 +6660,7 @@ async function handleUpdate(slug, name, description, purpose, icon, navGroup, fi
6659
6660
  invalidateCollectionCacheForScope();
6660
6661
  invalidateProfileCacheForScope();
6661
6662
  invalidateHubCacheForScope();
6662
- const changes = [name && "name", description && "description", purpose && "purpose", icon && "icon", navGroup && "navGroup", fields && "fields", meta.axis !== void 0 && "axis", meta.stageLabelMap !== void 0 && "stageLabelMap", meta.defaultCanonicalKey !== void 0 && "defaultCanonicalKey", meta.defaultWorkflowStatus !== void 0 && "defaultWorkflowStatus", meta.validWorkflowStatuses !== void 0 && "validWorkflowStatuses", meta.classificationCheck !== void 0 && "classificationCheck", meta.classificationPriority !== void 0 && "classificationPriority", meta.qualityCriteria !== void 0 && "qualityCriteria", meta.usageGuidance !== void 0 && "usageGuidance"].filter(Boolean).join(", ");
6663
+ const changes = [name && "name", description && "description", purpose && "purpose", icon && "icon", navGroup && "navGroup", fields && "fields", meta.axis !== void 0 && "axis", meta.stageLabelMap !== void 0 && "stageLabelMap", meta.defaultCanonicalKey !== void 0 && "defaultCanonicalKey", meta.defaultWorkflowStatus !== void 0 && "defaultWorkflowStatus", meta.validWorkflowStatuses !== void 0 && "validWorkflowStatuses", meta.classificationCheck !== void 0 && "classificationCheck", meta.classificationPriority !== void 0 && "classificationPriority", meta.qualityCriteria !== void 0 && "qualityCriteria", meta.usageGuidance !== void 0 && "usageGuidance", meta.statusList !== void 0 && "statusList"].filter(Boolean).join(", ");
6663
6664
  return {
6664
6665
  content: [{
6665
6666
  type: "text",
@@ -7910,10 +7911,6 @@ function formatNoticeLines(notices) {
7910
7911
  (n) => n.source === QUESTION_ACK_GATE_SOURCE_ID && n.payload ? formatQuestionAckGateBlock(n) : [`- ${safe(n.subject)} (${safe(n.change)})`]
7911
7912
  );
7912
7913
  }
7913
- function formatAdoptDiscoveryLine(display) {
7914
- if (!display) return [];
7915
- return [`- ${display} open question${display === "1" ? "" : "s"} you raised have no owner yet \u2014 use \`question action=adopt\` to route one.`];
7916
- }
7917
7914
 
7918
7915
  // src/lib/terminalStatuses.ts
7919
7916
  var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
@@ -12317,10 +12314,6 @@ async function handleStart2(data) {
12317
12314
  if (noticeLines.length > 0) {
12318
12315
  lines.push("", "Notices:", ...noticeLines);
12319
12316
  }
12320
- const adoptLines = formatAdoptDiscoveryLine(result2.adoptDiscoveryHint);
12321
- if (adoptLines.length > 0) {
12322
- lines.push("", ...adoptLines);
12323
- }
12324
12317
  const summary = `Session ${result2.sessionId} active. Workspace ${result2.workspaceName}. Write tools available after orient.`;
12325
12318
  return {
12326
12319
  content: [{ type: "text", text: lines.join("\n") }],
@@ -12333,8 +12326,7 @@ async function handleStart2(data) {
12333
12326
  roleName: result2.roleName,
12334
12327
  feedbackQueueNew: result2.feedbackQueueNew,
12335
12328
  feedbackOldestNewAt: result2.feedbackOldestNewAt,
12336
- notices: result2.notices ?? [],
12337
- adoptDiscoveryHint: result2.adoptDiscoveryHint
12329
+ notices: result2.notices ?? []
12338
12330
  // Finding #16: bare `parameters: {}` lets orient's schema default kick in
12339
12331
  // (action defaults to "task", which requires a `task` string) — the suggested
12340
12332
  // next-action call failed validation instead of running. `action: "start"` is
@@ -13570,45 +13562,34 @@ function registerShapeTools(server) {
13570
13562
 
13571
13563
  // src/tools/question.ts
13572
13564
  import { z as z32 } from "zod/v3";
13573
- var QUESTION_ACTIONS = ["create", "adopt", "assign", "snooze", "decline", "answer", "force-close", "list"];
13565
+ var QUESTION_ACTIONS = ["create", "assign", "answer", "force-close", "list"];
13574
13566
  var questionSchema = z32.object({
13575
13567
  action: z32.enum(QUESTION_ACTIONS).describe(
13576
- "'create': create a new routed question from text (domains come from `pb question --help` / this tool's own refusal \u2014 never hardcode a slug). 'adopt': adopt attaches routing to an existing tension or repairs a facet-less question \u2014 create is the birth path. Without adopt, a tension captured via `pb capture \"TEN: ...\"` stays invisible to routing forever. 'assign': confirm ('take it', omit toRole/toPerson) or reassign (supply toRole and/or toPerson) ownership. 'snooze': defer a routed/escalated question (max 2, then it escalates). 'decline': pass on a suggested question, or (as owner/lead) decline an owned one \u2014 reason required. 'answer': close the loop \u2014 creates the answering entry itself unless --entry is given. 'force-close': Team-Lead-only escape hatch \u2014 reason required. 'list': the ack-gate read model \u2014 what you owe (or, with mine:true, every question you asked, including closed ones)."
13568
+ "'create': create a new routed question from text \u2014 the only birth path now; every question is born escalated to the team lead. 'assign': reassign ownership to a role (toRole required \u2014 no bare self-confirm, no toPerson). 'answer': close the loop \u2014 creates the answering entry itself unless --entry is given. 'force-close': Team-Lead-only escape hatch \u2014 reason required. 'list': the ack-gate read model \u2014 what you owe (or, with mine:true, every question you asked, including closed ones)."
13577
13569
  ),
13578
13570
  entryId: z32.string().max(200).optional().describe("Target question's id (human TEN-nnn form or a raw entry id). Required for every action except 'create'/'list'."),
13579
13571
  text: z32.string().max(5e3).optional().describe("'create': the question text. 'answer': the answer text (mutually exclusive with answerEntryId, max 2000 chars)."),
13580
- domain: z32.string().max(200).optional().describe("'create'/'adopt': the domain slug (see this tool's refusal for the live list)."),
13581
- blocks: z32.array(z32.string().max(200)).max(50).optional().describe("'create'/'adopt': entry ids this question is blocking (repeatable)."),
13582
- toRole: z32.string().max(200).optional().describe("'assign': reassign to this role (human ROL-nnn form or a raw entry id)."),
13583
- toPerson: z32.string().max(200).optional().describe("'assign': reassign to this person (a raw person id)."),
13584
- reason: z32.string().max(2e3).optional().describe("'decline'/'force-close': required reason."),
13572
+ blocks: z32.array(z32.string().max(200)).max(50).optional().describe("'create': entry ids this question is blocking (repeatable)."),
13573
+ toRole: z32.string().max(200).optional().describe("'assign': reassign to this role (human ROL-nnn form or a raw entry id) \u2014 required."),
13574
+ reason: z32.string().max(2e3).optional().describe("'force-close': required reason."),
13585
13575
  answerEntryId: z32.string().max(200).optional().describe("'answer': point at an already-written entry instead of supplying text (mutually exclusive with text)."),
13586
13576
  mine: z32.boolean().optional().describe("'list': true for arm C (every question YOU asked, every status). Omit/false for the ack-gate view (what you owe).")
13587
13577
  });
13588
- var createVariant = z32.object({ action: z32.literal("create"), text: z32.string().min(1).max(5e3), domain: z32.string().max(200).optional(), blocks: z32.array(z32.string().max(200)).max(50).optional() });
13589
- var adoptVariant = z32.object({ action: z32.literal("adopt"), entryId: z32.string().max(200), domain: z32.string().max(200).optional(), blocks: z32.array(z32.string().max(200)).max(50).optional() });
13590
- var assignVariant = z32.object({ action: z32.literal("assign"), entryId: z32.string().max(200), toRole: z32.string().max(200).optional(), toPerson: z32.string().max(200).optional() });
13591
- var snoozeVariant = z32.object({ action: z32.literal("snooze"), entryId: z32.string().max(200) });
13592
- var declineVariant = z32.object({ action: z32.literal("decline"), entryId: z32.string().max(200), reason: z32.string().min(1).max(2e3) });
13578
+ var createVariant = z32.object({ action: z32.literal("create"), text: z32.string().min(1).max(5e3), blocks: z32.array(z32.string().max(200)).max(50).optional() });
13579
+ var assignVariant = z32.object({ action: z32.literal("assign"), entryId: z32.string().max(200), toRole: z32.string().max(200).min(1) });
13593
13580
  var answerVariant = z32.object({ action: z32.literal("answer"), entryId: z32.string().max(200), text: z32.string().max(2e3).optional(), answerEntryId: z32.string().max(200).optional() });
13594
13581
  var forceCloseVariant = z32.object({ action: z32.literal("force-close"), entryId: z32.string().max(200), reason: z32.string().min(1).max(2e3) });
13595
13582
  var listVariant = z32.object({ action: z32.literal("list"), mine: z32.boolean().optional() });
13596
13583
  var questionActionUnion = z32.discriminatedUnion("action", [
13597
13584
  createVariant,
13598
- adoptVariant,
13599
13585
  assignVariant,
13600
- snoozeVariant,
13601
- declineVariant,
13602
13586
  answerVariant,
13603
13587
  forceCloseVariant,
13604
13588
  listVariant
13605
13589
  ]);
13606
13590
  var QUESTION_ACTION_SPECS = {
13607
- create: { params: ["text", "domain", "blocks"], description: "text is required; domain/blocks are optional." },
13608
- adopt: { params: ["entryId", "domain", "blocks"], description: "entryId is required (the already-captured tension); domain/blocks are optional." },
13609
- assign: { params: ["entryId", "toRole", "toPerson"], description: "entryId is required; omit toRole/toPerson to self-confirm, or supply one/both to reassign." },
13610
- snooze: { params: ["entryId"], description: "entryId is required." },
13611
- decline: { params: ["entryId", "reason"], description: "entryId and reason are both required." },
13591
+ create: { params: ["text", "blocks"], description: "text is required; blocks is optional." },
13592
+ assign: { params: ["entryId", "toRole"], description: "entryId and toRole are both required \u2014 assign is reassign-to-a-role only." },
13612
13593
  answer: { params: ["entryId", "text", "answerEntryId"], description: "entryId is required; supply exactly one of text or answerEntryId." },
13613
13594
  "force-close": { params: ["entryId", "reason"], description: "entryId and reason are both required." },
13614
13595
  list: { params: ["mine"], description: "mine is optional \u2014 omit/false for the ack-gate view, true for every question you asked." }
@@ -13629,114 +13610,35 @@ async function displayName(id) {
13629
13610
  return void 0;
13630
13611
  }
13631
13612
  }
13632
- async function unresolvableRoleFallback(id) {
13633
- return await displayName(id) ?? `an unresolved role (\`${id}\` \u2014 reassign it)`;
13634
- }
13635
- function domainListSuffix(domainSlugs) {
13636
- if (!domainSlugs) return "";
13637
- return domainSlugs.length > 0 ? ` Domains in this workspace: ${domainSlugs.join(", ")}.` : " No domains exist in this workspace yet.";
13638
- }
13639
- async function renderCreateAdoptConfirmation(result2, isAdopt) {
13640
- const lead = isAdopt ? `${result2.entryId} is now a routed question,` : "Question created and";
13641
- let base;
13642
- if (result2.status === "unrouted") {
13643
- const roleName = result2.suggestedRoleId ? await unresolvableRoleFallback(result2.suggestedRoleId) : result2.suggestedRoleId;
13644
- base = isAdopt ? `${lead} suggested to ${roleName}.` : `${lead} suggested to ${roleName}. Confirm with: pb question assign ${result2.entryId}`;
13645
- } else {
13646
- const leadName = result2.ownerRoleId ? await unresolvableRoleFallback(result2.ownerRoleId) : result2.ownerRoleId;
13647
- base = isAdopt ? `${result2.entryId} is now a routed question. Nothing here says who owns it, so it has gone to ${leadName}.` : `Question created. Nothing here says who owns it, so it has gone to ${leadName}.`;
13648
- }
13613
+ async function renderCreateConfirmation(result2) {
13614
+ const leadName = result2.ownerRoleId ? await displayName(result2.ownerRoleId) ?? `an unresolved role (\`${result2.ownerRoleId}\` \u2014 reassign it)` : result2.ownerRoleId;
13615
+ const base = `Question created. Nothing here says who owns it, so it has gone to ${leadName}.`;
13649
13616
  const blocksHint = " If something is blocked on this, add blocks:[<entry-id>...].";
13650
13617
  const nudge = result2.nudge ? ` ${result2.nudge}` : "";
13651
- return `${base}${domainListSuffix(result2.domainSlugs)}${blocksHint}${nudge}`;
13618
+ return `${base}${blocksHint}${nudge}`;
13652
13619
  }
13653
- async function handleQuestionCreate(text, domain, blocks) {
13620
+ async function handleQuestionCreate(text, blocks) {
13654
13621
  requireWriteAccess();
13655
13622
  try {
13656
13623
  const resolvedBlocks = blocks ? await Promise.all(blocks.map((b) => resolveEntryRef(b, "Blocked entry"))) : void 0;
13657
- const result2 = await kernelMutation("question.create", { text, domainSlug: domain, blocks: resolvedBlocks, sessionId: getAgentSessionId() ?? void 0 });
13658
- const message = await renderCreateAdoptConfirmation(result2, false);
13659
- const next = result2.status === "unrouted" ? [{ tool: "question", description: "Confirm or reassign the suggestion", parameters: { action: "assign", entryId: result2.entryId } }] : void 0;
13660
- return successResult(message, message, result2, next);
13661
- } catch (err) {
13662
- return questionErrorResult(err, "create", { text, domain, blocks });
13663
- }
13664
- }
13665
- async function handleQuestionAdopt(entryId, domain, blocks) {
13666
- requireWriteAccess();
13667
- try {
13668
- const resolvedEntryId = await resolveEntryRef(entryId, "Entry");
13669
- const resolvedBlocks = blocks ? await Promise.all(blocks.map((b) => resolveEntryRef(b, "Blocked entry"))) : void 0;
13670
- const result2 = await kernelMutation("question.adopt", { entryId: resolvedEntryId, domainSlug: domain, blocks: resolvedBlocks, sessionId: getAgentSessionId() ?? void 0 });
13671
- const message = await renderCreateAdoptConfirmation(result2, true);
13624
+ const result2 = await kernelMutation("question.create", { text, blocks: resolvedBlocks, sessionId: getAgentSessionId() ?? void 0 });
13625
+ const message = await renderCreateConfirmation(result2);
13672
13626
  return successResult(message, message, result2);
13673
13627
  } catch (err) {
13674
- return questionErrorResult(err, "adopt", { entryId, domain, blocks });
13628
+ return questionErrorResult(err, "create", { text, blocks });
13675
13629
  }
13676
13630
  }
13677
- async function handleQuestionAssign(entryId, toRole, toPerson) {
13631
+ async function handleQuestionAssign(entryId, toRole) {
13678
13632
  requireWriteAccess();
13679
13633
  try {
13680
13634
  const resolvedEntryId = await resolveEntryRef(entryId, "Entry");
13681
- const resolvedToRoleId = toRole ? await resolveEntryRef(toRole, "Role") : void 0;
13682
- const result2 = await kernelMutation("question.assign", { entryId: resolvedEntryId, toRoleId: resolvedToRoleId, toPersonId: toPerson });
13683
- const isReassign = Boolean(toRole || toPerson);
13684
- let message;
13685
- if (isReassign) {
13686
- const who = toPerson ? stripControlChars(result2.ownerPersonName ?? "") || result2.ownerPersonId || "the target" : await displayName(result2.ownerRoleId) ?? result2.ownerRoleId ?? "the target";
13687
- message = `${entryId} reassigned to ${who}. They'll see it at their next session.`;
13688
- } else {
13689
- message = `${entryId} is yours now.`;
13690
- }
13691
- return successResult(message, message, result2);
13692
- } catch (err) {
13693
- return questionErrorResult(err, "assign", { entryId, toRole, toPerson });
13694
- }
13695
- }
13696
- async function handleQuestionSnooze(entryId) {
13697
- requireWriteAccess();
13698
- try {
13699
- const resolvedEntryId = await resolveEntryRef(entryId, "Entry");
13700
- const result2 = await kernelMutation("question.snooze", { entryId: resolvedEntryId });
13701
- let message;
13702
- if (result2.snoozeUntil) {
13703
- message = `Snoozed until ${new Date(result2.snoozeUntil).toISOString().slice(0, 10)}. ${result2.snoozeCount ?? "?"} of 2 snoozes used.`;
13704
- } else if (result2.status === "escalated") {
13705
- const leadName = await displayName(result2.ownerRoleId) ?? "the team lead";
13706
- message = `You have used both snoozes on this question \u2014 it has been escalated to ${leadName}.`;
13707
- } else {
13708
- message = "No snoozes left \u2014 answer it or force-close it.";
13709
- }
13710
- return successResult(message, message, result2);
13711
- } catch (err) {
13712
- return questionErrorResult(err, "snooze", { entryId });
13713
- }
13714
- }
13715
- async function handleQuestionDecline(entryId, reason) {
13716
- requireWriteAccess();
13717
- try {
13718
- const resolvedEntryId = await resolveEntryRef(entryId, "Entry");
13719
- const result2 = await kernelMutation("question.decline", { entryId: resolvedEntryId, reason });
13720
- let message;
13721
- if (result2.status === "processed") {
13722
- message = "Declined and closed.";
13723
- } else if (result2.isRoot === true) {
13724
- message = `Declined \u2014 but ${result2.dependentsCount ?? 0} item(s) still depend on it, so it stays with you. Answer it or force-close it.`;
13725
- } else if (result2.isRoot === false) {
13726
- const leadName = await displayName(result2.ownerRoleId) ?? result2.ownerRoleId;
13727
- message = `Declined and escalated to ${leadName}.`;
13728
- } else if (result2.suggestedRoleId) {
13729
- const roleName = await displayName(result2.suggestedRoleId) ?? result2.suggestedRoleId;
13730
- message = `Passed. ${entryId} is now suggested to ${roleName}.`;
13731
- } else if (result2.ownerRoleId) {
13732
- const leadName = await displayName(result2.ownerRoleId) ?? result2.ownerRoleId;
13733
- message = `Passed. Nobody else here owns this, so it has gone to ${leadName}.`;
13734
- } else {
13735
- message = "Declined.";
13736
- }
13635
+ const resolvedToRoleId = await resolveEntryRef(toRole, "Role");
13636
+ const result2 = await kernelMutation("question.assign", { entryId: resolvedEntryId, toRoleId: resolvedToRoleId });
13637
+ const who = await displayName(result2.ownerRoleId) ?? result2.ownerRoleId ?? "the target";
13638
+ const message = `${entryId} reassigned to ${who}. They'll see it at their next session.`;
13737
13639
  return successResult(message, message, result2);
13738
13640
  } catch (err) {
13739
- return questionErrorResult(err, "decline", { entryId, reason });
13641
+ return questionErrorResult(err, "assign", { entryId, toRole });
13740
13642
  }
13741
13643
  }
13742
13644
  async function handleQuestionAnswer(entryId, text, answerEntryId) {
@@ -13753,7 +13655,7 @@ async function handleQuestionAnswer(entryId, text, answerEntryId) {
13753
13655
  const result2 = await kernelMutation("question.answer", { entryId: resolvedEntryId, text, answerEntryId: resolvedAnswerEntryId });
13754
13656
  const askerName = (result2.askerName ? stripControlChars(result2.askerName) : "") || "The asker";
13755
13657
  const pendingNote = result2.answerPending ? " The answer is a draft awaiting accept." : "";
13756
- const message = result2.idempotent ? result2.processedVia === "declined" || result2.processedVia === "force_closed" ? `${entryId} is already processed (${result2.processedVia === "declined" ? "declined" : "force-closed"}) \u2014 no change.` : `${entryId} is already answered \u2014 no change.` : `Answered. ${askerName} will see the outcome at their next session (and in pb question list --mine).${pendingNote}`;
13658
+ const message = result2.idempotent ? result2.processedVia === "force_closed" ? `${entryId} is already processed (force-closed) \u2014 no change.` : result2.processedVia === "declined" ? `${entryId} is already processed (declined) \u2014 no change.` : `${entryId} is already answered \u2014 no change.` : `Answered. ${askerName} will see the outcome at their next session (and in pb question list --mine).${pendingNote}`;
13757
13659
  return successResult(message, message, result2);
13758
13660
  } catch (err) {
13759
13661
  return questionErrorResult(err, "answer", { entryId, text, answerEntryId });
@@ -13799,18 +13701,15 @@ function questionErrorResult(err, action, params = {}) {
13799
13701
  classified.message,
13800
13702
  classified.code,
13801
13703
  classified.message,
13802
- classified.recovery ?? "Use action='create' with no --domain, or `pb question --help` / this tool's description, to find valid domains and next steps.",
13704
+ classified.recovery ?? "Use action='create', or `pb question --help` / this tool's description, to find valid next steps.",
13803
13705
  classified.availableActions ?? fallbackActions,
13804
13706
  classified.diagnostics
13805
13707
  );
13806
13708
  }
13807
13709
  function registerQuestionTools(server) {
13808
13710
  const questionHandlers = {
13809
- create: (data) => handleQuestionCreate(data.text, data.domain, data.blocks),
13810
- adopt: (data) => handleQuestionAdopt(data.entryId, data.domain, data.blocks),
13811
- assign: (data) => handleQuestionAssign(data.entryId, data.toRole, data.toPerson),
13812
- snooze: (data) => handleQuestionSnooze(data.entryId),
13813
- decline: (data) => handleQuestionDecline(data.entryId, data.reason),
13711
+ create: (data) => handleQuestionCreate(data.text, data.blocks),
13712
+ assign: (data) => handleQuestionAssign(data.entryId, data.toRole),
13814
13713
  answer: (data) => handleQuestionAnswer(data.entryId, data.text, data.answerEntryId),
13815
13714
  "force-close": (data) => handleQuestionForceClose(data.entryId, data.reason),
13816
13715
  list: (data) => handleQuestionList(data.mine)
@@ -13819,7 +13718,7 @@ function registerQuestionTools(server) {
13819
13718
  "question",
13820
13719
  {
13821
13720
  title: "Question",
13822
- description: "Routed questions \u2014 a question with no owner routes itself to the owning role, the team lead when ambiguous, and up the team-lead chain until someone owns it. Eight actions:\n\n- **create**: create a new routed question from text. Domains are discovered by using this tool \u2014 an unresolvable `domain` refusal lists the workspace's real slugs; there is no fixed list to memorize.\n- **adopt**: adopt attaches routing to an existing tension or repairs a facet-less question \u2014 create is the birth path. Without adopt, a tension captured via `capture` with `collection: tensions` stays invisible to routing forever.\n- **assign**: confirm ('take it', no `toRole`/`toPerson`) or reassign (`toRole`/`toPerson`) ownership.\n- **snooze**: defer a routed/escalated question \u2014 max 2, then it escalates to the team lead.\n- **decline**: pass on a suggested question (never closes it), or \u2014 as the owner/team lead \u2014 decline an owned one (reason required; closes it, or escalates it, depending on dependents).\n- **answer**: close the loop. Creates the answering entry itself from `text`, or points at an existing one via `answerEntryId` \u2014 never both.\n- **force-close**: Team-Lead-only escape hatch (reason required) for a question that cannot otherwise be resolved.\n- **list**: the ack-gate read model \u2014 what you owe (`mine:false`, the default), or (`mine:true`) every question you asked, including closed ones, with its outcome.\n\n`blocks` (repeatable, on create/adopt) records what a question is holding up \u2014 pass entry ids it blocks.",
13721
+ description: "Routed questions \u2014 a question is born with no owner and escalates straight to the team lead; the team lead reassigns it to a role. Five actions:\n\n- **create**: create a new routed question from text. Always born escalated to the team lead \u2014 there is no domain-owner suggestion any more.\n- **assign**: reassign ownership to a role (`toRole` required \u2014 no self-confirm, no person-only reassignment).\n- **answer**: close the loop. Creates the answering entry itself from `text`, or points at an existing one via `answerEntryId` \u2014 never both.\n- **force-close**: Team-Lead-only escape hatch (reason required) for a question that cannot otherwise be resolved.\n- **list**: the ack-gate read model \u2014 what you owe (`mine:false`, the default), or (`mine:true`) every question you asked, including closed ones, with its outcome.\n\n`blocks` (repeatable, on create) records what a question is holding up \u2014 pass entry ids it blocks.",
13823
13722
  inputSchema: questionSchema,
13824
13723
  annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false }
13825
13724
  },
@@ -14189,7 +14088,7 @@ var AGENT_CHEATSHEET = `# Product Brain \u2014 Agent Cheatsheet
14189
14088
  | \`workspace\` | Check / whoami / status / self-test, usage, governance proposals | \`action\`: check, whoami, status, audit, self-test, usage, proposals-* |
14190
14089
  | \`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 |
14191
14090
  | \`shape\` | Review / disposition write-shape (single-concern split) advisories | \`action\`: list, show, agree, dismiss |
14192
- | \`question\` | Routed questions \u2014 create/adopt, own, snooze, decline, answer, force-close, list | \`action\`: create, adopt, assign, snooze, decline, answer, force-close, list |
14091
+ | \`question\` | Routed questions \u2014 create, reassign, answer, force-close, list | \`action\`: create, assign, answer, force-close, list |
14193
14092
  | \`role\` | What a role is accountable for \u2014 owns, reports on, and (not yet computed) inherits via Team-Lead vacancy coverage | \`action\`: accountable |
14194
14093
  | \`outcome\` | Record an outcome measurement and read the ones already recorded | \`action\`: record, list, show |
14195
14094
 
@@ -14989,4 +14888,4 @@ export {
14989
14888
  createProductBrainServer,
14990
14889
  initFeatureFlags
14991
14890
  };
14992
- //# sourceMappingURL=chunk-CPX4DW3V.js.map
14891
+ //# sourceMappingURL=chunk-CPOQXPOQ.js.map