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

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 z36 } from "zod/v3";
50
+ import { z as z35 } from "zod/v3";
51
51
 
52
52
  // src/lib/mcpGatewayClientMessages.ts
53
53
  function agentSessionNotActiveMessage(status2) {
@@ -454,7 +454,8 @@ var TOOL_NAME_MIGRATIONS = /* @__PURE__ */ new Map([
454
454
  // from executable replacements to NOTICE rows, same shape as `chain-version`/`chain-branch`
455
455
  // below, so the coach never points an agent at a dead action.
456
456
  ["skills", "closed \u2014 the setup-assets system this tool loaded skill bodies from has been retired. There is no supported way to load a skill body by entry id through MCP."],
457
- ["start_pb", 'orient action="start"'],
457
+ ["start_pb", 'session action="start"'],
458
+ // TASK-670: orient action=start is deleted too; session start is the one way in
458
459
  ["record_activation", "closed \u2014 the setup-assets activation telemetry this tool wrote has been retired. There is no supported way to record an activation receipt through MCP."],
459
460
  ["get-usage-summary", 'workspace action="usage"'],
460
461
  // WP-776 S2a (DEC-1771): the `workspace` tool's proposals-list/proposals-respond/proposals-count
@@ -1492,7 +1493,7 @@ var captureCompoundSchema = z4.object({
1492
1493
  action: z4.enum(CAPTURE_ACTIONS).optional().default("capture").describe("Create a single knowledge entry."),
1493
1494
  collection: z4.string().max(200).optional().describe("Collection slug, e.g. 'tensions', 'rules', 'glossary', 'decisions'. Optional \u2014 classifier auto-routes when omitted."),
1494
1495
  name: z4.string().max(ENTRY_NAME_MAX_CHARS).optional().describe("Display name \u2014 required."),
1495
- description: z4.string().max(ENTRY_BODY_MAX_CHARS).optional().describe("Full context \u2014 required."),
1496
+ description: z4.string().max(ENTRY_BODY_MAX_CHARS).optional().describe("Full context, in Markdown \u2014 required."),
1496
1497
  context: z4.string().max(5e3).optional().describe("Optional additional context."),
1497
1498
  canonicalKey: z4.string().max(200).optional().describe("Semantic type (e.g. 'decision', 'tension', 'vision'). Auto-assigned from collection if omitted."),
1498
1499
  data: z4.record(z4.unknown()).optional().describe("Explicit field values; user-provided wins over inferred."),
@@ -1535,7 +1536,7 @@ All other collections get an ENT-{random} ID and sensible defaults.
1535
1536
 
1536
1537
  **Explicit data:** When you know the schema, pass \`data: { field: value }\` to set fields directly. Top-level \`name\`/\`description\` win for those fields; \`data\` wins over inference for the rest. Governance entries also require a top-level \`why\`.
1537
1538
 
1538
- **Compound capture:** Pass \`links\` to create relations in the same call; a capture writes no other relations. Entries are always created as \`draft\` \u2014 use \`entries action=commit\` to promote when ready.`,
1539
+ **Compound capture:** Pass \`links\` to create relations in the same call; a capture writes no other relations. A capture lands as a \`draft\` to accept with \`entries action=commit\`; a question is born open. The reply names possible duplicates; only an exact name in the collection is refused.`,
1539
1540
  inputSchema: captureCompoundSchema,
1540
1541
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
1541
1542
  },
@@ -5936,7 +5937,7 @@ async function handleExport() {
5936
5937
  }
5937
5938
 
5938
5939
  // src/tools/orient.ts
5939
- import { z as z20 } from "zod/v3";
5940
+ import { z as z19 } from "zod/v3";
5940
5941
 
5941
5942
  // src/tools/planned-work.ts
5942
5943
  var MORE_DRAFTS_LINE = "_More drafts exist than shown. `context action=list status=draft` lists more, but that list is capped as well; page with limit/cursor once context list supports it._";
@@ -6564,32 +6565,12 @@ function buildTaskAlignmentLines(ta) {
6564
6565
  lines.push("");
6565
6566
  return lines;
6566
6567
  }
6567
- var CORE_PROTOCOL_BASE = [
6568
- '**Search before proposing.** Before suggesting new features, architecture, or changes, search the Chain: `context action=search query="<relevant terms>"`. Build on what exists.',
6569
- "**Reference by ID.** When discussing a topic that has Chain entries, cite them by entry ID (e.g. `DEC-50`, `PRI-3`). This keeps conversations grounded in shared knowledge.",
6570
- "**Check scope.** If the proposed work doesn't fall under an active work package, stop and say so. Do not design implementation for out-of-scope work without explicit user go-ahead.",
6571
- `**Capture continuously.** When a decision, tension, insight, or new term surfaces during work, capture it as a draft immediately. Don't defer to "later."`
6572
- ];
6573
- var RULE5 = '**Validate against the rules.** Before proposing or building anything, check it against the rules served in your briefing (`orient task="<your task>"` serves them for a task). If your proposal conflicts with one \u2014 stop, name the rule, and get explicit user confirmation.';
6574
6568
  function provenanceMark(p) {
6575
6569
  if (p === "authoritative") return "\u2605 ";
6576
6570
  if (p === "provisional") return "~ ";
6577
6571
  if (p === "floor") return "\xB7 ";
6578
6572
  return "";
6579
6573
  }
6580
- function buildOperatingProtocol() {
6581
- const protocol = [...CORE_PROTOCOL_BASE, RULE5];
6582
- const lines = [
6583
- "## Operating Protocol",
6584
- "_How to work in this workspace. Follow these before and during every task._",
6585
- ""
6586
- ];
6587
- for (let i = 0; i < protocol.length; i++) {
6588
- lines.push(`${i + 1}. ${protocol[i]}`);
6589
- }
6590
- lines.push("");
6591
- return lines;
6592
- }
6593
6574
  function summariseOutcomeAttentionCounts(rows) {
6594
6575
  const ready = rows.filter((b) => b.reason === "ready-to-close").length;
6595
6576
  const stale = rows.length - ready;
@@ -6867,7 +6848,6 @@ function quoteDataLines(lines, tag) {
6867
6848
  }
6868
6849
 
6869
6850
  // src/tools/orient-rituals.ts
6870
- var RULE_LISTS_EVERYONE_ONLY_LABEL = "Rules (everyone list only; run `orient mode=full` for your team and role lists):";
6871
6851
  var SPINE_UNAVAILABLE_LINE = "Spine check: unavailable \u2014 orient fetch failed, retry `orient`";
6872
6852
  function buildRitualsSurfaceLines(view, opts) {
6873
6853
  const lines = [""];
@@ -6882,16 +6862,9 @@ function buildRitualsSurfaceLines(view, opts) {
6882
6862
  if (view?.atCapLine) lines.push(view.atCapLine);
6883
6863
  return lines;
6884
6864
  }
6885
- function buildOrientBriefingLines(view, opts) {
6865
+ function buildOrientBriefingLines(view) {
6886
6866
  if (!view) return [];
6887
- const rl = view.ruleLists ?? void 0;
6888
- const ruleLists = rl && opts?.everyoneOnly ? { everyone: rl.everyone, team: null, role: { rows: [], overCap: 0 }, unassigned: rl.unassigned, truncated: rl.truncated, ...rl.namesTruncated ? { namesTruncated: true } : {} } : rl;
6889
- const lines = formatBriefingLines({ facts: view.briefing ?? void 0, ruleLists, surface: "mcp" });
6890
- if (!opts?.everyoneOnly || !rl) return lines;
6891
- if (lines.includes("Rules:")) return lines.map((l) => l === "Rules:" ? RULE_LISTS_EVERYONE_ONLY_LABEL : l);
6892
- const pull = lines.findIndex((l) => l.startsWith("Pull more:"));
6893
- lines.splice(pull < 0 ? lines.length : pull, 0, RULE_LISTS_EVERYONE_ONLY_LABEL, "(none currently served)", "");
6894
- return lines;
6867
+ return formatBriefingLines({ facts: view.briefing ?? void 0, ruleLists: view.ruleLists ?? void 0, surface: "mcp" });
6895
6868
  }
6896
6869
  async function fetchAndRenderOpenerDirection(kernelQuery3, errors, session) {
6897
6870
  let view = null;
@@ -7001,371 +6974,6 @@ function getCollectionLabel(canonicalKey, form, workspaceCtx, collectionCtx) {
7001
6974
  return canonicalKey;
7002
6975
  }
7003
6976
 
7004
- // src/tools/start_pb.ts
7005
- import { z as z19 } from "zod/v3";
7006
-
7007
- // src/lib/sessionNotices.ts
7008
- var ANSI_AND_CONTROL_RE2 = /\x1b\[[0-9;]*[A-Za-z]|\x1b\][^\x07]*\x07|\x1b[^[\]0-9;A-Za-z]?|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
7009
- function safe(str) {
7010
- return str ? str.replace(ANSI_AND_CONTROL_RE2, "") : "";
7011
- }
7012
- var FEEDBACK_SUBMIT_HINT = "Friction or ideas? Use `feedback action=submit` \u2014 describe how pb behaved, not your workspace content. Once screening completes, it enters the builders' triage queue.";
7013
- function nextActionForVerb(verb, args) {
7014
- switch (verb) {
7015
- case "feedback.list":
7016
- return { tool: "feedback", description: "Read your workspace's own feedback (all statuses)", parameters: { action: "list" } };
7017
- case "feedback.queue":
7018
- return { tool: "feedback", description: "Read the vendor triage queue (system admins only)", parameters: { action: "queue" } };
7019
- case "question.create":
7020
- return { tool: "question", description: "Create a routed question", parameters: { action: "create" } };
7021
- case "question.adopt":
7022
- return { tool: "question", description: "Attach routing to a captured tension", parameters: { action: "adopt" } };
7023
- case "question.assign":
7024
- return { tool: "question", description: "Confirm or reassign a question", parameters: { action: "assign" } };
7025
- case "question.snooze":
7026
- return { tool: "question", description: "Snooze a routed/escalated question", parameters: { action: "snooze" } };
7027
- case "question.decline":
7028
- return { tool: "question", description: "Pass on or decline a question", parameters: { action: "decline" } };
7029
- case "question.answer":
7030
- return { tool: "question", description: "Answer a question", parameters: { action: "answer" } };
7031
- case "question.forceClose":
7032
- return { tool: "question", description: "Team-Lead-only force-close", parameters: { action: "force-close" } };
7033
- case "question.list":
7034
- return { tool: "question", description: args.mine === true ? "List every question you asked" : "List open questions", parameters: { action: "list", ...args.mine === true ? { mine: true } : {} } };
7035
- default:
7036
- return null;
7037
- }
7038
- }
7039
- function noticesToNextActions(notices) {
7040
- if (!notices || notices.length === 0) return [];
7041
- const actions2 = notices.map((n) => nextActionForVerb(n.action.verb, n.action.args)).filter((a) => a !== null);
7042
- const seen = /* @__PURE__ */ new Set();
7043
- return actions2.filter((a) => {
7044
- const key = `${a.tool}:${JSON.stringify(a.parameters)}`;
7045
- if (seen.has(key)) return false;
7046
- seen.add(key);
7047
- return true;
7048
- });
7049
- }
7050
- var QUESTION_ACK_GATE_SOURCE_ID = "question-ack-gate";
7051
- function displayVerbFromRoute(route) {
7052
- switch (route) {
7053
- case "question.assign":
7054
- return "take it / reassign";
7055
- case "question.decline":
7056
- return "pass / decline";
7057
- case "question.forceClose":
7058
- return "force-close";
7059
- default:
7060
- return route.replace(/^question\./, "");
7061
- }
7062
- }
7063
- function formatQuestionBrief(q) {
7064
- const lines = [];
7065
- lines.push(` [${safe(q.sourcePointer)}] ${safe(q.name)} \u2014 ${safe(q.whyYou)}, ${q.ageDays}d`);
7066
- lines.push(` "${safe(q.question)}" \u2014 asked by ${safe(q.asker)}${q.domain ? `, ${safe(q.domain)} domain` : ""}`);
7067
- if (q.dependents.count > 0) {
7068
- const names = q.dependents.named.map((d) => `${safe(d.name)} (${safe(d.id)})`).join(", ");
7069
- const countLabel = q.dependents.truncated ? `${q.dependents.count}+` : String(q.dependents.count);
7070
- lines.push(` blocks ${countLabel} item(s)${names ? `: ${names}` : ""}`);
7071
- }
7072
- if (q.declineContext) lines.push(` declined: ${safe(q.declineContext)}`);
7073
- if (q.answerContext) lines.push(` ${safe(q.answerContext)}`);
7074
- const verbList = [...new Set(q.actions.map((a) => safe(a.label) || displayVerbFromRoute(a.verb)))].join(" \xB7 ") || "answer";
7075
- lines.push(` ${verbList} -> question action=answer entryId=${safe(q.sourcePointer)} text="<your answer>"`);
7076
- return lines;
7077
- }
7078
- function formatQuestionAckGateBlock(notice) {
7079
- const questions = notice.payload?.questions ?? [];
7080
- const lines = [];
7081
- lines.push(`- ${questions.length} open question${questions.length === 1 ? "" : "s"} need you before you continue.`);
7082
- for (const q of questions) lines.push(...formatQuestionBrief(q));
7083
- lines.push(" Deal with each question above before proceeding.");
7084
- if (notice.payload?.truncated) {
7085
- lines.push(" (This list may be incomplete \u2014 you may have more open questions than shown.)");
7086
- }
7087
- return lines;
7088
- }
7089
- function formatNoticeLines(notices) {
7090
- if (!notices || notices.length === 0) return [];
7091
- return notices.flatMap(
7092
- (n) => n.source === QUESTION_ACK_GATE_SOURCE_ID && n.payload ? formatQuestionAckGateBlock(n) : [`- ${safe(n.subject)} (${safe(n.change)})`]
7093
- );
7094
- }
7095
-
7096
- // src/lib/terminalStatuses.ts
7097
- var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
7098
- "shipped",
7099
- "resolved",
7100
- "done",
7101
- "closed",
7102
- "decided",
7103
- "deprecated",
7104
- // WP-707 S1 round-3 sign-off (P1, PRRT_kwDORS4ils6eVtHg): the questions drawer this PR revives
7105
- // declares validWorkflowStatuses ['open','investigating','answered','dropped'] — before this
7106
- // PR, questions were shelved as 'tension' so confidence.ts's QUESTION_SLUGS (['questions'])
7107
- // matched nothing and questionResolutionRate defaulted to 1. Reviving the drawer without this
7108
- // entry makes resolvedQuestions always empty (0/N), so any workspace with ≥3 questions goes
7109
- // PERMANENTLY red and every answered question with ≥3 downstream stays a whatsNext critical
7110
- // blocker forever, even after being answered. 'dropped' is pre-existing on objectives/
7111
- // key-results/work-packages/chains (already terminal there) but becomes newly reachable via
7112
- // this PR's two new drawers — same additive edit, taken together per the sign-off ruling.
7113
- "answered",
7114
- "dropped"
7115
- ]);
7116
-
7117
- // src/lib/rankActiveBets.ts
7118
- function betWorkflowPriority(e) {
7119
- if (e.workflowStatus === "building") return 2;
7120
- if (e.workflowStatus === "shaped") return 1;
7121
- return 0;
7122
- }
7123
- function rankActiveBets(entries) {
7124
- return [...entries].sort((a, b) => {
7125
- const p = betWorkflowPriority(b) - betWorkflowPriority(a);
7126
- if (p !== 0) return p;
7127
- return (b.updatedAt ?? 0) - (a.updatedAt ?? 0);
7128
- });
7129
- }
7130
-
7131
- // src/tools/start_pb.ts
7132
- var startPbSchema = z19.object({
7133
- task: z19.string().max(2e3).optional().describe(
7134
- "What you're about to work on (e.g. 'implementing auth middleware'). Shapes the opener to the task \u2014 for every workspace state, including a fresh one."
7135
- )
7136
- });
7137
- async function tryMarkOriented(agentSessionId, coherenceSnapshot) {
7138
- if (!agentSessionId) return { oriented: false, orientationStatus: "no_session" };
7139
- try {
7140
- await kernelCall("agent.markOriented", {
7141
- sessionId: agentSessionId,
7142
- ...coherenceSnapshot ? { coherenceSnapshot } : {}
7143
- });
7144
- setSessionOriented(true);
7145
- return { oriented: true, orientationStatus: "complete" };
7146
- } catch {
7147
- if (!coherenceSnapshot) {
7148
- return { oriented: false, orientationStatus: "failed" };
7149
- }
7150
- try {
7151
- await kernelCall("agent.markOriented", { sessionId: agentSessionId });
7152
- setSessionOriented(true);
7153
- return { oriented: true, orientationStatus: "complete" };
7154
- } catch {
7155
- return { oriented: false, orientationStatus: "failed" };
7156
- }
7157
- }
7158
- }
7159
- function isActiveBet(e) {
7160
- return !TERMINAL_STATUSES.has(e.workflowStatus ?? "");
7161
- }
7162
- async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
7163
- let priorSessions = [];
7164
- let recoveryBlock = null;
7165
- try {
7166
- const recentConversationId = getConversationId();
7167
- const sessionsResult = await kernelQuery(
7168
- "agent.recentSessions",
7169
- recentConversationId ? { limit: 3, conversationId: recentConversationId } : { limit: 3 }
7170
- );
7171
- priorSessions = sessionsResult?.sessions ?? [];
7172
- recoveryBlock = sessionsResult?.recoveryBlock ?? null;
7173
- } catch {
7174
- }
7175
- let openTensions = [];
7176
- try {
7177
- const tensions = await kernelQuery("chain.listEntries", { collectionSlug: "tensions" });
7178
- openTensions = (tensions ?? []).filter(
7179
- (e) => e.workflowStatus === "open"
7180
- );
7181
- } catch {
7182
- }
7183
- let readiness = null;
7184
- try {
7185
- readiness = await kernelQuery("chain.workspaceReadiness");
7186
- } catch (e) {
7187
- errors.push(`Readiness: ${e instanceof Error ? e.message : String(e)}`);
7188
- }
7189
- const lines = [];
7190
- if (readiness?.sampleTruncated) lines.push(READINESS_PROVISIONAL_LINE, "");
7191
- const captureBehaviorNote = "_Everything I capture stays pending until you confirm._";
7192
- const coherence = await buildCoherenceSection();
7193
- let allCollections = [];
7194
- try {
7195
- allCollections = await kernelQuery("chain.listCollections") ?? [];
7196
- } catch {
7197
- }
7198
- let shapeAdvisorySummary;
7199
- try {
7200
- shapeAdvisorySummary = await kernelQuery("chain.shapeAdvisorySummary");
7201
- } catch {
7202
- }
7203
- lines.push(`# ${wsCtx.workspaceName}`);
7204
- lines.push("_Product Brain is ready._");
7205
- lines.push("");
7206
- const startShapeLine = formatShapeAdvisorySummaryLine(shapeAdvisorySummary);
7207
- if (startShapeLine) lines.push(startShapeLine, "");
7208
- const opener = await fetchAndRenderOpenerDirection(kernelQuery, errors, { agentSessionId, conversationId: getConversationId(), task });
7209
- lines.push(...opener.lines);
7210
- lines.push("_Need a collection that doesn't exist yet? Creating one is not available through this tool. Ask a platform admin to provision it, or enable the module ontology pack that owns it._");
7211
- lines.push("");
7212
- let activeBets = [];
7213
- try {
7214
- const betsEntries = await kernelQuery("chain.listEntries", { collectionSlug: "work-packages" });
7215
- activeBets = rankActiveBets((betsEntries ?? []).filter((e) => isActiveBet(e))).map((e) => ({ name: e.name, entryId: e.entryId ?? null })).slice(0, 8);
7216
- } catch {
7217
- }
7218
- const ruleListsView = opener.view?.ruleLists ?? void 0;
7219
- if (task) {
7220
- if (activeBets.length > 0) {
7221
- lines.push("");
7222
- lines.push("## Active work packages \u2014 current scope");
7223
- lines.push("_Work outside these work packages requires explicit user confirmation._");
7224
- lines.push("");
7225
- for (const e of activeBets) {
7226
- lines.push(`- \`${e.entryId ?? e.name}\` ${e.name}`);
7227
- }
7228
- lines.push("");
7229
- }
7230
- lines.push(...buildOperatingProtocol());
7231
- if (coherence) {
7232
- lines.push(...coherence.lines);
7233
- }
7234
- if (allCollections.length > 0) {
7235
- const docCompleteness = buildDocCompletenessSection(allCollections);
7236
- if (docCompleteness.errorCount > 0 || docCompleteness.warningCount > 0) {
7237
- lines.push(...docCompleteness.lines);
7238
- }
7239
- }
7240
- const plannedWork = opener.view?.plannedWork ?? NO_PLANNED_WORK;
7241
- if (hasPlannedWork(plannedWork)) {
7242
- lines.push("");
7243
- lines.push(...buildPlannedWorkSection(plannedWork, priorSessions, recoveryBlock));
7244
- } else if (recoveryBlock) {
7245
- lines.push("");
7246
- lines.push(...formatRecoveryBlock(recoveryBlock));
7247
- }
7248
- try {
7249
- const allEntries = await kernelQuery(
7250
- "chain.listEntries",
7251
- { fields: ["seededByPlatform", "collectionId"] }
7252
- );
7253
- const committed = (allEntries ?? []).filter((e) => !e.seededByPlatform);
7254
- const committedCollections = new Set(committed.map((e) => e.collectionId));
7255
- if (committed.length >= 10 && committedCollections.size >= 3) {
7256
- lines.push("");
7257
- lines.push(`_Context sample: ${committed.length} accepted/active entries across ${committedCollections.size} collections._`);
7258
- }
7259
- } catch {
7260
- }
7261
- lines.push("");
7262
- lines.push(`Working on: **${task}**`);
7263
- lines.push("");
7264
- } else {
7265
- if (activeBets.length > 0) {
7266
- lines.push("");
7267
- lines.push("## Active work packages \u2014 current scope");
7268
- lines.push("_Work outside these work packages requires explicit user confirmation._");
7269
- lines.push("");
7270
- for (const e of activeBets) {
7271
- lines.push(`- \`${e.entryId ?? e.name}\` ${e.name}`);
7272
- }
7273
- lines.push("");
7274
- }
7275
- lines.push(...buildOperatingProtocol());
7276
- if (coherence) {
7277
- lines.push(...coherence.lines);
7278
- }
7279
- if (allCollections.length > 0) {
7280
- const docCompleteness = buildDocCompletenessSection(allCollections);
7281
- if (docCompleteness.errorCount > 0 || docCompleteness.warningCount > 0) {
7282
- lines.push(...docCompleteness.lines);
7283
- }
7284
- }
7285
- if (priorSessions.length > 0 && !recoveryBlock) {
7286
- const last = priorSessions[0];
7287
- const date = last.startedAt ? new Date(last.startedAt).toISOString().split("T")[0] : "unknown";
7288
- const created = Array.isArray(last.entriesCreated) ? last.entriesCreated.length : last.entriesCreated ?? 0;
7289
- const modified = Array.isArray(last.entriesModified) ? last.entriesModified.length : last.entriesModified ?? 0;
7290
- lines.push(`_Last session (${date}): ${created} created, ${modified} modified._`);
7291
- lines.push("");
7292
- }
7293
- if (recoveryBlock) {
7294
- lines.push("");
7295
- lines.push(...formatRecoveryBlock(recoveryBlock));
7296
- }
7297
- lines.push(captureBehaviorNote);
7298
- lines.push("");
7299
- lines.push("**What are you working on?** Tell me and I'll load relevant governance and context.");
7300
- lines.push("");
7301
- }
7302
- if (errors.length > 0) {
7303
- lines.push("## Errors");
7304
- for (const err of errors) lines.push(`- ${err}`);
7305
- lines.push("");
7306
- }
7307
- const { oriented, orientationStatus } = await tryMarkOriented(agentSessionId, coherence?.snapshot ?? void 0);
7308
- const gapCount = readiness?.gaps?.length ?? 0;
7309
- const summaryParts = ["Product Brain ready."];
7310
- if (gapCount > 0) summaryParts.push(`${gapCount} ${readiness?.sampleTruncated ? "potential " : ""}gap${gapCount === 1 ? "" : "s"}${readiness?.sampleTruncated ? " in sampled entries; absence unverified" : ""}.`);
7311
- if (openTensions.length > 0) summaryParts.push(`${openTensions.length} open tension${openTensions.length === 1 ? "" : "s"}.`);
7312
- const next = [];
7313
- if (gapCount > 0 && !readiness?.sampleTruncated) {
7314
- next.push({ tool: "capture", description: "Fill a gap", parameters: {} });
7315
- }
7316
- if (openTensions.length > 0) {
7317
- next.push({ tool: "context", description: "View open tensions", parameters: { action: "list", collection: "tensions" } });
7318
- }
7319
- return {
7320
- text: lines.join("\n"),
7321
- structuredContent: success(
7322
- summaryParts.join(" "),
7323
- {
7324
- sampleTruncated: readiness?.sampleTruncated ?? null,
7325
- totalGaps: gapCount,
7326
- openTensions: openTensions.length,
7327
- oriented,
7328
- orientationStatus,
7329
- ...agentSessionId ? { sessionId: agentSessionId } : {},
7330
- // PR #519 review round 3 (Codex P2): the rendered shape-advisory line must reach
7331
- // structuredContent too, matching orient.ts's brief/full sites.
7332
- ...shapeAdvisorySummary ? { shapeAdvisorySummary } : {},
7333
- ...ruleListsView ? { ruleLists: ruleListsView } : {}
7334
- // TASK-436 (DEC-1774)
7335
- },
7336
- next.length > 0 ? next : void 0
7337
- )
7338
- };
7339
- }
7340
- async function handleStartPb({ task }) {
7341
- const errors = [];
7342
- const agentSessionId = getAgentSessionId();
7343
- let wsCtx = null;
7344
- try {
7345
- wsCtx = await getWorkspaceContext();
7346
- } catch (e) {
7347
- errors.push(`Workspace: ${e instanceof Error ? e.message : String(e)}`);
7348
- }
7349
- if (!wsCtx) {
7350
- const text = "# Could not connect to Product Brain\n\n" + (errors.length > 0 ? errors.map((e) => `- ${e}`).join("\n") : "Check your API key and CONVEX_SITE_URL.");
7351
- return {
7352
- content: [{ type: "text", text }],
7353
- structuredContent: failure(
7354
- "BACKEND_UNAVAILABLE",
7355
- "Could not connect to Product Brain.",
7356
- "Check your API key and CONVEX_SITE_URL."
7357
- )
7358
- };
7359
- }
7360
- const orientResult = await buildOrientResponse(wsCtx, agentSessionId, errors, task);
7361
- return {
7362
- content: [{ type: "text", text: `${orientResult.text}
7363
-
7364
- ${FEEDBACK_SUBMIT_HINT}` }],
7365
- structuredContent: orientResult.structuredContent
7366
- };
7367
- }
7368
-
7369
6977
  // src/tools/orient.ts
7370
6978
  var PURPOSE_GAP_PREFIX = "purpose-gap-";
7371
6979
  var READINESS_GAPS_MAX = 50;
@@ -7399,54 +7007,27 @@ async function markOrientedWithSnapshotFallback(agentSessionId, coherenceSnapsho
7399
7007
  }
7400
7008
  }
7401
7009
  }
7402
- var ORIENT_ACTIONS = ["start", "task"];
7403
- var orientSchema = z20.object({
7404
- action: z20.enum(ORIENT_ACTIONS).optional().default("task").describe(
7405
- "'start': universal session opener (absorbs start_pb) \u2014 a standup briefing for every workspace state, fresh or active. 'task': task-grounded context loader \u2014 the original orient behavior."
7010
+ var ORIENT_ACTIONS = ["task"];
7011
+ var orientSchema = z19.object({
7012
+ action: z19.enum(ORIENT_ACTIONS).optional().default("task").describe(
7013
+ "'task': task-grounded context loader, the only action."
7406
7014
  ),
7407
- mode: z20.enum(["full", "brief"]).optional().default("full").describe("For 'task': full = full context (default). brief = compact summary for mid-session re-orientation. Prefer using the `tier` param for depth control."),
7408
- tier: z20.enum(["summary", "standard", "full"]).optional().describe(
7015
+ mode: z19.enum(["full", "brief"]).optional().default("full").describe("For 'task': full = full context (default). brief = compact summary for mid-session re-orientation. Prefer using the `tier` param for depth control."),
7016
+ tier: z19.enum(["summary", "standard", "full"]).optional().describe(
7409
7017
  "For 'task': payload depth. Defaults to summary (~10 KB) when task is provided; standard (~256 KB) when task is absent. Pass summary, standard, or full to override."
7410
7018
  ),
7411
- task: z20.string().max(2e3).optional().describe(
7412
- "For 'task': natural-language task description for task-scoped context. For 'start': what you're about to work on. When provided to 'task', orient returns scored, relevant entries for the task."
7019
+ task: z19.string().max(2e3).optional().describe(
7020
+ "For 'task': natural-language task description for task-scoped context. When provided, orient returns scored, relevant entries for the task."
7413
7021
  ),
7414
7022
  // D1d (WP-754/TASK-204): matches the server's StartupResolutionSignals shape; domain resolution (original purpose) is deleted, sole survivor is `context.served` telemetry.
7415
- startupSignals: z20.object({
7416
- changedPaths: z20.array(z20.string()).max(25).optional().describe("Paths changed in the current working tree, if known (e.g. from a prior git status/diff tool call)."),
7417
- reviewedArtifactRefs: z20.array(z20.string()).max(25).optional().describe("Chain entry IDs the caller has already reviewed this session, if tracked."),
7418
- branchName: z20.string().max(120).optional().describe("Current git branch name, if known."),
7419
- worktreeName: z20.string().max(120).optional().describe("Current worktree/directory name, if known.")
7023
+ startupSignals: z19.object({
7024
+ changedPaths: z19.array(z19.string()).max(25).optional().describe("Paths changed in the current working tree, if known (e.g. from a prior git status/diff tool call)."),
7025
+ reviewedArtifactRefs: z19.array(z19.string()).max(25).optional().describe("Chain entry IDs the caller has already reviewed this session, if tracked."),
7026
+ branchName: z19.string().max(120).optional().describe("Current git branch name, if known."),
7027
+ worktreeName: z19.string().max(120).optional().describe("Current worktree/directory name, if known.")
7420
7028
  }).optional().describe("For 'task': best-effort startup signals \u2014 changedPaths/reviewedArtifactRefs/branchName/worktreeName. All optional; omit fields you don't know."),
7421
- invocationPath: z20.enum(["session-start", "session-close", "manual-orient", "handshake", "unknown"]).optional().describe("For 'task': how this orient was invoked. Defaults to 'manual-orient' (the direct `orient task=...` call shape) when omitted.")
7422
- });
7423
- var orientStartVariant = z20.object({
7424
- action: z20.literal("start"),
7425
- task: z20.string().max(2e3).optional()
7426
- });
7427
- var orientTaskVariant = z20.object({
7428
- action: z20.literal("task"),
7429
- mode: z20.enum(["full", "brief"]).optional().default("full"),
7430
- tier: z20.enum(["summary", "standard", "full"]).optional(),
7431
- task: z20.string().max(2e3).optional(),
7432
- // WP-486 Slice 1 (FEAT-1371) — mirrors orientSchema's top-level declaration above; see its
7433
- // doc comment for the shape rationale.
7434
- startupSignals: z20.object({
7435
- changedPaths: z20.array(z20.string()).max(25).optional(),
7436
- reviewedArtifactRefs: z20.array(z20.string()).max(25).optional(),
7437
- branchName: z20.string().max(120).optional(),
7438
- worktreeName: z20.string().max(120).optional()
7439
- }).optional(),
7440
- invocationPath: z20.enum(["session-start", "session-close", "manual-orient", "handshake", "unknown"]).optional()
7029
+ invocationPath: z19.enum(["session-start", "session-close", "manual-orient", "handshake", "unknown"]).optional().describe("For 'task': how this orient was invoked. Defaults to 'manual-orient' (the direct `orient task=...` call shape) when omitted.")
7441
7030
  });
7442
- var orientActionUnion = z20.discriminatedUnion("action", [
7443
- orientStartVariant,
7444
- orientTaskVariant
7445
- ]);
7446
- var ORIENT_ACTION_SPECS = {
7447
- start: { params: ["task"], description: "Universal session opener; task is optional." },
7448
- task: { params: ["mode", "tier", "task"], description: "Task-grounded context loader; all params optional." }
7449
- };
7450
7031
  function taskGroundingWarningLines(task, hasTaskGrounding = false) {
7451
7032
  if (hasTaskGrounding) return [];
7452
7033
  if (task) {
@@ -7471,32 +7052,22 @@ function readinessSummaryFact(readiness, sampleTruncated) {
7471
7052
  return !readiness ? "Readiness unavailable." : sampleTruncated ? "Provisional readiness sample." : `${n} gap${n === 1 ? "" : "s"}.`;
7472
7053
  }
7473
7054
  function registerOrientTool(server) {
7474
- const orientHandlers = {
7475
- start: (data) => handleStartPb({ task: data.task }),
7476
- task: (data) => _handleOrient({
7477
- mode: data.mode,
7478
- tier: data.tier,
7479
- task: data.task,
7480
- startupSignals: data.startupSignals,
7481
- invocationPath: data.invocationPath
7482
- })
7483
- };
7484
7055
  server.registerTool(
7485
7056
  "orient",
7486
7057
  {
7487
7058
  title: "Orient",
7488
- description: "Workspace orientation and session bootstrapping. Two actions:\n\n- **start**: Universal opener (absorbs `start_pb`) \u2014 say 'Start PB' to begin every session. Returns a standup briefing for every workspace state, fresh or active. Orients the CURRENT session \u2014 it does not create one; for writes, call `session action=start` first.\n- **task**: Task-grounded context loader (the original `orient` behavior). Use after `action=start` to load governance and context for a specific task. Orient is advice for captures, relations, accepts, ticks and questions; " + ORIENT_REQUIRED_NOTE + "\n\n**tier** (action=task): Controls payload depth.\n- `summary` (~10 KB) \u2014 compact, calibrated for task-scoped retrieval.\n- `standard` (~256 KB) \u2014 rich, suited to workspace standups.\n- `full` \u2014 complete payload, no cap. Use with deliberation.\n\nThe spine (vision, purpose, strategy highlights) is served in full and a grounding status flags when context was reduced.\n\nDefaults: `summary` when `task` is provided; `standard` when `task` is absent. Explicit `tier` always wins.\n\n**mode** (action=task): `full` (default) returns full context. `brief` returns compact summary \u2014 mapped to tier=summary internally.",
7059
+ description: "Task-grounded context loader. Call `session action=start` first; it opens the session and hands you the briefing.\n\nPass `task` to load the governance and context for a specific task. Orient is advice for captures, relations, accepts, ticks and questions; " + ORIENT_REQUIRED_NOTE + "\n\n**tier** (action=task): Controls payload depth.\n- `summary` (~10 KB) \u2014 compact, calibrated for task-scoped retrieval.\n- `standard` (~256 KB) \u2014 rich, suited to workspace standups.\n- `full` \u2014 complete payload, no cap. Use with deliberation.\n\nThe spine (vision, purpose, strategy highlights) is served in full and a grounding status flags when context was reduced.\n\nDefaults: `summary` when `task` is provided; `standard` when `task` is absent. Explicit `tier` always wins.\n\n**mode** (action=task): `full` (default) returns full context. `brief` returns compact summary \u2014 mapped to tier=summary internally.",
7489
7060
  inputSchema: orientSchema,
7490
- // Mixed read/write noun (§3 R3): 'start' writes session state.
7061
+ // Not read-only: a task-shaped orient marks the session oriented.
7491
7062
  annotations: { readOnlyHint: false, idempotentHint: true, openWorldHint: false }
7492
7063
  },
7493
7064
  thinWrapper(async (args) => {
7494
7065
  const parsed = parseOrFail(orientSchema, args);
7495
7066
  if (!parsed.ok) return parsed.result;
7496
- const { action } = parsed.data;
7067
+ const { action, mode, tier, task, startupSignals, invocationPath } = parsed.data;
7497
7068
  return runWithToolContext(
7498
7069
  { tool: "orient", action },
7499
- () => dispatchDiscriminated("orient", orientActionUnion, parsed.data, ORIENT_ACTION_SPECS, orientHandlers)
7070
+ () => _handleOrient({ mode, tier, task, startupSignals, invocationPath })
7500
7071
  );
7501
7072
  })
7502
7073
  );
@@ -7574,9 +7145,7 @@ async function _handleOrient({ mode = "full", tier, task, startupSignals, invoca
7574
7145
  "No accepted user-authored entries were found; drafts may already exist.",
7575
7146
  "",
7576
7147
  "For your current task, follow the spine guidance below: discover and reuse existing knowledge before creating anything.",
7577
- "Capture only what the task needs, inspect its returned status, and read it back. Accept it only if it remains a draft; already accepted or committed entries need no extra acceptance.",
7578
- "",
7579
- "Full onboarding is optional: call `orient action=start` if you want guided workspace setup. If you already ran setup, continue your task without restarting it."
7148
+ "Capture only what the task needs, inspect its returned status, and read it back. Accept it only if it remains a draft; already accepted or committed entries need no extra acceptance."
7580
7149
  ];
7581
7150
  scanLines.push(...buildRitualsSurfaceLines(orientView, { fetchFailed: orientFetchFailed || orientView?.fetchFailed === true }));
7582
7151
  scanLines.push("", ...buildOrientBriefingLines(orientView));
@@ -7592,9 +7161,8 @@ async function _handleOrient({ mode = "full", tier, task, startupSignals, invoca
7592
7161
  const blankResult = {
7593
7162
  content: [{ type: "text", text: scanLines.join("\n") }],
7594
7163
  structuredContent: success(
7595
- "No accepted user-authored entries found. Follow the spine guidance for your task; full onboarding is optional.",
7596
- { oriented, orientationStatus: orientationStatus2, redirectHint: "Optional full onboarding: `orient action=start`. Continue an existing task using the spine guidance.", ...blankRuleListsExtra },
7597
- task ? [] : [{ tool: "orient", description: "Optional full workspace onboarding", parameters: { action: "start" } }]
7164
+ "No accepted user-authored entries found. Follow the spine guidance for your task.",
7165
+ { oriented, orientationStatus: orientationStatus2, ...blankRuleListsExtra }
7598
7166
  )
7599
7167
  };
7600
7168
  reportOrientWrapperBytes(blankResult, false, resolvedTier, taskMeaningful);
@@ -7692,10 +7260,6 @@ async function _handleOrient({ mode = "full", tier, task, startupSignals, invoca
7692
7260
  lines.push(...docCompleteness.lines);
7693
7261
  }
7694
7262
  }
7695
- if (orientView) {
7696
- lines.push("");
7697
- lines.push(...buildOperatingProtocol());
7698
- }
7699
7263
  let orientationStatus2 = "no_session";
7700
7264
  if (agentSessionId && taskAsked) {
7701
7265
  const orientedOk = await markOrientedWithSnapshotFallback(agentSessionId, coherenceSnapshot);
@@ -7972,7 +7536,6 @@ async function _handleOrient({ mode = "full", tier, task, startupSignals, invoca
7972
7536
  fullArchitectureNotes.forEach((e) => lines.push(fmt(e)));
7973
7537
  lines.push("");
7974
7538
  }
7975
- lines.push(...buildOperatingProtocol());
7976
7539
  }
7977
7540
  const plannedWork = orientView?.plannedWork ?? NO_PLANNED_WORK;
7978
7541
  if (hasPlannedWork(plannedWork)) {
@@ -8010,7 +7573,6 @@ async function _handleOrient({ mode = "full", tier, task, startupSignals, invoca
8010
7573
  }
8011
7574
  const noTaskShapeLine = formatShapeAdvisorySummaryLine(orientView?.shapeAdvisorySummary);
8012
7575
  if (noTaskShapeLine) lines.push(noTaskShapeLine, "");
8013
- lines.push(...buildOperatingProtocol());
8014
7576
  if (fullCoherence) {
8015
7577
  lines.push(...fullCoherence.lines);
8016
7578
  }
@@ -8138,7 +7700,7 @@ async function _handleOrient({ mode = "full", tier, task, startupSignals, invoca
8138
7700
  }
8139
7701
 
8140
7702
  // src/tools/workflows.ts
8141
- import { z as z21 } from "zod/v3";
7703
+ import { z as z20 } from "zod/v3";
8142
7704
 
8143
7705
  // src/workflows/descriptor.ts
8144
7706
  function cloneWorkflowQuestion(question) {
@@ -8983,73 +8545,73 @@ function workflowRunOutputToText(output) {
8983
8545
 
8984
8546
  // src/tools/workflows.ts
8985
8547
  var WORKFLOWS_ACTIONS = ["list", "start", "checkpoint", "get-run"];
8986
- var jsonValueSchema = z21.lazy(
8987
- () => z21.union([
8988
- z21.string(),
8989
- z21.number(),
8990
- z21.boolean(),
8991
- z21.null(),
8992
- z21.array(jsonValueSchema),
8993
- z21.record(jsonValueSchema)
8548
+ var jsonValueSchema = z20.lazy(
8549
+ () => z20.union([
8550
+ z20.string(),
8551
+ z20.number(),
8552
+ z20.boolean(),
8553
+ z20.null(),
8554
+ z20.array(jsonValueSchema),
8555
+ z20.record(jsonValueSchema)
8994
8556
  ])
8995
8557
  );
8996
- var workflowRunOutputInputSchema = z21.union([
8997
- z21.object({
8998
- format: z21.literal("freetext"),
8999
- value: z21.string()
8558
+ var workflowRunOutputInputSchema = z20.union([
8559
+ z20.object({
8560
+ format: z20.literal("freetext"),
8561
+ value: z20.string()
9000
8562
  }),
9001
- z21.object({
9002
- format: z21.literal("list"),
9003
- value: z21.array(z21.string())
8563
+ z20.object({
8564
+ format: z20.literal("list"),
8565
+ value: z20.array(z20.string())
9004
8566
  }),
9005
- z21.object({
9006
- format: z21.literal("choice"),
9007
- value: z21.union([z21.string(), z21.array(z21.string())])
8567
+ z20.object({
8568
+ format: z20.literal("choice"),
8569
+ value: z20.union([z20.string(), z20.array(z20.string())])
9008
8570
  }),
9009
- z21.object({
9010
- format: z21.literal("structured"),
8571
+ z20.object({
8572
+ format: z20.literal("structured"),
9011
8573
  value: jsonValueSchema
9012
8574
  })
9013
8575
  ]);
9014
- var workflowsSchema = z21.object({
9015
- action: z21.enum(WORKFLOWS_ACTIONS).describe(
8576
+ var workflowsSchema = z20.object({
8577
+ action: z20.enum(WORKFLOWS_ACTIONS).describe(
9016
8578
  "'list': browse available workflows. 'start': start or resume a workflow \u2014 returns first (or current) round and next-step checkpoint call. 'checkpoint': record round output or final summary. 'get-run': inspect a persisted workflow run."
9017
8579
  ),
9018
- workflowId: z21.string().max(200).optional().describe("Workflow ID for start, checkpoint, or get-run, e.g. 'retro', 'implementation-review'"),
9019
- runId: z21.string().max(200).optional().describe("Workflow run ID: for get-run, which run to load; for checkpoint, target this run (avoids session drift \u2014 pass runId from get-run)."),
9020
- roundId: z21.string().max(200).optional().describe("Round ID for checkpoint, e.g. 'what-went-well'"),
9021
- output: z21.union([z21.string(), workflowRunOutputInputSchema]).optional().describe("The round's output \u2014 either legacy synthesized text or a typed workflow run payload."),
9022
- isFinal: z21.boolean().optional().describe("If true, finalize an existing durable workflow run from its terminal round and create the summary chain entry."),
9023
- restart: z21.boolean().optional().describe("If true, start a new durable run from the workflow's first round in the current session."),
9024
- summaryName: z21.string().max(ENTRY_NAME_MAX_CHARS).optional().describe("Optional name for final chain entry. If omitted, the workflow summary template is used."),
9025
- summaryDescription: z21.string().max(ENTRY_BODY_MAX_CHARS).optional().describe("Optional override for the final chain entry description. Defaults to the final round output text."),
9026
- summaryEntryId: z21.string().max(200).optional().describe("Link an existing entry as the run's summary instead of creating one. Used by facilitated workflows (e.g. shape) where the primary record is created by the specialized tool."),
8580
+ workflowId: z20.string().max(200).optional().describe("Workflow ID for start, checkpoint, or get-run, e.g. 'retro', 'implementation-review'"),
8581
+ runId: z20.string().max(200).optional().describe("Workflow run ID: for get-run, which run to load; for checkpoint, target this run (avoids session drift \u2014 pass runId from get-run)."),
8582
+ roundId: z20.string().max(200).optional().describe("Round ID for checkpoint, e.g. 'what-went-well'"),
8583
+ output: z20.union([z20.string(), workflowRunOutputInputSchema]).optional().describe("The round's output \u2014 either legacy synthesized text or a typed workflow run payload."),
8584
+ isFinal: z20.boolean().optional().describe("If true, finalize an existing durable workflow run from its terminal round and create the summary chain entry."),
8585
+ restart: z20.boolean().optional().describe("If true, start a new durable run from the workflow's first round in the current session."),
8586
+ summaryName: z20.string().max(ENTRY_NAME_MAX_CHARS).optional().describe("Optional name for final chain entry. If omitted, the workflow summary template is used."),
8587
+ summaryDescription: z20.string().max(ENTRY_BODY_MAX_CHARS).optional().describe("Optional override for the final chain entry description. Defaults to the final round output text."),
8588
+ summaryEntryId: z20.string().max(200).optional().describe("Link an existing entry as the run's summary instead of creating one. Used by facilitated workflows (e.g. shape) where the primary record is created by the specialized tool."),
9027
8589
  // WP-513: team+role to create the finalize summary AS OWNER of (rung 2 only).
9028
- ownerTeamEntryId: z21.string().max(200).optional().describe("For 'checkpoint' (isFinal): owning team (rung-2)."),
9029
- ownerRoleEntryId: z21.string().max(200).optional().describe("For 'checkpoint' (isFinal): owning role (rung-2).")
8590
+ ownerTeamEntryId: z20.string().max(200).optional().describe("For 'checkpoint' (isFinal): owning team (rung-2)."),
8591
+ ownerRoleEntryId: z20.string().max(200).optional().describe("For 'checkpoint' (isFinal): owning role (rung-2).")
9030
8592
  });
9031
- var workflowsListVariant = z21.object({ action: z21.literal("list") });
9032
- var workflowsGetRunVariant = z21.object({
9033
- action: z21.literal("get-run"),
9034
- runId: z21.string().max(200).optional(),
9035
- workflowId: z21.string().max(200).optional()
8593
+ var workflowsListVariant = z20.object({ action: z20.literal("list") });
8594
+ var workflowsGetRunVariant = z20.object({
8595
+ action: z20.literal("get-run"),
8596
+ runId: z20.string().max(200).optional(),
8597
+ workflowId: z20.string().max(200).optional()
9036
8598
  });
9037
- var workflowsStartVariant = z21.object({ action: z21.literal("start"), workflowId: z21.string().max(200) });
9038
- var workflowsCheckpointVariant = z21.object({
9039
- action: z21.literal("checkpoint"),
9040
- workflowId: z21.string().max(200),
9041
- roundId: z21.string().max(200),
9042
- output: z21.union([z21.string(), workflowRunOutputInputSchema]),
9043
- isFinal: z21.boolean().optional(),
9044
- restart: z21.boolean().optional(),
9045
- summaryName: z21.string().max(ENTRY_NAME_MAX_CHARS).optional(),
9046
- summaryDescription: z21.string().max(ENTRY_BODY_MAX_CHARS).optional(),
9047
- summaryEntryId: z21.string().max(200).optional(),
9048
- runId: z21.string().max(200).optional(),
9049
- ownerTeamEntryId: z21.string().max(200).optional(),
9050
- ownerRoleEntryId: z21.string().max(200).optional()
8599
+ var workflowsStartVariant = z20.object({ action: z20.literal("start"), workflowId: z20.string().max(200) });
8600
+ var workflowsCheckpointVariant = z20.object({
8601
+ action: z20.literal("checkpoint"),
8602
+ workflowId: z20.string().max(200),
8603
+ roundId: z20.string().max(200),
8604
+ output: z20.union([z20.string(), workflowRunOutputInputSchema]),
8605
+ isFinal: z20.boolean().optional(),
8606
+ restart: z20.boolean().optional(),
8607
+ summaryName: z20.string().max(ENTRY_NAME_MAX_CHARS).optional(),
8608
+ summaryDescription: z20.string().max(ENTRY_BODY_MAX_CHARS).optional(),
8609
+ summaryEntryId: z20.string().max(200).optional(),
8610
+ runId: z20.string().max(200).optional(),
8611
+ ownerTeamEntryId: z20.string().max(200).optional(),
8612
+ ownerRoleEntryId: z20.string().max(200).optional()
9051
8613
  });
9052
- var workflowsActionUnion = z21.discriminatedUnion("action", [
8614
+ var workflowsActionUnion = z20.discriminatedUnion("action", [
9053
8615
  workflowsListVariant,
9054
8616
  workflowsGetRunVariant,
9055
8617
  workflowsStartVariant,
@@ -9778,7 +9340,7 @@ function parseListOutput(output) {
9778
9340
  }
9779
9341
 
9780
9342
  // src/tools/quality.ts
9781
- import { z as z22 } from "zod/v3";
9343
+ import { z as z21 } from "zod/v3";
9782
9344
 
9783
9345
  // src/lib/spineVerdictRead.ts
9784
9346
  function isRecord(value) {
@@ -9943,36 +9505,36 @@ async function handleSpineCheck(story, refresh = false) {
9943
9505
 
9944
9506
  // src/tools/quality.ts
9945
9507
  var QUALITY_ACTIONS = ["check", "re-evaluate", "verify-chain", "spine-check"];
9946
- var qualitySchema = z22.object({
9947
- action: z22.enum(QUALITY_ACTIONS).describe(
9508
+ var qualitySchema = z21.object({
9509
+ action: z21.enum(QUALITY_ACTIONS).describe(
9948
9510
  "'check': read the entry's server quality verdict (tier + criteria). 're-evaluate': trigger fresh evaluation. 'verify-chain': verify entries against the codebase (codeMapping drift, cross-references) \u2014 absorbs `verify`. 'spine-check': structural completeness of the purpose+vision+strategy spine \u2014 story:true also runs the on-demand one-story judgment."
9949
9511
  ),
9950
- entryId: z22.string().max(200).optional().describe("For 'check'/'re-evaluate': entry ID, e.g. 'TEN-graph-db', '<PREFIX>-<n>'."),
9951
- context: z22.enum(["capture", "commit", "review"]).default("review").optional().describe("For re-evaluate: evaluation context"),
9952
- collection: z22.string().max(200).optional().describe("For 'verify-chain': collection slug to verify (default: glossary)."),
9953
- mode: z22.enum(["report", "fix"]).optional().describe("For 'verify-chain': 'report' = read-only trust report (default). 'fix' = also update drifted codeMapping statuses."),
9954
- story: z22.boolean().optional().describe("For 'spine-check': also run the on-demand one-story LLM judgment (never scheduled; result labelled [unvalidated])."),
9955
- refresh: z22.boolean().optional().describe(
9512
+ entryId: z21.string().max(200).optional().describe("For 'check'/'re-evaluate': entry ID, e.g. 'TEN-graph-db', '<PREFIX>-<n>'."),
9513
+ context: z21.enum(["capture", "commit", "review"]).default("review").optional().describe("For re-evaluate: evaluation context"),
9514
+ collection: z21.string().max(200).optional().describe("For 'verify-chain': collection slug to verify (default: glossary)."),
9515
+ mode: z21.enum(["report", "fix"]).optional().describe("For 'verify-chain': 'report' = read-only trust report (default). 'fix' = also update drifted codeMapping statuses."),
9516
+ story: z21.boolean().optional().describe("For 'spine-check': also run the on-demand one-story LLM judgment (never scheduled; result labelled [unvalidated])."),
9517
+ refresh: z21.boolean().optional().describe(
9956
9518
  "For 'spine-check': force a new run even if a fresh verdict already exists. Default false \u2014 a plain call NEVER re-triggers work just to read the last result; it auto-schedules only when no run is already in flight AND the stored verdict is absent/stale/missing the requested story pass."
9957
9519
  )
9958
9520
  });
9959
- var qualityCheckVariant = z22.object({ action: z22.literal("check"), entryId: z22.string().max(200) });
9960
- var qualityReEvaluateVariant = z22.object({
9961
- action: z22.literal("re-evaluate"),
9962
- entryId: z22.string().max(200),
9963
- context: z22.enum(["capture", "commit", "review"]).optional().default("review")
9521
+ var qualityCheckVariant = z21.object({ action: z21.literal("check"), entryId: z21.string().max(200) });
9522
+ var qualityReEvaluateVariant = z21.object({
9523
+ action: z21.literal("re-evaluate"),
9524
+ entryId: z21.string().max(200),
9525
+ context: z21.enum(["capture", "commit", "review"]).optional().default("review")
9964
9526
  });
9965
- var qualityVerifyChainVariant = z22.object({
9966
- action: z22.literal("verify-chain"),
9967
- collection: z22.string().max(200).optional().default("glossary"),
9968
- mode: z22.enum(["report", "fix"]).optional().default("report")
9527
+ var qualityVerifyChainVariant = z21.object({
9528
+ action: z21.literal("verify-chain"),
9529
+ collection: z21.string().max(200).optional().default("glossary"),
9530
+ mode: z21.enum(["report", "fix"]).optional().default("report")
9969
9531
  });
9970
- var qualitySpineCheckVariant = z22.object({
9971
- action: z22.literal("spine-check"),
9972
- story: z22.boolean().optional().default(false),
9973
- refresh: z22.boolean().optional().default(false)
9532
+ var qualitySpineCheckVariant = z21.object({
9533
+ action: z21.literal("spine-check"),
9534
+ story: z21.boolean().optional().default(false),
9535
+ refresh: z21.boolean().optional().default(false)
9974
9536
  });
9975
- var qualityActionUnion = z22.discriminatedUnion("action", [
9537
+ var qualityActionUnion = z21.discriminatedUnion("action", [
9976
9538
  qualityCheckVariant,
9977
9539
  qualityReEvaluateVariant,
9978
9540
  qualityVerifyChainVariant,
@@ -9984,43 +9546,43 @@ var QUALITY_ACTION_SPECS = {
9984
9546
  "verify-chain": { params: ["collection", "mode"], description: "All params optional; collection defaults to 'glossary'." },
9985
9547
  "spine-check": { params: ["story", "refresh"], description: "All params optional; story and refresh default to false \u2014 a plain call never re-triggers work, it just reads back the latest verdict (auto-refreshing only when stale/absent)." }
9986
9548
  };
9987
- var qualityCheckOutputSchema = z22.object({
9988
- entryId: z22.string(),
9549
+ var qualityCheckOutputSchema = z21.object({
9550
+ entryId: z21.string(),
9989
9551
  /** WP-480 S1: false = the ID does not resolve to an entry (typo/deleted) — distinct from "no verdict yet". */
9990
- entryFound: z22.boolean().optional(),
9991
- hasVerdict: z22.boolean(),
9552
+ entryFound: z21.boolean().optional(),
9553
+ hasVerdict: z21.boolean(),
9992
9554
  /** WP-480 S1: the verdict was judged against content the entry no longer has — re-evaluate for a current one. */
9993
- stale: z22.boolean().optional(),
9994
- tier: z22.string().optional(),
9995
- passed: z22.boolean().optional(),
9996
- criteria: z22.array(z22.object({
9997
- id: z22.string(),
9998
- passed: z22.boolean(),
9999
- hint: z22.string().optional()
9555
+ stale: z21.boolean().optional(),
9556
+ tier: z21.string().optional(),
9557
+ passed: z21.boolean().optional(),
9558
+ criteria: z21.array(z21.object({
9559
+ id: z21.string(),
9560
+ passed: z21.boolean(),
9561
+ hint: z21.string().optional()
10000
9562
  }))
10001
9563
  });
10002
- var qualityReevaluateOutputSchema = z22.object({
10003
- entryId: z22.string(),
10004
- context: z22.string(),
10005
- score: z22.number(),
10006
- maxScore: z22.number(),
10007
- improved: z22.boolean()
9564
+ var qualityReevaluateOutputSchema = z21.object({
9565
+ entryId: z21.string(),
9566
+ context: z21.string(),
9567
+ score: z21.number(),
9568
+ maxScore: z21.number(),
9569
+ improved: z21.boolean()
10008
9570
  });
10009
- var qualitySpineCheckOutputSchema = z22.object({
10010
- hasVerdict: z22.boolean(),
10011
- noSpineEntries: z22.boolean().optional(),
10012
- structuralPassed: z22.boolean().optional(),
10013
- storyPassed: z22.boolean().nullable().optional(),
10014
- stale: z22.boolean().optional(),
9571
+ var qualitySpineCheckOutputSchema = z21.object({
9572
+ hasVerdict: z21.boolean(),
9573
+ noSpineEntries: z21.boolean().optional(),
9574
+ structuralPassed: z21.boolean().optional(),
9575
+ storyPassed: z21.boolean().nullable().optional(),
9576
+ stale: z21.boolean().optional(),
10015
9577
  // TASK-156: a capped membership scan must stay distinguishable from a complete one, and a
10016
9578
  // malformed verdict needs its own discriminator — both were silently stripped before this.
10017
- staleMemberIds: z22.array(z22.string()).optional(),
10018
- membershipTruncated: z22.boolean().optional(),
9579
+ staleMemberIds: z21.array(z21.string()).optional(),
9580
+ membershipTruncated: z21.boolean().optional(),
10019
9581
  // PRRT_kwDORS4ils6i_PVS: a KNOWN relations-fingerprint mismatch, distinct from "unknown" —
10020
9582
  // without this, a detected topology change and an unverifiable legacy fingerprint were
10021
9583
  // indistinguishable in structured output.
10022
- relationsChanged: z22.boolean().optional(),
10023
- malformed: z22.boolean().optional()
9584
+ relationsChanged: z21.boolean().optional(),
9585
+ malformed: z21.boolean().optional()
10024
9586
  });
10025
9587
  function registerQualityTools(server) {
10026
9588
  const qualityHandlers = {
@@ -10176,10 +9738,10 @@ async function handleReEvaluate(entryId, context) {
10176
9738
  }
10177
9739
 
10178
9740
  // src/tools/session.ts
10179
- import { z as z25 } from "zod/v3";
9741
+ import { z as z24 } from "zod/v3";
10180
9742
 
10181
9743
  // src/tools/wrapup.ts
10182
- import { z as z23 } from "zod/v3";
9744
+ import { z as z22 } from "zod/v3";
10183
9745
 
10184
9746
  // src/lib/compose-wrapup-view.ts
10185
9747
  function toComposed(e) {
@@ -10467,8 +10029,8 @@ async function runWrapupCommitAll(data) {
10467
10029
  overflowUnscanned
10468
10030
  };
10469
10031
  }
10470
- var wrapupSchema = z23.object({
10471
- action: z23.enum(["review", "commit-all"]).optional().describe(
10032
+ var wrapupSchema = z22.object({
10033
+ action: z22.enum(["review", "commit-all"]).optional().describe(
10472
10034
  "Action to perform. 'review' (default) shows the wrapup summary. 'commit-all' accepts all uncommitted drafts; it writes no relations."
10473
10035
  )
10474
10036
  });
@@ -10561,26 +10123,26 @@ ${text}` : text;
10561
10123
  }
10562
10124
 
10563
10125
  // src/tools/facilitate.ts
10564
- import { z as z24 } from "zod/v3";
10126
+ import { z as z23 } from "zod/v3";
10565
10127
  var FACILITATE_ACTIONS = ["resume", "commit-constellation"];
10566
- var coherencyAcknowledgementSchema = z24.object({
10567
- response: z24.string().max(200).describe("Acknowledgement response per offender: 'linked' | 'accepted-fix' | 'diverged'."),
10568
- entryId: z24.string().max(200).optional().describe("Entry the acknowledgement links to (e.g. the strategic spine entry)."),
10569
- reason: z24.string().max(2e3).optional().describe("Free-text justification, required for a 'diverged' response."),
10570
- subjectEntryId: z24.string().max(200).optional().describe("The refused entry this acknowledgement answers.")
10128
+ var coherencyAcknowledgementSchema = z23.object({
10129
+ response: z23.string().max(200).describe("Acknowledgement response per offender: 'linked' | 'accepted-fix' | 'diverged'."),
10130
+ entryId: z23.string().max(200).optional().describe("Entry the acknowledgement links to (e.g. the strategic spine entry)."),
10131
+ reason: z23.string().max(2e3).optional().describe("Free-text justification, required for a 'diverged' response."),
10132
+ subjectEntryId: z23.string().max(200).optional().describe("The refused entry this acknowledgement answers.")
10571
10133
  });
10572
- var facilitateSchema = z24.object({
10573
- action: z24.enum(FACILITATE_ACTIONS).describe(
10134
+ var facilitateSchema = z23.object({
10135
+ action: z23.enum(FACILITATE_ACTIONS).describe(
10574
10136
  "'resume': load session state from an existing bet entry. 'commit-constellation': atomically accept a bet and all its linked draft entries in one call. Requires betEntryId."
10575
10137
  ),
10576
- betEntryId: z24.string().max(200).optional().describe("Bet entry ID. Required for both actions."),
10577
- operationId: z24.string().max(200).optional().describe("Optional idempotency key for commit-constellation retries."),
10138
+ betEntryId: z23.string().max(200).optional().describe("Bet entry ID. Required for both actions."),
10139
+ operationId: z23.string().max(200).optional().describe("Optional idempotency key for commit-constellation retries."),
10578
10140
  // WP-465 slice ⑤: coherency retry controls for a COHERENCY_REFUSED constellation hold.
10579
10141
  // Forwarded verbatim to agentKnowledge.facilitateEnvelope (validation lives at the gate).
10580
- coherencyAcknowledgements: z24.array(coherencyAcknowledgementSchema).max(20).optional().describe(
10142
+ coherencyAcknowledgements: z23.array(coherencyAcknowledgementSchema).max(20).optional().describe(
10581
10143
  "Per-offender acknowledgements to retry a constellation held under standard/strict coherency mode. Each: {subjectEntryId, response: 'linked' | 'accepted-fix' | 'diverged', entryId?, reason?}."
10582
10144
  ),
10583
- steeringOverrideReason: z24.string().max(2e3).optional().describe(
10145
+ steeringOverrideReason: z23.string().max(2e3).optional().describe(
10584
10146
  "Typed override reason (>= 12 chars) to push a constellation past a coherency hold instead of acknowledging."
10585
10147
  )
10586
10148
  });
@@ -10826,6 +10388,95 @@ No constellation entries were accepted.`
10826
10388
  };
10827
10389
  }
10828
10390
 
10391
+ // src/lib/sessionNotices.ts
10392
+ var ANSI_AND_CONTROL_RE2 = /\x1b\[[0-9;]*[A-Za-z]|\x1b\][^\x07]*\x07|\x1b[^[\]0-9;A-Za-z]?|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g;
10393
+ function safe(str) {
10394
+ return str ? str.replace(ANSI_AND_CONTROL_RE2, "") : "";
10395
+ }
10396
+ var FEEDBACK_SUBMIT_HINT = "Friction or ideas? Use `feedback action=submit` \u2014 describe how pb behaved, not your workspace content. Once screening completes, it enters the builders' triage queue.";
10397
+ function nextActionForVerb(verb, args) {
10398
+ switch (verb) {
10399
+ case "feedback.list":
10400
+ return { tool: "feedback", description: "Read your workspace's own feedback (all statuses)", parameters: { action: "list" } };
10401
+ case "feedback.queue":
10402
+ return { tool: "feedback", description: "Read the vendor triage queue (system admins only)", parameters: { action: "queue" } };
10403
+ case "question.create":
10404
+ return { tool: "question", description: "Create a routed question", parameters: { action: "create" } };
10405
+ case "question.adopt":
10406
+ return { tool: "question", description: "Attach routing to a captured tension", parameters: { action: "adopt" } };
10407
+ case "question.assign":
10408
+ return { tool: "question", description: "Confirm or reassign a question", parameters: { action: "assign" } };
10409
+ case "question.snooze":
10410
+ return { tool: "question", description: "Snooze a routed/escalated question", parameters: { action: "snooze" } };
10411
+ case "question.decline":
10412
+ return { tool: "question", description: "Pass on or decline a question", parameters: { action: "decline" } };
10413
+ case "question.answer":
10414
+ return { tool: "question", description: "Answer a question", parameters: { action: "answer" } };
10415
+ case "question.forceClose":
10416
+ return { tool: "question", description: "Team-Lead-only force-close", parameters: { action: "force-close" } };
10417
+ case "question.list":
10418
+ return { tool: "question", description: args.mine === true ? "List every question you asked" : "List open questions", parameters: { action: "list", ...args.mine === true ? { mine: true } : {} } };
10419
+ default:
10420
+ return null;
10421
+ }
10422
+ }
10423
+ function noticesToNextActions(notices) {
10424
+ if (!notices || notices.length === 0) return [];
10425
+ const actions2 = notices.map((n) => nextActionForVerb(n.action.verb, n.action.args)).filter((a) => a !== null);
10426
+ const seen = /* @__PURE__ */ new Set();
10427
+ return actions2.filter((a) => {
10428
+ const key = `${a.tool}:${JSON.stringify(a.parameters)}`;
10429
+ if (seen.has(key)) return false;
10430
+ seen.add(key);
10431
+ return true;
10432
+ });
10433
+ }
10434
+ var QUESTION_ACK_GATE_SOURCE_ID = "question-ack-gate";
10435
+ function displayVerbFromRoute(route) {
10436
+ switch (route) {
10437
+ case "question.assign":
10438
+ return "take it / reassign";
10439
+ case "question.decline":
10440
+ return "pass / decline";
10441
+ case "question.forceClose":
10442
+ return "force-close";
10443
+ default:
10444
+ return route.replace(/^question\./, "");
10445
+ }
10446
+ }
10447
+ function formatQuestionBrief(q) {
10448
+ const lines = [];
10449
+ lines.push(` [${safe(q.sourcePointer)}] ${safe(q.name)} \u2014 ${safe(q.whyYou)}, ${q.ageDays}d`);
10450
+ lines.push(` "${safe(q.question)}" \u2014 asked by ${safe(q.asker)}${q.domain ? `, ${safe(q.domain)} domain` : ""}`);
10451
+ if (q.dependents.count > 0) {
10452
+ const names = q.dependents.named.map((d) => `${safe(d.name)} (${safe(d.id)})`).join(", ");
10453
+ const countLabel = q.dependents.truncated ? `${q.dependents.count}+` : String(q.dependents.count);
10454
+ lines.push(` blocks ${countLabel} item(s)${names ? `: ${names}` : ""}`);
10455
+ }
10456
+ if (q.declineContext) lines.push(` declined: ${safe(q.declineContext)}`);
10457
+ if (q.answerContext) lines.push(` ${safe(q.answerContext)}`);
10458
+ const verbList = [...new Set(q.actions.map((a) => safe(a.label) || displayVerbFromRoute(a.verb)))].join(" \xB7 ") || "answer";
10459
+ lines.push(` ${verbList} -> question action=answer entryId=${safe(q.sourcePointer)} text="<your answer>"`);
10460
+ return lines;
10461
+ }
10462
+ function formatQuestionAckGateBlock(notice) {
10463
+ const questions = notice.payload?.questions ?? [];
10464
+ const lines = [];
10465
+ lines.push(`- ${questions.length} open question${questions.length === 1 ? "" : "s"} need you before you continue.`);
10466
+ for (const q of questions) lines.push(...formatQuestionBrief(q));
10467
+ lines.push(" Deal with each question above before proceeding.");
10468
+ if (notice.payload?.truncated) {
10469
+ lines.push(" (This list may be incomplete \u2014 you may have more open questions than shown.)");
10470
+ }
10471
+ return lines;
10472
+ }
10473
+ function formatNoticeLines(notices) {
10474
+ if (!notices || notices.length === 0) return [];
10475
+ return notices.flatMap(
10476
+ (n) => n.source === QUESTION_ACK_GATE_SOURCE_ID && n.payload ? formatQuestionAckGateBlock(n) : [`- ${safe(n.subject)} (${safe(n.change)})`]
10477
+ );
10478
+ }
10479
+
10829
10480
  // src/tools/session.ts
10830
10481
  var SESSION_ACTIONS = [
10831
10482
  "start",
@@ -10836,44 +10487,44 @@ var SESSION_ACTIONS = [
10836
10487
  "resume",
10837
10488
  "commit-constellation"
10838
10489
  ];
10839
- var coherencyAcknowledgementFlatSchema2 = z25.object({
10840
- response: z25.string().max(200).describe("Acknowledgement response per offender: 'linked' | 'accepted-fix' | 'diverged'."),
10841
- entryId: z25.string().max(200).optional().describe("Entry the acknowledgement links to."),
10842
- reason: z25.string().max(2e3).optional().describe("Free-text justification, required for a 'diverged' response."),
10843
- subjectEntryId: z25.string().max(200).optional().describe("The refused entry this acknowledgement answers.")
10490
+ var coherencyAcknowledgementFlatSchema2 = z24.object({
10491
+ response: z24.string().max(200).describe("Acknowledgement response per offender: 'linked' | 'accepted-fix' | 'diverged'."),
10492
+ entryId: z24.string().max(200).optional().describe("Entry the acknowledgement links to."),
10493
+ reason: z24.string().max(2e3).optional().describe("Free-text justification, required for a 'diverged' response."),
10494
+ subjectEntryId: z24.string().max(200).optional().describe("The refused entry this acknowledgement answers.")
10844
10495
  });
10845
- var sessionSchema = z25.object({
10846
- action: z25.enum(SESSION_ACTIONS).describe(
10496
+ var sessionSchema = z24.object({
10497
+ action: z24.enum(SESSION_ACTIONS).describe(
10847
10498
  "'start': begin a tracked session. 'close': end the session and record activity. 'status': check current session state. 'wrapup-review': review uncommitted drafts before closing (absorbs session-wrapup action=review). 'wrapup-commit': accept all uncommitted drafts (absorbs session-wrapup action=commit-all). 'resume': load session state from an existing bet entry (absorbs facilitate action=resume). 'commit-constellation': atomically accept a bet and its linked drafts (absorbs facilitate action=commit-constellation)."
10848
10499
  ),
10849
- betEntryId: z25.string().max(200).optional().describe("For 'resume'/'commit-constellation': bet entry ID. Required for both."),
10850
- operationId: z25.string().max(200).optional().describe("For 'commit-constellation': optional idempotency key for retries."),
10851
- coherencyAcknowledgements: z25.array(coherencyAcknowledgementFlatSchema2).max(20).optional().describe(
10500
+ betEntryId: z24.string().max(200).optional().describe("For 'resume'/'commit-constellation': bet entry ID. Required for both."),
10501
+ operationId: z24.string().max(200).optional().describe("For 'commit-constellation': optional idempotency key for retries."),
10502
+ coherencyAcknowledgements: z24.array(coherencyAcknowledgementFlatSchema2).max(20).optional().describe(
10852
10503
  "For 'commit-constellation': per-offender acknowledgements to retry a constellation held under standard/strict coherency mode."
10853
10504
  ),
10854
- steeringOverrideReason: z25.string().max(2e3).optional().describe(
10505
+ steeringOverrideReason: z24.string().max(2e3).optional().describe(
10855
10506
  "For 'commit-constellation': typed override reason (>= 12 chars) to push past a coherency hold instead of acknowledging."
10856
10507
  ),
10857
10508
  // DEC-1715 (WP-753 O1a)
10858
- role: z25.string().max(200).optional().describe(
10509
+ role: z24.string().max(200).optional().describe(
10859
10510
  "For 'start': role to act as this session, by the '<PREFIX>-<n>' id a start reply prints beside the role name \u2014 only needed if you hold more than one; the error lists your roles if omitted."
10860
10511
  )
10861
10512
  });
10862
- var sessionStartVariant = z25.object({
10863
- action: z25.literal("start"),
10513
+ var sessionStartVariant = z24.object({
10514
+ action: z24.literal("start"),
10864
10515
  // DEC-1715 (WP-753 O1a): the role to act as this session — only needed when the person holds
10865
10516
  // more than one role; the server refuses and lists the choices when it's required and omitted.
10866
- role: z25.string().max(200).optional().describe(
10517
+ role: z24.string().max(200).optional().describe(
10867
10518
  "Role to act as this session, by the '<PREFIX>-<n>' id a start reply prints beside the role name \u2014 only needed if you hold more than one; the error lists your roles if omitted."
10868
10519
  )
10869
10520
  });
10870
- var sessionCloseVariant = z25.object({ action: z25.literal("close") });
10871
- var sessionStatusVariant = z25.object({ action: z25.literal("status") });
10872
- var sessionWrapupReviewVariant = z25.object({ action: z25.literal("wrapup-review") });
10873
- var sessionWrapupCommitVariant = z25.object({ action: z25.literal("wrapup-commit") });
10874
- var sessionResumeVariant = facilitateSchema.omit({ action: true }).extend({ action: z25.literal("resume") });
10875
- var sessionCommitConstellationVariant = facilitateSchema.omit({ action: true }).extend({ action: z25.literal("commit-constellation") });
10876
- var sessionActionUnion = z25.discriminatedUnion("action", [
10521
+ var sessionCloseVariant = z24.object({ action: z24.literal("close") });
10522
+ var sessionStatusVariant = z24.object({ action: z24.literal("status") });
10523
+ var sessionWrapupReviewVariant = z24.object({ action: z24.literal("wrapup-review") });
10524
+ var sessionWrapupCommitVariant = z24.object({ action: z24.literal("wrapup-commit") });
10525
+ var sessionResumeVariant = facilitateSchema.omit({ action: true }).extend({ action: z24.literal("resume") });
10526
+ var sessionCommitConstellationVariant = facilitateSchema.omit({ action: true }).extend({ action: z24.literal("commit-constellation") });
10527
+ var sessionActionUnion = z24.discriminatedUnion("action", [
10877
10528
  sessionStartVariant,
10878
10529
  sessionCloseVariant,
10879
10530
  sessionStatusVariant,
@@ -10910,7 +10561,7 @@ function registerSessionTools(server) {
10910
10561
  "session",
10911
10562
  {
10912
10563
  title: "Session \u2014 Lifecycle & Facilitation",
10913
- description: "Session lifecycle, wrapup review, and work package facilitation.\n\n- **start**: Begin a tracked write session for attribution. **This is the ONLY call that creates a session** \u2014 write tools (capture, entries action=update/commit, relations) require it. `orient action=start` orients an existing session and loads stage-aware context, but does NOT create one: call `session action=start` first, then `orient action=start`.\n- **close**: End the current session. Records structured data (entries created, modified, relations). Run `session action=wrapup-review` before closing.\n- **status**: Check current session \u2014 active or not, oriented, activity counts.\n- **wrapup-review**: Review uncommitted drafts with quality scores. Run before close.\n- **wrapup-commit**: Accept all uncommitted drafts. Writes no relations; create the ones you want with relations action=create.\n- **resume**: Load session state (work package data, constellation, drafts) from an existing work package entry.\n- **commit-constellation**: Atomically accept a work package and all its linked draft entries in one call.",
10564
+ description: 'Session lifecycle, wrapup review, and work package facilitation.\n\n- **start**: Begin a tracked write session for attribution. **This is the ONLY call that creates a session** \u2014 write tools (capture, entries action=update/commit, relations) require it. Its reply is the briefing; then `orient task="<what you are doing>"` loads the context for a task.\n- **close**: End the current session. Records structured data (entries created, modified, relations). Run `session action=wrapup-review` before closing.\n- **status**: Check current session \u2014 active or not, oriented, activity counts.\n- **wrapup-review**: Review uncommitted drafts with quality scores. Run before close.\n- **wrapup-commit**: Accept all uncommitted drafts. Writes no relations; create the ones you want with relations action=create.\n- **resume**: Load session state (work package data, constellation, drafts) from an existing work package entry.\n- **commit-constellation**: Atomically accept a work package and all its linked draft entries in one call.',
10914
10565
  inputSchema: sessionSchema,
10915
10566
  // Mixed read/write noun (§3 R3): wrapup-commit/resume/commit-constellation write.
10916
10567
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
@@ -10965,14 +10616,11 @@ async function handleStart2(data) {
10965
10616
  // TASK-436
10966
10617
  briefing: result2.briefing
10967
10618
  // TASK-552
10968
- // Finding #16: bare `parameters: {}` lets orient's schema default kick in
10969
- // (action defaults to "task", which requires a `task` string) — the suggested
10970
- // next-action call failed validation instead of running. `action: "start"` is
10971
- // the guided-setup entrypoint this session just opened write access for.
10619
+ // TASK-670 (WP-820): orient's only action is `task`; the agent fills in the task it is doing.
10972
10620
  // FEAT-1409 spine core: notice next-actions are appended through this SAME existing
10973
10621
  // `next` field (HARD CONSTRAINT 2) — never a parallel action list.
10974
10622
  // TASK-583 (DEC-1907): every next action carries the sessionId, since the next call may arrive on a new connection.
10975
- }, [{ tool: "orient", description: "Orient session", parameters: { action: "start" } }, ...noticesToNextActions(result2.notices)].map((next) => ({ ...next, parameters: { ...next.parameters, sessionId: result2.sessionId } })))
10623
+ }, [{ tool: "orient", description: "Load the context for your task: pass task", parameters: { action: "task" } }, ...noticesToNextActions(result2.notices)].map((next) => ({ ...next, parameters: { ...next.parameters, sessionId: result2.sessionId } })))
10976
10624
  };
10977
10625
  }
10978
10626
  async function handleClose() {
@@ -11127,10 +10775,10 @@ async function handleStatus() {
11127
10775
  }
11128
10776
 
11129
10777
  // src/tools/workspace.ts
11130
- import { z as z28 } from "zod/v3";
10778
+ import { z as z27 } from "zod/v3";
11131
10779
 
11132
10780
  // src/tools/health.ts
11133
- import { z as z26 } from "zod/v3";
10781
+ import { z as z25 } from "zod/v3";
11134
10782
 
11135
10783
  // src/lib/auditView.ts
11136
10784
  async function buildTenantAuditView(limit) {
@@ -11474,60 +11122,60 @@ ${logLines.join("\n")}${seamSummaryText}` }],
11474
11122
  };
11475
11123
  }
11476
11124
  var HEALTH_ACTIONS = ["check", "whoami", "status", "audit", "self-test"];
11477
- var healthSchema = z26.object({
11478
- action: z26.enum(HEALTH_ACTIONS).describe(
11125
+ var healthSchema = z25.object({
11126
+ action: z25.enum(HEALTH_ACTIONS).describe(
11479
11127
  "'check': connectivity and workspace stats. 'whoami': session identity. 'status': workspace readiness. 'audit': session audit log. 'self-test': validate all tool schemas."
11480
11128
  ),
11481
- limit: z26.number().min(1).max(50).default(20).optional().describe("For audit: how many recent calls to show (max 50)")
11129
+ limit: z25.number().min(1).max(50).default(20).optional().describe("For audit: how many recent calls to show (max 50)")
11482
11130
  });
11483
- var healthCheckOutputSchema = z26.object({
11484
- healthy: z26.boolean(),
11485
- collections: z26.number(),
11486
- entries: z26.number(),
11487
- latencyMs: z26.number(),
11488
- workspace: z26.string()
11131
+ var healthCheckOutputSchema = z25.object({
11132
+ healthy: z25.boolean(),
11133
+ collections: z25.number(),
11134
+ entries: z25.number(),
11135
+ latencyMs: z25.number(),
11136
+ workspace: z25.string()
11489
11137
  });
11490
- var organisationHealthSchema = z26.object({
11491
- reviewed: z26.number(),
11492
- agreements: z26.number(),
11493
- disagreements: z26.number(),
11494
- abstentions: z26.number(),
11495
- agreementRate: z26.number(),
11496
- flags: z26.array(z26.object({ collection: z26.string(), count: z26.number(), suggestedCollection: z26.string() }))
11138
+ var organisationHealthSchema = z25.object({
11139
+ reviewed: z25.number(),
11140
+ agreements: z25.number(),
11141
+ disagreements: z25.number(),
11142
+ abstentions: z25.number(),
11143
+ agreementRate: z25.number(),
11144
+ flags: z25.array(z25.object({ collection: z25.string(), count: z25.number(), suggestedCollection: z25.string() }))
11497
11145
  });
11498
- var healthStatusOutputSchema = z26.object({
11499
- sampleTruncated: z26.boolean().nullable().optional(),
11500
- activeEntries: z26.number(),
11501
- totalRelations: z26.number(),
11502
- orphanedEntries: z26.number(),
11503
- checks: z26.array(z26.object({ label: z26.string(), passed: z26.boolean(), current: z26.number(), required: z26.number() })),
11504
- gaps: z26.array(z26.object({ id: z26.string(), label: z26.string(), guidance: z26.string() })),
11146
+ var healthStatusOutputSchema = z25.object({
11147
+ sampleTruncated: z25.boolean().nullable().optional(),
11148
+ activeEntries: z25.number(),
11149
+ totalRelations: z25.number(),
11150
+ orphanedEntries: z25.number(),
11151
+ checks: z25.array(z25.object({ label: z25.string(), passed: z25.boolean(), current: z25.number(), required: z25.number() })),
11152
+ gaps: z25.array(z25.object({ id: z25.string(), label: z25.string(), guidance: z25.string() })),
11505
11153
  organisationHealth: organisationHealthSchema.optional()
11506
11154
  });
11507
- var healthAuditOutputSchema = z26.object({
11508
- totalCalls: z26.number(),
11509
- calls: z26.array(z26.object({
11510
- tool: z26.string(),
11511
- action: z26.string().optional(),
11512
- timestamp: z26.string(),
11513
- durationMs: z26.number().optional()
11155
+ var healthAuditOutputSchema = z25.object({
11156
+ totalCalls: z25.number(),
11157
+ calls: z25.array(z25.object({
11158
+ tool: z25.string(),
11159
+ action: z25.string().optional(),
11160
+ timestamp: z25.string(),
11161
+ durationMs: z25.number().optional()
11514
11162
  }))
11515
11163
  });
11516
- var healthWhoamiOutputSchema = z26.object({
11517
- workspaceId: z26.string(),
11518
- workspaceName: z26.string(),
11519
- scope: z26.string(),
11520
- sessionId: z26.union([z26.string(), z26.null()]),
11521
- oriented: z26.boolean()
11164
+ var healthWhoamiOutputSchema = z25.object({
11165
+ workspaceId: z25.string(),
11166
+ workspaceName: z25.string(),
11167
+ scope: z25.string(),
11168
+ sessionId: z25.union([z25.string(), z25.null()]),
11169
+ oriented: z25.boolean()
11522
11170
  });
11523
- var selfTestOutputSchema = z26.object({
11524
- passed: z26.number(),
11525
- failed: z26.number(),
11526
- total: z26.number(),
11527
- results: z26.array(z26.object({
11528
- tool: z26.string(),
11529
- valid: z26.boolean(),
11530
- error: z26.string().optional()
11171
+ var selfTestOutputSchema = z25.object({
11172
+ passed: z25.number(),
11173
+ failed: z25.number(),
11174
+ total: z25.number(),
11175
+ results: z25.array(z25.object({
11176
+ tool: z25.string(),
11177
+ valid: z25.boolean(),
11178
+ error: z25.string().optional()
11531
11179
  }))
11532
11180
  });
11533
11181
  function handleSelfTest(server) {
@@ -11578,9 +11226,9 @@ function handleSelfTest(server) {
11578
11226
  }
11579
11227
 
11580
11228
  // src/tools/usage.ts
11581
- import { z as z27 } from "zod/v3";
11582
- var usageSummarySchema = z27.object({
11583
- periodDays: z27.number().min(1).max(90).optional().describe("Number of days to look back (default 30, max 90)")
11229
+ import { z as z26 } from "zod/v3";
11230
+ var usageSummarySchema = z26.object({
11231
+ periodDays: z26.number().min(1).max(90).optional().describe("Number of days to look back (default 30, max 90)")
11584
11232
  });
11585
11233
  async function handleUsageSummary(periodDays) {
11586
11234
  const ws = await getWorkspaceContext();
@@ -11650,20 +11298,20 @@ var WORKSPACE_ACTIONS = [
11650
11298
  "self-test",
11651
11299
  "usage"
11652
11300
  ];
11653
- var workspaceSchema = z28.object({
11654
- action: z28.enum(WORKSPACE_ACTIONS).describe(
11301
+ var workspaceSchema = z27.object({
11302
+ action: z27.enum(WORKSPACE_ACTIONS).describe(
11655
11303
  "'check': connectivity and workspace stats (absorbs health action=check). 'whoami': session identity (absorbs health action=whoami). 'status': workspace readiness (absorbs health action=status). 'audit': session audit log (absorbs health action=audit). 'self-test': validate all tool schemas (absorbs health action=self-test). 'usage': LLM usage and cost summary (absorbs get-usage-summary)."
11656
11304
  ),
11657
- limit: z28.number().min(1).max(50).optional().describe("For 'audit': how many recent calls to show (max 50, default 20)."),
11658
- periodDays: z28.number().min(1).max(90).optional().describe("For 'usage': number of days to look back (default 30, max 90).")
11305
+ limit: z27.number().min(1).max(50).optional().describe("For 'audit': how many recent calls to show (max 50, default 20)."),
11306
+ periodDays: z27.number().min(1).max(90).optional().describe("For 'usage': number of days to look back (default 30, max 90).")
11659
11307
  });
11660
- var workspaceCheckVariant = z28.object({ action: z28.literal("check") });
11661
- var workspaceWhoamiVariant = z28.object({ action: z28.literal("whoami") });
11662
- var workspaceStatusVariant = z28.object({ action: z28.literal("status") });
11663
- var workspaceAuditVariant = z28.object({ action: z28.literal("audit"), limit: z28.number().min(1).max(50).optional().default(20) });
11664
- var workspaceSelfTestVariant = z28.object({ action: z28.literal("self-test") });
11665
- var workspaceUsageVariant = z28.object({ action: z28.literal("usage"), periodDays: z28.number().min(1).max(90).optional() });
11666
- var workspaceActionUnion = z28.discriminatedUnion("action", [
11308
+ var workspaceCheckVariant = z27.object({ action: z27.literal("check") });
11309
+ var workspaceWhoamiVariant = z27.object({ action: z27.literal("whoami") });
11310
+ var workspaceStatusVariant = z27.object({ action: z27.literal("status") });
11311
+ var workspaceAuditVariant = z27.object({ action: z27.literal("audit"), limit: z27.number().min(1).max(50).optional().default(20) });
11312
+ var workspaceSelfTestVariant = z27.object({ action: z27.literal("self-test") });
11313
+ var workspaceUsageVariant = z27.object({ action: z27.literal("usage"), periodDays: z27.number().min(1).max(90).optional() });
11314
+ var workspaceActionUnion = z27.discriminatedUnion("action", [
11667
11315
  workspaceCheckVariant,
11668
11316
  workspaceWhoamiVariant,
11669
11317
  workspaceStatusVariant,
@@ -11709,7 +11357,7 @@ function registerWorkspaceTools(server) {
11709
11357
  }
11710
11358
 
11711
11359
  // src/tools/feedback.ts
11712
- import { z as z29 } from "zod/v3";
11360
+ import { z as z28 } from "zod/v3";
11713
11361
 
11714
11362
  // src/lib/productFeedbackConstants.ts
11715
11363
  var PRODUCT_FEEDBACK_CATEGORIES = ["bug", "friction", "idea", "praise", "other"];
@@ -11720,37 +11368,37 @@ var VENDOR_SETTABLE_STATUSES = PRODUCT_FEEDBACK_STATUSES.filter(
11720
11368
 
11721
11369
  // src/tools/feedback.ts
11722
11370
  var actions = ["submit", "list", "queue", "note", "group", "status"];
11723
- var category = z29.enum(PRODUCT_FEEDBACK_CATEGORIES);
11724
- var status = z29.enum(PRODUCT_FEEDBACK_STATUSES);
11725
- var vendorStatus = z29.enum(VENDOR_SETTABLE_STATUSES);
11371
+ var category = z28.enum(PRODUCT_FEEDBACK_CATEGORIES);
11372
+ var status = z28.enum(PRODUCT_FEEDBACK_STATUSES);
11373
+ var vendorStatus = z28.enum(VENDOR_SETTABLE_STATUSES);
11726
11374
  var MESSAGE_DISPLAY_LIMIT = 1e3;
11727
11375
  var FULL_MESSAGE_MAX_LIMIT = 5;
11728
- var feedbackSchema = z29.object({
11729
- action: z29.enum(actions).describe("submit requires message; list returns your own workspace's feedback (all statuses); queue is the vendor triage queue (system admins only) and accepts filters; note requires feedbackId+note; group requires feedbackIds+groupId (null clears); status requires feedbackId+status."),
11730
- message: z29.string().max(1e4).optional().describe("Required for submit: the product feedback text (max 10,000 characters)."),
11376
+ var feedbackSchema = z28.object({
11377
+ action: z28.enum(actions).describe("submit requires message; list returns your own workspace's feedback (all statuses); queue is the vendor triage queue (system admins only) and accepts filters; note requires feedbackId+note; group requires feedbackIds+groupId (null clears); status requires feedbackId+status."),
11378
+ message: z28.string().max(1e4).optional().describe("Required for submit: the product feedback text (max 10,000 characters)."),
11731
11379
  category: category.optional().describe("Submit/queue category; submit defaults to other."),
11732
- command: z29.string().max(1e3).optional().describe("Optional submit command context; argument values are scrubbed server-side."),
11733
- route: z29.string().max(500).optional().describe("Optional submit route context."),
11734
- client: z29.string().max(100).optional().describe("Optional submit client label."),
11735
- feedbackId: z29.string().max(200).optional().describe("Required for note/status: target feedback ID."),
11736
- feedbackIds: z29.array(z29.string().max(200)).max(100).optional().describe("Required for group: 1\u2013100 target feedback IDs."),
11737
- note: z29.string().max(4e3).optional().describe("Required for note: replacement triage note (max 4,000 characters)."),
11380
+ command: z28.string().max(1e3).optional().describe("Optional submit command context; argument values are scrubbed server-side."),
11381
+ route: z28.string().max(500).optional().describe("Optional submit route context."),
11382
+ client: z28.string().max(100).optional().describe("Optional submit client label."),
11383
+ feedbackId: z28.string().max(200).optional().describe("Required for note/status: target feedback ID."),
11384
+ feedbackIds: z28.array(z28.string().max(200)).max(100).optional().describe("Required for group: 1\u2013100 target feedback IDs."),
11385
+ note: z28.string().max(4e3).optional().describe("Required for note: replacement triage note (max 4,000 characters)."),
11738
11386
  status: status.optional().describe("list/queue filter (any status, including 'screening'); the status action's required target value excludes 'screening' (system-only)."),
11739
- groupId: z29.string().min(1).max(200).nullable().optional().describe("Group filter, or required group destination; null explicitly clears grouping."),
11740
- workspaceId: z29.string().max(200).optional().describe("Queue-only filter (vendor cross-workspace triage); omitted means all workspaces. Has no effect on list \u2014 your own workspace is always injected server-side."),
11741
- since: z29.number().optional().describe("Queue-only lower createdAt window bound (inclusive) in epoch milliseconds."),
11742
- before: z29.number().optional().describe("Queue-only upper createdAt window bound (exclusive) in epoch milliseconds. A time-window FILTER, not the pager \u2014 use cursor to page."),
11743
- limit: z29.number().int().min(1).max(100).optional().describe("list/queue row limit, default 50, maximum 100."),
11744
- cursor: z29.string().max(2e3).optional().describe("Opaque pagination cursor from a previous list/queue response (continueCursor); omit for the first page."),
11745
- full: z29.boolean().optional().describe(`list/queue only: return untruncated messages; allowed only when limit <= ${FULL_MESSAGE_MAX_LIMIT}.`)
11387
+ groupId: z28.string().min(1).max(200).nullable().optional().describe("Group filter, or required group destination; null explicitly clears grouping."),
11388
+ workspaceId: z28.string().max(200).optional().describe("Queue-only filter (vendor cross-workspace triage); omitted means all workspaces. Has no effect on list \u2014 your own workspace is always injected server-side."),
11389
+ since: z28.number().optional().describe("Queue-only lower createdAt window bound (inclusive) in epoch milliseconds."),
11390
+ before: z28.number().optional().describe("Queue-only upper createdAt window bound (exclusive) in epoch milliseconds. A time-window FILTER, not the pager \u2014 use cursor to page."),
11391
+ limit: z28.number().int().min(1).max(100).optional().describe("list/queue row limit, default 50, maximum 100."),
11392
+ cursor: z28.string().max(2e3).optional().describe("Opaque pagination cursor from a previous list/queue response (continueCursor); omit for the first page."),
11393
+ full: z28.boolean().optional().describe(`list/queue only: return untruncated messages; allowed only when limit <= ${FULL_MESSAGE_MAX_LIMIT}.`)
11746
11394
  });
11747
- var union = z29.discriminatedUnion("action", [
11748
- z29.object({ action: z29.literal("submit"), message: z29.string().min(1).max(1e4), category: category.optional(), command: z29.string().max(1e3).optional(), route: z29.string().max(500).optional(), client: z29.string().max(100).optional() }),
11749
- z29.object({ action: z29.literal("list"), status: status.optional(), limit: z29.number().int().min(1).max(100).optional(), cursor: z29.string().max(2e3).optional(), full: z29.boolean().optional() }),
11750
- z29.object({ action: z29.literal("queue"), status: status.optional(), category: category.optional(), groupId: z29.string().min(1).max(200).optional(), workspaceId: z29.string().max(200).optional(), since: z29.number().optional(), before: z29.number().optional(), limit: z29.number().int().min(1).max(100).optional(), cursor: z29.string().max(2e3).optional(), full: z29.boolean().optional() }),
11751
- z29.object({ action: z29.literal("note"), feedbackId: z29.string().min(1).max(200), note: z29.string().max(4e3) }),
11752
- z29.object({ action: z29.literal("group"), feedbackIds: z29.array(z29.string().min(1).max(200)).min(1).max(100), groupId: z29.string().min(1).max(200).nullable() }),
11753
- z29.object({ action: z29.literal("status"), feedbackId: z29.string().min(1).max(200), status: vendorStatus })
11395
+ var union = z28.discriminatedUnion("action", [
11396
+ z28.object({ action: z28.literal("submit"), message: z28.string().min(1).max(1e4), category: category.optional(), command: z28.string().max(1e3).optional(), route: z28.string().max(500).optional(), client: z28.string().max(100).optional() }),
11397
+ z28.object({ action: z28.literal("list"), status: status.optional(), limit: z28.number().int().min(1).max(100).optional(), cursor: z28.string().max(2e3).optional(), full: z28.boolean().optional() }),
11398
+ z28.object({ action: z28.literal("queue"), status: status.optional(), category: category.optional(), groupId: z28.string().min(1).max(200).optional(), workspaceId: z28.string().max(200).optional(), since: z28.number().optional(), before: z28.number().optional(), limit: z28.number().int().min(1).max(100).optional(), cursor: z28.string().max(2e3).optional(), full: z28.boolean().optional() }),
11399
+ z28.object({ action: z28.literal("note"), feedbackId: z28.string().min(1).max(200), note: z28.string().max(4e3) }),
11400
+ z28.object({ action: z28.literal("group"), feedbackIds: z28.array(z28.string().min(1).max(200)).min(1).max(100), groupId: z28.string().min(1).max(200).nullable() }),
11401
+ z28.object({ action: z28.literal("status"), feedbackId: z28.string().min(1).max(200), status: vendorStatus })
11754
11402
  ]);
11755
11403
  var specs = {
11756
11404
  submit: { params: ["message", "category", "command", "route", "client"], description: "message is required; category defaults to other." },
@@ -11877,34 +11525,34 @@ function registerFeedbackTool(server) {
11877
11525
  }
11878
11526
 
11879
11527
  // src/tools/shape.ts
11880
- import { z as z30 } from "zod/v3";
11528
+ import { z as z29 } from "zod/v3";
11881
11529
  var SHAPE_ACTIONS = ["list", "show", "agree", "dismiss"];
11882
11530
  var LIST_DISPOSITIONS = ["pending", "agreed", "dismissed", "expired"];
11883
11531
  var LIST_OUTCOMES = ["not_candidate", "atomic", "compound", "unavailable"];
11884
- var shapeSchema = z30.object({
11885
- action: z30.enum(SHAPE_ACTIONS).describe(
11532
+ var shapeSchema = z29.object({
11533
+ action: z29.enum(SHAPE_ACTIONS).describe(
11886
11534
  "'list': list shape advisories for this workspace, latest per subject. 'show': show one shape advisory by id. 'agree': agree with a compound advisory's split verdict. 'dismiss': dismiss a compound advisory's split verdict."
11887
11535
  ),
11888
- disposition: z30.enum(LIST_DISPOSITIONS).optional().describe(
11536
+ disposition: z29.enum(LIST_DISPOSITIONS).optional().describe(
11889
11537
  "For 'list': filter by disposition. Omitting both disposition and outcome defaults to the actionable set (disposition:pending, outcome:compound)."
11890
11538
  ),
11891
- outcome: z30.enum(LIST_OUTCOMES).optional().describe(
11539
+ outcome: z29.enum(LIST_OUTCOMES).optional().describe(
11892
11540
  "For 'list': filter by outcome. Omitting both disposition and outcome defaults to the actionable set (disposition:pending, outcome:compound)."
11893
11541
  ),
11894
- limit: z30.number().min(1).max(200).optional().describe("For 'list': max rows (default 50, max 200)."),
11895
- rowId: z30.string().max(200).optional().describe("For 'show'/'agree'/'dismiss': the advisory row id."),
11896
- reason: z30.string().max(1e3).optional().describe("For 'agree'/'dismiss': optional reason (capped at 1000 chars).")
11542
+ limit: z29.number().min(1).max(200).optional().describe("For 'list': max rows (default 50, max 200)."),
11543
+ rowId: z29.string().max(200).optional().describe("For 'show'/'agree'/'dismiss': the advisory row id."),
11544
+ reason: z29.string().max(1e3).optional().describe("For 'agree'/'dismiss': optional reason (capped at 1000 chars).")
11897
11545
  });
11898
- var shapeListVariant = z30.object({
11899
- action: z30.literal("list"),
11900
- disposition: z30.enum(LIST_DISPOSITIONS).optional(),
11901
- outcome: z30.enum(LIST_OUTCOMES).optional(),
11902
- limit: z30.number().min(1).max(200).optional()
11546
+ var shapeListVariant = z29.object({
11547
+ action: z29.literal("list"),
11548
+ disposition: z29.enum(LIST_DISPOSITIONS).optional(),
11549
+ outcome: z29.enum(LIST_OUTCOMES).optional(),
11550
+ limit: z29.number().min(1).max(200).optional()
11903
11551
  });
11904
- var shapeShowVariant = z30.object({ action: z30.literal("show"), rowId: z30.string().max(200) });
11905
- var shapeAgreeVariant = z30.object({ action: z30.literal("agree"), rowId: z30.string().max(200), reason: z30.string().max(1e3).optional() });
11906
- var shapeDismissVariant = z30.object({ action: z30.literal("dismiss"), rowId: z30.string().max(200), reason: z30.string().max(1e3).optional() });
11907
- var shapeActionUnion = z30.discriminatedUnion("action", [
11552
+ var shapeShowVariant = z29.object({ action: z29.literal("show"), rowId: z29.string().max(200) });
11553
+ var shapeAgreeVariant = z29.object({ action: z29.literal("agree"), rowId: z29.string().max(200), reason: z29.string().max(1e3).optional() });
11554
+ var shapeDismissVariant = z29.object({ action: z29.literal("dismiss"), rowId: z29.string().max(200), reason: z29.string().max(1e3).optional() });
11555
+ var shapeActionUnion = z29.discriminatedUnion("action", [
11908
11556
  shapeListVariant,
11909
11557
  shapeShowVariant,
11910
11558
  shapeAgreeVariant,
@@ -12050,26 +11698,26 @@ function registerShapeTools(server) {
12050
11698
  }
12051
11699
 
12052
11700
  // src/tools/question.ts
12053
- import { z as z31 } from "zod/v3";
11701
+ import { z as z30 } from "zod/v3";
12054
11702
  var QUESTION_ACTIONS = ["create", "assign", "answer", "force-close", "list"];
12055
- var questionSchema = z31.object({
12056
- action: z31.enum(QUESTION_ACTIONS).describe(
11703
+ var questionSchema = z30.object({
11704
+ action: z30.enum(QUESTION_ACTIONS).describe(
12057
11705
  "'create': create a new routed question from text \u2014 the dedicated birth action; every question is born escalated to the team lead (generic entry creation and move-and-retype also pair a routing facet). 'assign': reassign ownership to a role (toRole required \u2014 no bare self-confirm, no toPerson). 'answer': close the loop \u2014 writes the answer on the question and moves it to answered; --entry links an existing decision instead. '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)."
12058
11706
  ),
12059
- entryId: z31.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'."),
12060
- text: z31.string().max(5e3).optional().describe("'create': the question text. 'answer': the answer text (mutually exclusive with answerEntryId, max 2000 chars)."),
12061
- blocks: z31.array(z31.string().max(200)).max(50).optional().describe("'create': entry ids this question is blocking (repeatable)."),
12062
- toRole: z31.string().max(200).optional().describe("'assign': reassign to this role (human ROL-nnn form or a raw entry id) \u2014 required."),
12063
- reason: z31.string().max(2e3).optional().describe("'force-close': required reason."),
12064
- answerEntryId: z31.string().max(200).optional().describe("'answer': point at an already-written entry instead of supplying text (mutually exclusive with text)."),
12065
- mine: z31.boolean().optional().describe("'list': true for arm C (every question YOU asked, every status). Omit/false for the ack-gate view (what you owe).")
11707
+ entryId: z30.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'."),
11708
+ text: z30.string().max(5e3).optional().describe("'create': the question text. 'answer': the answer text (mutually exclusive with answerEntryId, max 2000 chars)."),
11709
+ blocks: z30.array(z30.string().max(200)).max(50).optional().describe("'create': entry ids this question is blocking (repeatable)."),
11710
+ toRole: z30.string().max(200).optional().describe("'assign': reassign to this role (human ROL-nnn form or a raw entry id) \u2014 required."),
11711
+ reason: z30.string().max(2e3).optional().describe("'force-close': required reason."),
11712
+ answerEntryId: z30.string().max(200).optional().describe("'answer': point at an already-written entry instead of supplying text (mutually exclusive with text)."),
11713
+ mine: z30.boolean().optional().describe("'list': true for arm C (every question YOU asked, every status). Omit/false for the ack-gate view (what you owe).")
12066
11714
  });
12067
- var createVariant = z31.object({ action: z31.literal("create"), text: z31.string().min(1).max(5e3), blocks: z31.array(z31.string().max(200)).max(50).optional() });
12068
- var assignVariant = z31.object({ action: z31.literal("assign"), entryId: z31.string().max(200), toRole: z31.string().max(200).min(1) });
12069
- var answerVariant = z31.object({ action: z31.literal("answer"), entryId: z31.string().max(200), text: z31.string().max(2e3).optional(), answerEntryId: z31.string().max(200).optional() });
12070
- var forceCloseVariant = z31.object({ action: z31.literal("force-close"), entryId: z31.string().max(200), reason: z31.string().min(1).max(2e3) });
12071
- var listVariant = z31.object({ action: z31.literal("list"), mine: z31.boolean().optional() });
12072
- var questionActionUnion = z31.discriminatedUnion("action", [
11715
+ var createVariant = z30.object({ action: z30.literal("create"), text: z30.string().min(1).max(5e3), blocks: z30.array(z30.string().max(200)).max(50).optional() });
11716
+ var assignVariant = z30.object({ action: z30.literal("assign"), entryId: z30.string().max(200), toRole: z30.string().max(200).min(1) });
11717
+ var answerVariant = z30.object({ action: z30.literal("answer"), entryId: z30.string().max(200), text: z30.string().max(2e3).optional(), answerEntryId: z30.string().max(200).optional() });
11718
+ var forceCloseVariant = z30.object({ action: z30.literal("force-close"), entryId: z30.string().max(200), reason: z30.string().min(1).max(2e3) });
11719
+ var listVariant = z30.object({ action: z30.literal("list"), mine: z30.boolean().optional() });
11720
+ var questionActionUnion = z30.discriminatedUnion("action", [
12073
11721
  createVariant,
12074
11722
  assignVariant,
12075
11723
  answerVariant,
@@ -12223,7 +11871,7 @@ function registerQuestionTools(server) {
12223
11871
  }
12224
11872
 
12225
11873
  // src/tools/role.ts
12226
- import { z as z32 } from "zod/v3";
11874
+ import { z as z31 } from "zod/v3";
12227
11875
 
12228
11876
  // src/lib/seatSentence.ts
12229
11877
  function formatSeatSentence(action, reply, clean) {
@@ -12246,45 +11894,45 @@ function formatInviteSentence(reply, clean) {
12246
11894
 
12247
11895
  // src/tools/role.ts
12248
11896
  var ROLE_ACTIONS = ["accountable", "seat", "unseat"];
12249
- var roleSchema = z32.object({
12250
- action: z32.enum(ROLE_ACTIONS).describe(
11897
+ var roleSchema = z31.object({
11898
+ action: z31.enum(ROLE_ACTIONS).describe(
12251
11899
  "'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
11900
  ),
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
- canonicalKey: z32.string().max(200).optional().describe("'accountable': narrow to one kind (e.g. objective, key_result, tension)."),
12255
- limit: z32.number().int().positive().max(200).optional().describe("'accountable': max explicit-list rows per page (default and maximum 200)."),
12256
- cursor: z32.number().optional().describe("'accountable': continue the explicit list from an earlier truncated page's nextCursor."),
12257
- person: z32.string().max(200).optional().describe(
11901
+ roleId: z31.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."),
11902
+ canonicalKey: z31.string().max(200).optional().describe("'accountable': narrow to one kind (e.g. objective, key_result, tension)."),
11903
+ limit: z31.number().int().positive().max(200).optional().describe("'accountable': max explicit-list rows per page (default and maximum 200)."),
11904
+ cursor: z31.number().optional().describe("'accountable': continue the explicit list from an earlier truncated page's nextCursor."),
11905
+ person: z31.string().max(200).optional().describe(
12258
11906
  "'seat' / 'unseat': an existing person, by person id or exact email. For 'seat', give this or newPersonName, not both."
12259
11907
  ),
12260
- newPersonName: z32.string().max(120).optional().describe(
11908
+ newPersonName: z31.string().max(120).optional().describe(
12261
11909
  "'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
11910
  ),
12263
- teamId: z32.string().max(200).optional().describe(
11911
+ teamId: z31.string().max(200).optional().describe(
12264
11912
  "'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."
12265
11913
  )
12266
11914
  });
12267
- var accountableVariant = z32.object({
12268
- action: z32.literal("accountable"),
12269
- roleId: z32.string().max(200),
12270
- canonicalKey: z32.string().max(200).optional(),
12271
- limit: z32.number().int().positive().max(200).optional(),
12272
- cursor: z32.number().optional()
11915
+ var accountableVariant = z31.object({
11916
+ action: z31.literal("accountable"),
11917
+ roleId: z31.string().max(200),
11918
+ canonicalKey: z31.string().max(200).optional(),
11919
+ limit: z31.number().int().positive().max(200).optional(),
11920
+ cursor: z31.number().optional()
12273
11921
  });
12274
- var seatVariant = z32.object({
12275
- action: z32.literal("seat"),
12276
- roleId: z32.string().max(200),
12277
- person: z32.string().max(200).optional(),
12278
- newPersonName: z32.string().max(120).optional(),
12279
- teamId: z32.string().max(200).optional()
11922
+ var seatVariant = z31.object({
11923
+ action: z31.literal("seat"),
11924
+ roleId: z31.string().max(200),
11925
+ person: z31.string().max(200).optional(),
11926
+ newPersonName: z31.string().max(120).optional(),
11927
+ teamId: z31.string().max(200).optional()
12280
11928
  });
12281
- var unseatVariant = z32.object({
12282
- action: z32.literal("unseat"),
12283
- roleId: z32.string().max(200),
12284
- person: z32.string().max(200),
12285
- teamId: z32.string().max(200).optional()
11929
+ var unseatVariant = z31.object({
11930
+ action: z31.literal("unseat"),
11931
+ roleId: z31.string().max(200),
11932
+ person: z31.string().max(200),
11933
+ teamId: z31.string().max(200).optional()
12286
11934
  });
12287
- var roleActionUnion = z32.discriminatedUnion("action", [accountableVariant, seatVariant, unseatVariant]);
11935
+ var roleActionUnion = z31.discriminatedUnion("action", [accountableVariant, seatVariant, unseatVariant]);
12288
11936
  var ROLE_ACTION_SPECS = {
12289
11937
  accountable: { params: ["roleId", "canonicalKey", "limit", "cursor"], description: "roleId is required; canonicalKey/limit/cursor are optional." },
12290
11938
  seat: { params: ["roleId", "person", "newPersonName", "teamId"], description: "roleId is required, plus exactly one of person or newPersonName; teamId only for Team Lead." },
@@ -12412,29 +12060,29 @@ function registerRoleTools(server) {
12412
12060
  }
12413
12061
 
12414
12062
  // src/tools/people.ts
12415
- import { z as z33 } from "zod/v3";
12063
+ import { z as z32 } from "zod/v3";
12416
12064
  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(
12065
+ var peopleSchema = z32.object({
12066
+ action: z32.enum(PEOPLE_ACTIONS).describe("'invite': send a login link by email. 'remove': take a person out of a team."),
12067
+ email: z32.string().max(320).optional().describe("'invite': where the login link goes. Required for 'invite'."),
12068
+ person: z32.string().max(320).optional().describe(
12421
12069
  "'invite': a placeholder's person id, instead of newPersonName. 'remove': the person, by person id or exact email."
12422
12070
  ),
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'.")
12071
+ newPersonName: z32.string().max(120).optional().describe("'invite': the name of a new person to invite, instead of person."),
12072
+ teamId: z32.string().max(200).optional().describe("'remove': the team, by chain id or entry id. Required for 'remove'.")
12425
12073
  });
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()
12074
+ var inviteVariant = z32.object({
12075
+ action: z32.literal("invite"),
12076
+ email: z32.string().max(320),
12077
+ person: z32.string().max(320).optional(),
12078
+ newPersonName: z32.string().max(120).optional()
12431
12079
  });
12432
- var removeVariant = z33.object({
12433
- action: z33.literal("remove"),
12434
- person: z33.string().max(320),
12435
- teamId: z33.string().max(200)
12080
+ var removeVariant = z32.object({
12081
+ action: z32.literal("remove"),
12082
+ person: z32.string().max(320),
12083
+ teamId: z32.string().max(200)
12436
12084
  });
12437
- var peopleActionUnion = z33.discriminatedUnion("action", [inviteVariant, removeVariant]);
12085
+ var peopleActionUnion = z32.discriminatedUnion("action", [inviteVariant, removeVariant]);
12438
12086
  var PEOPLE_ACTION_SPECS = {
12439
12087
  invite: { params: ["email", "person", "newPersonName"], description: "email is required, plus exactly one of person (a placeholder's id) or newPersonName." },
12440
12088
  remove: { params: ["person", "teamId"], description: "person and teamId are required." }
@@ -12523,49 +12171,49 @@ function registerPeopleTools(server) {
12523
12171
  }
12524
12172
 
12525
12173
  // src/tools/outcome.ts
12526
- import { z as z34 } from "zod/v3";
12174
+ import { z as z33 } from "zod/v3";
12527
12175
  var OUTCOME_ACTIONS = ["record", "list", "show"];
12528
12176
  var READING_KINDS = ["measurement", "estimate", "correction"];
12529
- var outcomeSchema = z34.object({
12530
- action: z34.enum(OUTCOME_ACTIONS).describe(
12177
+ var outcomeSchema = z33.object({
12178
+ action: z33.enum(OUTCOME_ACTIONS).describe(
12531
12179
  "'record': record a reading (evidence, not a verdict). 'list': list readings, newest first. 'show': the derived view \u2014 current, progress, pace, staleness, recorded completion."
12532
12180
  ),
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."),
12181
+ entryId: z33.string().max(200).optional().describe("The key result (or other outcome-bearing) entry, by its chain id. Required for all three actions."),
12534
12182
  // PR #858 review round 3 (Codex P2, PRRT_kwDORS4ils6gXHj9): a JSON number that overflows to
12535
12183
  // `Infinity` (e.g. `1e400`) parses fine in JS and passes a plain `z.number()`, but
12536
12184
  // `callGateway()` serializes it with `JSON.stringify`, which turns `Infinity` into `null` —
12537
12185
  // the backend's `v.number()` validator then rejects a call this tool's own schema accepted.
12538
12186
  // `.finite()` rejects it here, matching the explicit finite-value checks the CLI/other outcome
12539
12187
  // entry surfaces already use.
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).")
12188
+ value: z33.number().finite().optional().describe("'record': the measured value. Required for 'record'."),
12189
+ valueText: z33.string().max(500).optional().describe("'record': verbatim as-reported form when the source was not clean, e.g. '~62%'. Display only."),
12190
+ 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."),
12191
+ kind: z33.enum(READING_KINDS).optional().describe("'record': measurement | estimate | correction (default: measurement)."),
12192
+ 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."),
12193
+ recordedByRoleId: z33.string().max(200).optional().describe("'record': the accountable role (default: the entry's reporterRoleId, then its owning role)."),
12194
+ note: z33.string().max(2e3).optional().describe("'record': free-text note recorded with the reading."),
12195
+ source: z33.string().max(500).optional().describe("'record': where the number came from \u2014 a dashboard name, a query, a URL."),
12196
+ limit: z33.number().int().positive().max(200).optional().describe("'list': max rows to return (default and maximum 200).")
12549
12197
  });
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()
12198
+ var recordVariant = z33.object({
12199
+ action: z33.literal("record"),
12200
+ entryId: z33.string().max(200),
12201
+ value: z33.number().finite(),
12202
+ valueText: z33.string().max(500).optional(),
12203
+ asOf: z33.string().max(64).optional(),
12204
+ kind: z33.enum(READING_KINDS).optional(),
12205
+ correctsReadingId: z33.string().max(200).optional(),
12206
+ recordedByRoleId: z33.string().max(200).optional(),
12207
+ note: z33.string().max(2e3).optional(),
12208
+ source: z33.string().max(500).optional()
12561
12209
  });
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()
12210
+ var listVariant2 = z33.object({
12211
+ action: z33.literal("list"),
12212
+ entryId: z33.string().max(200),
12213
+ limit: z33.number().int().positive().max(200).optional()
12566
12214
  });
12567
- var showVariant = z34.object({ action: z34.literal("show"), entryId: z34.string().max(200) });
12568
- var outcomeActionUnion = z34.discriminatedUnion("action", [recordVariant, listVariant2, showVariant]);
12215
+ var showVariant = z33.object({ action: z33.literal("show"), entryId: z33.string().max(200) });
12216
+ var outcomeActionUnion = z33.discriminatedUnion("action", [recordVariant, listVariant2, showVariant]);
12569
12217
  var OUTCOME_ACTION_SPECS = {
12570
12218
  record: {
12571
12219
  params: ["entryId", "value", "valueText", "asOf", "kind", "correctsReadingId", "recordedByRoleId", "note", "source"],
@@ -12768,223 +12416,6 @@ import { dirname, join, resolve as resolve4 } from "path";
12768
12416
  import { fileURLToPath } from "url";
12769
12417
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
12770
12418
 
12771
- // src/lib/relationTypes.ts
12772
- var ALLOWED_RELATION_TYPES_LIST = [
12773
- // Core semantic types
12774
- "informs",
12775
- "governs",
12776
- "surfaces_tension_in",
12777
- "defines_term_for",
12778
- "belongs_to",
12779
- "references",
12780
- "related_to",
12781
- "fills_slot",
12782
- "commits_to",
12783
- "informed_by",
12784
- "depends_on",
12785
- "conflicts_with",
12786
- "confused_with",
12787
- // WP-449: Glossary Foundation — negative assertion for agents (spec §4.5)
12788
- "do_not_confuse_with",
12789
- "replaces",
12790
- "part_of",
12791
- "constrains",
12792
- // Epistemic types — evidence linking
12793
- "validates",
12794
- "invalidates",
12795
- // Extended types
12796
- "governed_by",
12797
- "alternative_to",
12798
- "has_proposal",
12799
- "requests_promotion_of",
12800
- "resolves",
12801
- // WP-228 Slice 0: Playing Field relation types
12802
- "experiences",
12803
- "addresses",
12804
- // Tier 1 Hierarchy: DEC-401 (experiments prereqs), DEC-402 (risk views)
12805
- "threatens",
12806
- "tests",
12807
- "produces",
12808
- // WP-274: Capability tier relation types
12809
- "realizes",
12810
- "requires",
12811
- "invests_in",
12812
- "enables",
12813
- // PARKED (park note, 2026-08-23) — these 5 verbs are untouched by a separate
12814
- // governance-verb-ownership review: no delete, no lock, no whitelist, no picker change,
12815
- // and no relation of this note to that review. Live source-canonicalKey row counts, from
12816
- // a read-only pass over an uncapped, exhaustive census of a real workspace (measured
12817
- // 2026-08-23): follows = 11 rows (decision 2, milestone 5, work_package 4);
12818
- // extracted_from = 4 rows (decision 1, insight 1, tension 1, work_package 1);
12819
- // part_of_workstream = 0 rows; stated_by = 0 rows; discussed_in = 0 rows. Whether any of
12820
- // these should be retired, merged, or promoted is a founder call, deliberately deferred —
12821
- // not decided here, and not decided by silence either. Re-verify the counts before acting
12822
- // on them; they will drift.
12823
- // WP-344 S0a: Evolve-First Ingestion relation types
12824
- "follows",
12825
- // sequential/derived — new entry follows/continues from another
12826
- "extracted_from",
12827
- // provenance — this entry was extracted from a source document
12828
- // WP-353 S0: scope-propagated commit relation types (DEC-A captured as exploring per RULE-187)
12829
- "part_of_workstream",
12830
- // entry belongs to a workstream anchor confirmed at scope step
12831
- "stated_by",
12832
- // entry was stated by a speaker in the source document
12833
- "discussed_in",
12834
- // entry was discussed within a particular meeting/doc context
12835
- // Specification types — WP-363 E2: precision alternatives to governs for decision→governance relations
12836
- "specializes",
12837
- // DEC narrows or refines a broader STD/PRI — more specific application of the parent
12838
- "implements",
12839
- // DEC/WP realizes a BR/STD in concrete operational form
12840
- // WP-641 S2a (WP-647): belongs_to vocabulary split — the strategy-scope successor.
12841
- // The other two successors (authority_domain, domain_routed_to) and `blocks` are no longer
12842
- // legal to write: a new workspace carries none of them (DEC-1845, TASK-331, TASK-425).
12843
- "anchored_to"
12844
- // strategy-scope anchor: a raw strategy entry belongs_to a product (DEC-1523)
12845
- ];
12846
- var ALLOWED_RELATION_TYPES = new Set(ALLOWED_RELATION_TYPES_LIST);
12847
-
12848
- // src/resources/agentCheatsheet.ts
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.";
12850
- var AGENT_CHEATSHEET = `# Product Brain \u2014 Agent Cheatsheet
12851
-
12852
- ## Core Tools (Serves 16 compound tools, always)
12853
- | Tool | Purpose | Actions |
12854
- |---|---|---|
12855
- | \`orient\` | Workspace context, governance, active work packages, session start | \`action\`: start, task |
12856
- | \`capture\` | Create entry | \`action\`: capture |
12857
- | \`entries\` | Write entries | \`action\`: update, commit, move, verify, preflight, still-true |
12858
- | \`relations\` | Link entries, delete links | \`action\`: create, batch-create, delete |
12859
- | \`context\` | Reads; only incremental writes | \`action\`: get, batch, history, list, search, find, gather, build, neighborhood, changes, chain, cross-cut, incremental, brief, last-verified-brief |
12860
- | \`collections\` | Browse, describe, audit and export collections; manage labels | \`action\`: list, update, describe, audit, export, label-* |
12861
- | \`quality\` | Score an entry, verify the Chain, spine check | \`action\`: check, re-evaluate, verify-chain, spine-check |
12862
- | \`session\` | Start / close, wrapup, resume, constellation accept | \`action\`: start, close, status, wrapup-review, wrapup-commit, resume, commit-constellation |
12863
- | \`workflows\` | Run workflows, checkpoint | \`action\`: list, start, checkpoint, get-run |
12864
- | \`workspace\` | Check / whoami / status / self-test, usage | \`action\`: check, whoami, status, audit, self-test, usage |
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 |
12866
- | \`shape\` | Review / disposition write-shape (single-concern split) advisories | \`action\`: list, show, agree, dismiss |
12867
- | \`question\` | Routed questions \u2014 create, reassign, answer, force-close, list | \`action\`: create, assign, answer, force-close, list |
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 |
12870
- | \`outcome\` | Record an outcome measurement and read the ones already recorded | \`action\`: record, list, show |
12871
-
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.
12873
-
12874
- ## Collection Prefixes
12875
- GLO (glossary), RULE (rules), DEC (decisions), STR (strategy), TEN (tensions), FEAT (features),
12876
- BET (bets), INS (insights), ARCH (architecture), TEAM (teams),
12877
- ROL (roles), MTRC (tracking-events), ST (semantic-types)
12878
-
12879
- ## Valid Relation Types (${ALLOWED_RELATION_TYPES_LIST.length})
12880
- ${ALLOWED_RELATION_TYPES_LIST.join(", ")}
12881
-
12882
- ## Lifecycle Status
12883
- Write via \`update\`: \`draft\` | \`archived\` (\`accepted\` via \`commit\`, never \`update\`). \`active\`/\`deprecated\` are legacy: readable, refused on write.
12884
-
12885
- ## Workflow Status (per collection)
12886
- - **tensions:** open \u2192 processing \u2192 decided \u2192 closed
12887
- - **decisions:** pending \u2192 decided
12888
- - **bets:** shaped \u2192 bet \u2192 building \u2192 shipped
12889
-
12890
- ## Rule Lists
12891
- - Session start and \`orient action=task\` serve the rules that bind you, each as one sentence with its why.
12892
- - A rule's \`applies\` and its one owner role place it on one list: everyone (cap 15), team (cap 5 per team) or role (cap 5 per role).
12893
- - A rule on no list binds nobody; a full list refuses a new rule until one retires, and a list over its cap is served whole.
12894
-
12895
- ## Key Patterns
12896
- - **Capture flow:** \`capture\` \u2192 \`relations action=batch-create\` \u2192 \`entries action=commit\`
12897
- - Use \`workflowStatus\` (not \`status\`) for domain workflow state
12898
- - \`data\` param is merged (not replaced) \u2014 safe for partial updates
12899
- `;
12900
-
12901
- // src/resources/orientationMarkdown.ts
12902
- function buildOrientationMarkdown(collections, briefingLines) {
12903
- if (briefingLines.length === 0) {
12904
- throw new Error("buildOrientationMarkdown: briefingLines is empty \u2014 pass the rendered briefing or the read-failed marker line");
12905
- }
12906
- const sections = ["# Product Brain \u2014 Orientation", briefingLines.join("\n")];
12907
- sections.push(
12908
- "## About Product Brain (PB)\nPB is a knowledge management system used by many product teams. This section describes how PB works \u2014 not your product.\n\n### How PB Organizes Its Tools\n- **Tools** = actions with side-effects or dynamic computation (capture, accept, search).\n- **Resources** = stable, read-only data (orientation, terminology, collection schemas).\n- **Prompts** = multi-step choreography (workflows, guided capture, deep dives).\n- Every tool, resource, and prompt works for ANY workspace \u2014 no bespoke logic.\n- Resource templates (URI params) serve workspace-specific data through generic patterns.\n- Compound tools with `action` enums keep the tool count minimal.\n\n" + DATA_HANDLING_SECTION
12909
- );
12910
- sections.push(
12911
- "## Core Product Architecture\nThe Chain is a versioned, connected, compounding knowledge base \u2014 the SSOT for a product team.\nEverything on the Chain is one of **two primitives**:\n\n- **Entry** (atom) \u2014 a discrete knowledge unit: target audience, rule, glossary term, metric, tension. Lives in a collection.\n- **Process** (authored) \u2014 a narrative chain with named links (Strategy Coherence, IDM Proposal). Content is inline text. Collection: `chains`.\n\nEntries are atoms. Processes author narrative from atoms.\nBoth share the same versioning, branching, gating, and relation system.\n\nThe Chain compounds: each new relation makes entries discoverable from more starting points \u2192 context gathering returns richer results \u2192 AI processes have more context \u2192 quality scores improve \u2192 next accept is smarter than the last."
12912
- );
12913
- sections.push(
12914
- "## Architecture\n```\nCursor (stdio) \u2192 MCP Server (mcp-server/src/index.ts)\n \u2192 POST /api/aki with Bearer token\n \u2192 Convex HTTP Action (convex/http.ts)\n \u2192 internalQuery / internalMutation (convex/agentKnowledge/*)\n \u2192 Convex DB (workspace-scoped)\n```\nSecurity: API key auth on every request, workspace-scoped data, internal functions blocked from external clients.\nKey files: `packages/mcp-server/src/client.ts` (HTTP client + audit), `convex/schema.ts` (schema)."
12915
- );
12916
- if (collections) {
12917
- const collList = collections.map((c) => {
12918
- const prefix = c.icon ? `${c.icon} ` : "";
12919
- return `- ${prefix}**${c.name}** (\`${c.slug}\`) \u2014 ${c.description || "no description"}`;
12920
- }).join("\n");
12921
- sections.push(
12922
- `## Data Model (${collections.length} collections)
12923
- Unified entries model: collections define field schemas, entries hold data in a flexible \`data\` field.
12924
- The \`data\` field is polymorphic: plain fields for generic entries, \`ChainData\` (chainTypeId + links) for processes.
12925
- Tags for filtering (e.g. \`severity:high\`), relations via \`entryRelations\`, versions via \`entryVersions\`, labels via \`labels\` + \`entryLabels\`.
12926
-
12927
- ` + collList + "\n\nUse `collections action=list` for field schemas, `context action=get` for full records."
12928
- );
12929
- } else {
12930
- sections.push(
12931
- "## Data Model\nCould not load collections \u2014 use `collections action=list` to browse manually."
12932
- );
12933
- }
12934
- sections.push(
12935
- '## Rules\nThe rules that bind you are in the briefing above, each with its why. A rule on no list binds nobody.\nOpen one: `context action=get entryId="<ID>"`. Find one: `context action=search query="<noun>"`.\nDraft a new rule: use the `draft-rule-from-context` prompt.'
12936
- );
12937
- sections.push(
12938
- "## Knowledge Graph\nEntries are connected via typed relations (`entryRelations` table). Relations are bidirectional and collection-agnostic \u2014 any entry can link to any other entry.\n\n**Recommended relation types** (extensible \u2014 any string accepted):\n- `governs` \u2014 a rule constrains behavior of a feature\n- `defines_term_for` \u2014 a glossary term is canonical vocabulary for a feature/area\n- `belongs_to` \u2014 a feature belongs to a product area or parent concept (typed successor: `anchored_to`)\n- `informs` \u2014 a decision or insight informs a feature\n- `surfaces_tension_in` \u2014 a tension exists within a feature area\n- `related_to`, `depends_on`, `replaces`, `conflicts_with`, `references`, `confused_with`\n\nEach relation type is defined as a glossary entry (prefix `GT-REL-*`) to prevent terminology drift.\n\n**Tools:**\n- `context action=gather` \u2014 get the full context around any entry (multi-hop graph traversal)\n- `relations action=create` \u2014 create a typed link between two entries\n- `context action=find` \u2014 list direct relations for an entry\n\n**Convention:** A relation is written only when you ask for it, with exactly the verb you send \u2014 link the entries you know are related with `relations action=create`."
12939
- );
12940
- sections.push(
12941
- `## Creating Knowledge
12942
- **Entries:** Use \`capture\` as the primary tool for creating new entries. It handles the full workflow in one call:
12943
- 1. Creates the entry with collection-aware defaults (auto-fills dates, infers domains, sets priority)
12944
- 2. Writes the relations you pass in \`links\` \u2014 and no others; use \`relations action=create\` to add more
12945
- 3. Returns the server's quality verdict with improvement hints
12946
-
12947
- **Smart profiles** exist for: ${SMART_PROFILE_COLLECTIONS.map((c) => `\`${c}\``).join(", ")}.
12948
- All other collections use the \`ENT-{random}\` fallback profile.
12949
-
12950
- **Processes:** Use \`chain action=create\` with a template (e.g., \`strategy-coherence\`, \`idm-proposal\`). Fill links with \`chain action=edit\`.
12951
-
12952
- **Prompts** (invoke via MCP prompt protocol):
12953
- - \`name-check\` \u2014 verify a name against glossary conventions
12954
- - \`draft-decision-record\` \u2014 scaffold a decision entry
12955
- - \`draft-rule-from-context\` \u2014 create a new rule from context
12956
-
12957
- Use \`quality action=check\` to score existing entries retroactively.
12958
- Use \`entries action=update\` for post-creation adjustments (status changes, field updates, deprecation).`
12959
- );
12960
- sections.push(
12961
- "## Where to Go Next\n- **Create entry** \u2192 `capture` tool (entry, the links you pass, and a quality verdict in one call)\n- **Full context** \u2192 `context action=gather` (by entry ID or task description)\n- **Link entries** \u2192 `relations action=create`\n- **Quality audit** \u2192 `quality action=check`\n- **Terminology** \u2192 `name-check` prompt or `productbrain://terminology` resource\n- **Schema details** \u2192 `productbrain://collections` resource or `collections action=list`\n- **Labels** \u2192 `productbrain://labels` resource or `collections action=label-list`\n- **Any collection** \u2192 `productbrain://{slug}/entries` resource\n- **Log a decision** \u2192 `draft-decision-record` prompt\n- **Growth funnel** \u2192 `productbrain://growth-funnel` resource or `context action=list collection=strategy status=draft`\n- **Session identity** \u2192 `workspace action=whoami`\n- **Health check** \u2192 `workspace action=check`\n- **Debug MCP calls** \u2192 `workspace action=audit`"
12962
- );
12963
- return sections.join("\n\n---\n\n");
12964
- }
12965
- async function buildOrientationResourceText(kernelQuery3, agentSessionId) {
12966
- const [collectionsResult, orientViewResult] = await Promise.allSettled([
12967
- kernelQuery3("chain.listCollections"),
12968
- kernelQuery3("chain.getOrientView", {
12969
- tier: "summary",
12970
- invocationPath: "unknown",
12971
- ...agentSessionId ? { sessionId: agentSessionId } : {}
12972
- })
12973
- ]);
12974
- const collections = collectionsResult.status === "fulfilled" ? collectionsResult.value : null;
12975
- let briefingLines;
12976
- if (orientViewResult.status === "rejected") {
12977
- const reason = orientViewResult.reason instanceof Error ? orientViewResult.reason.message : String(orientViewResult.reason);
12978
- const normalized = sanitizeSingleLineField(reason);
12979
- const truncated = normalized.length > 120 ? `${normalized.slice(0, 120)}\u2026` : normalized;
12980
- briefingLines = [`_Briefing unavailable: the orient read failed (${truncated}); purpose, objective and the rules that bind you are not shown. Read this resource again or run orient to retry._`];
12981
- } else {
12982
- const orientView = orientViewResult.value;
12983
- briefingLines = buildOrientBriefingLines(orientView, { everyoneOnly: !agentSessionId });
12984
- }
12985
- return buildOrientationMarkdown(collections, briefingLines);
12986
- }
12987
-
12988
12419
  // src/resources/terminologyResource.ts
12989
12420
  async function buildTerminologyResourceText() {
12990
12421
  const [glossaryResult] = await Promise.allSettled([
@@ -13031,25 +12462,6 @@ function formatEntryMarkdown(entry) {
13031
12462
  return lines.join("\n");
13032
12463
  }
13033
12464
  function registerResources(server) {
13034
- server.resource(
13035
- "agent-cheatsheet",
13036
- "productbrain://agent-cheatsheet",
13037
- async (uri) => ({
13038
- contents: [{
13039
- uri: uri.href,
13040
- text: AGENT_CHEATSHEET,
13041
- mimeType: "text/markdown"
13042
- }]
13043
- })
13044
- );
13045
- server.resource(
13046
- "chain-orientation",
13047
- "productbrain://orientation",
13048
- async (uri) => ({
13049
- contents: [{ uri: uri.href, text: await buildOrientationResourceText(kernelQuery, getAgentSessionId()), mimeType: "text/markdown" }]
13050
- // TEN-3912 fix 2
13051
- })
13052
- );
13053
12465
  server.resource(
13054
12466
  "chain-terminology",
13055
12467
  "productbrain://terminology",
@@ -13335,7 +12747,7 @@ ${entry.labels.map((l) => `- ${l.name ?? l.slug}`).join("\n")}`);
13335
12747
  }
13336
12748
 
13337
12749
  // src/prompts/index.ts
13338
- import { z as z35 } from "zod/v3";
12750
+ import { z as z34 } from "zod/v3";
13339
12751
  async function buildReviewAgainstRulesPrompt(domain) {
13340
12752
  const entries = await kernelQuery("chain.listEntries", { collectionSlug: "rules" });
13341
12753
  const rules = entries.slice(0, 50);
@@ -13417,27 +12829,27 @@ function registerPrompts(server) {
13417
12829
  server.prompt(
13418
12830
  "review-against-rules",
13419
12831
  "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.",
13420
- { domain: z35.string().describe("Business rule domain (e.g. 'Identity & Access', 'Governance & Decision-Making')") },
12832
+ { domain: z34.string().describe("Business rule domain (e.g. 'Identity & Access', 'Governance & Decision-Making')") },
13421
12833
  async ({ domain }) => buildReviewAgainstRulesPrompt(domain)
13422
12834
  );
13423
12835
  server.prompt(
13424
12836
  "name-check",
13425
12837
  "Check variable names, field names, or API names against the glossary for terminology alignment. Flags drift from canonical terms.",
13426
- { names: z35.string().describe("Comma-separated list of names to check (e.g. 'user_id, workspace_id, entry_status')") },
12838
+ { names: z34.string().describe("Comma-separated list of names to check (e.g. 'user_id, workspace_id, entry_status')") },
13427
12839
  async ({ names }) => buildNameCheckPrompt(names)
13428
12840
  );
13429
12841
  server.prompt(
13430
12842
  "draft-decision-record",
13431
12843
  "Draft a structured decision record from a description of what was decided. Includes context from recent decisions and relevant rules.",
13432
- { context: z35.string().describe("Description of the decision (e.g. 'We decided to keep one body field per entry because...')") },
12844
+ { context: z34.string().describe("Description of the decision (e.g. 'We decided to keep one body field per entry because...')") },
13433
12845
  async ({ context }) => buildDraftDecisionRecordPrompt(context)
13434
12846
  );
13435
12847
  server.prompt(
13436
12848
  "draft-rule-from-context",
13437
12849
  "Draft a new business rule from an observation or discovery made while coding. Fetches existing rules for the domain to ensure consistency.",
13438
12850
  {
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')")
12851
+ observation: z34.string().describe("What you observed or discovered (e.g. 'A workspace can have more than one owner role')"),
12852
+ domain: z34.string().describe("Which domain this rule belongs to (e.g. 'Governance & Decision-Making')")
13441
12853
  },
13442
12854
  async ({ observation, domain }) => buildDraftRuleFromContextPrompt(observation, domain)
13443
12855
  );
@@ -13518,15 +12930,6 @@ var INSTRUCTIONS = [
13518
12930
  "",
13519
12931
  "Read those rules before you propose or build. If the work conflicts with one, stop, name the rule and ask the user before going on. Never design around a rule.",
13520
12932
  "",
13521
- "## What applies means",
13522
- "",
13523
- "Every rule (collection `rules`) declares whom it applies to, and its one owner role places it:",
13524
- "- **everyone**: the whole workspace, owned by the root team's lead; capped at 15 rules;",
13525
- "- **team**: its owner role's team; capped at 5 per team;",
13526
- "- **role**: its owner role; capped at 5 per role.",
13527
- "",
13528
- "A rule on no list binds nobody. A full list refuses a new rule until one retires; a list already over its cap is served whole.",
13529
- "",
13530
12933
  "## Pull the rest",
13531
12934
  "",
13532
12935
  '- `orient action=task task="<what you are doing>"`: context shaped to the task, with the rules for your team and role.',
@@ -13537,16 +12940,12 @@ var INSTRUCTIONS = [
13537
12940
  "",
13538
12941
  "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.",
13539
12942
  "Pass the `sessionId` that `session action=start` returned on every write: the connection does not remember it.",
13540
- "Use `capture` to create an entry and read its reply: an entry that came back accepted needs no `entries action=commit`.",
12943
+ "Use `capture` to create an entry and read its reply: it lands as a draft, except a question, which is born open and needs no `entries action=commit`.",
13541
12944
  "Link entries with `relations action=create` \u2014 a relation is written only when you ask, with exactly the verb you send.",
13542
12945
  "Finish with `session action=wrapup-review`, then `session action=close`.",
13543
- "If the work falls outside the active work packages, name the gap and ask the user before designing it.",
13544
- "",
13545
- DATA_HANDLING_SECTION,
13546
- "",
13547
- "Every tool and action: `productbrain://agent-cheatsheet`."
12946
+ "If the work falls outside the active work packages, name the gap and ask the user before designing it."
13548
12947
  ].join("\n");
13549
- var sessionIdArg = z36.string().max(200).optional().describe(
12948
+ var sessionIdArg = z35.string().max(200).optional().describe(
13550
12949
  "The sessionId `session action=start` returned. Pass it on every write: the connection does not remember your session between calls."
13551
12950
  );
13552
12951
  function acceptSessionIdOnEveryTool(server) {
@@ -13639,4 +13038,4 @@ export {
13639
13038
  createProductBrainServer,
13640
13039
  initFeatureFlags
13641
13040
  };
13642
- //# sourceMappingURL=chunk-U6BVSTXX.js.map
13041
+ //# sourceMappingURL=chunk-LLNKILES.js.map