@productbrain/mcp 0.0.1-beta.4239 → 0.0.1-beta.4243
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.
- package/dist/{chunk-522272VK.js → chunk-3NAU25R5.js} +254 -15
- package/dist/chunk-3NAU25R5.js.map +1 -0
- package/dist/{chunk-LSOA2AH2.js → chunk-WTGHEQHB.js} +8 -1
- package/dist/chunk-WTGHEQHB.js.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/http.js +2 -2
- package/dist/index.js +2 -2
- package/dist/{setup-HM2J4MDE.js → setup-RTUHXH7J.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-522272VK.js.map +0 -1
- package/dist/chunk-LSOA2AH2.js.map +0 -1
- /package/dist/{setup-HM2J4MDE.js.map → setup-RTUHXH7J.js.map} +0 -0
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
trackSessionCaptureRate,
|
|
43
43
|
trackWriteBackHintServed,
|
|
44
44
|
trackZeroCaptureAuditFired
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-WTGHEQHB.js";
|
|
46
46
|
|
|
47
47
|
// src/server.ts
|
|
48
48
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -790,8 +790,8 @@ function formatFieldGuidance(fields) {
|
|
|
790
790
|
if (guidedFields.length === 0) return "";
|
|
791
791
|
const lines = ["## Writing Guidance"];
|
|
792
792
|
for (const field of guidedFields) {
|
|
793
|
-
const
|
|
794
|
-
lines.push(`- ${
|
|
793
|
+
const displayName2 = field.label || field.key;
|
|
794
|
+
lines.push(`- ${displayName2}: ${field.writingGuidance}`);
|
|
795
795
|
if (field.writingExamples && field.writingExamples.length > 0) {
|
|
796
796
|
lines.push(` Examples: ${field.writingExamples.join(" | ")}`);
|
|
797
797
|
}
|
|
@@ -2174,7 +2174,7 @@ async function handleCapture(server, { collection, name, description, context, e
|
|
|
2174
2174
|
const profile = await getProfile(resolvedCollection);
|
|
2175
2175
|
const col = await kernelQuery("chain.getCollection", { slug: resolvedCollection });
|
|
2176
2176
|
if (!col) {
|
|
2177
|
-
const
|
|
2177
|
+
const displayName2 = resolvedCollection.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
2178
2178
|
return {
|
|
2179
2179
|
content: [{
|
|
2180
2180
|
type: "text",
|
|
@@ -2182,7 +2182,7 @@ async function handleCapture(server, { collection, name, description, context, e
|
|
|
2182
2182
|
|
|
2183
2183
|
**To create it**, run:
|
|
2184
2184
|
\`\`\`
|
|
2185
|
-
collections action=create slug="${resolvedCollection}" name="${
|
|
2185
|
+
collections action=create slug="${resolvedCollection}" name="${displayName2}" description="..."
|
|
2186
2186
|
\`\`\`
|
|
2187
2187
|
|
|
2188
2188
|
Or use \`collections action=list\` to see available collections.`
|
|
@@ -2192,7 +2192,7 @@ Or use \`collections action=list\` to see available collections.`
|
|
|
2192
2192
|
`Collection '${resolvedCollection}' not found.`,
|
|
2193
2193
|
"Create the collection first, or use collections action=list to see available ones.",
|
|
2194
2194
|
[
|
|
2195
|
-
{ tool: "collections", description: "Create collection", parameters: { action: "create", slug: resolvedCollection, name:
|
|
2195
|
+
{ tool: "collections", description: "Create collection", parameters: { action: "create", slug: resolvedCollection, name: displayName2 } },
|
|
2196
2196
|
{ tool: "collections", description: "List collections", parameters: { action: "list" } }
|
|
2197
2197
|
]
|
|
2198
2198
|
)
|
|
@@ -15809,6 +15809,243 @@ function registerShapeTools(server) {
|
|
|
15809
15809
|
);
|
|
15810
15810
|
}
|
|
15811
15811
|
|
|
15812
|
+
// src/tools/question.ts
|
|
15813
|
+
import { z as z34 } from "zod/v3";
|
|
15814
|
+
var QUESTION_ACTIONS = ["create", "adopt", "assign", "snooze", "decline", "answer", "force-close"];
|
|
15815
|
+
var questionSchema = z34.object({
|
|
15816
|
+
action: z34.enum(QUESTION_ACTIONS).describe(
|
|
15817
|
+
"'create': create a new routed question from text (domains come from `pb question --help` / this tool's own refusal \u2014 never hardcode a slug). 'adopt': attach routing to a tension already captured (e.g. via `pb capture \"TEN: ...\"`) \u2014 the pilot's real birth path; without adopt, captured tensions stay invisible to routing forever. 'assign': confirm ('take it', omit toRole/toPerson) or reassign (supply toRole and/or toPerson) ownership. 'snooze': defer a routed/escalated question (max 2, then it escalates). 'decline': pass on a suggested question, or (as owner/lead) decline an owned one \u2014 reason required. 'answer': close the loop \u2014 creates the answering entry itself unless --entry is given. 'force-close': Team-Lead-only escape hatch \u2014 reason required."
|
|
15818
|
+
),
|
|
15819
|
+
entryId: z34.string().max(200).optional().describe("Target question's id (human TEN-nnn form or a raw entry id). Required for every action except 'create'."),
|
|
15820
|
+
text: z34.string().max(5e3).optional().describe("'create': the question text. 'answer': the answer text (mutually exclusive with answerEntryId, max 2000 chars)."),
|
|
15821
|
+
domain: z34.string().max(200).optional().describe("'create'/'adopt': the domain slug (see this tool's refusal for the live list)."),
|
|
15822
|
+
blocks: z34.array(z34.string().max(200)).max(50).optional().describe("'create'/'adopt': entry ids this question is blocking (repeatable)."),
|
|
15823
|
+
toRole: z34.string().max(200).optional().describe("'assign': reassign to this role (human ROL-nnn form or a raw entry id)."),
|
|
15824
|
+
toPerson: z34.string().max(200).optional().describe("'assign': reassign to this person (a raw person id)."),
|
|
15825
|
+
reason: z34.string().max(2e3).optional().describe("'decline'/'force-close': required reason."),
|
|
15826
|
+
answerEntryId: z34.string().max(200).optional().describe("'answer': point at an already-written entry instead of supplying text (mutually exclusive with text).")
|
|
15827
|
+
});
|
|
15828
|
+
var createVariant = z34.object({ action: z34.literal("create"), text: z34.string().min(1).max(5e3), domain: z34.string().max(200).optional(), blocks: z34.array(z34.string().max(200)).max(50).optional() });
|
|
15829
|
+
var adoptVariant = z34.object({ action: z34.literal("adopt"), entryId: z34.string().max(200), domain: z34.string().max(200).optional(), blocks: z34.array(z34.string().max(200)).max(50).optional() });
|
|
15830
|
+
var assignVariant = z34.object({ action: z34.literal("assign"), entryId: z34.string().max(200), toRole: z34.string().max(200).optional(), toPerson: z34.string().max(200).optional() });
|
|
15831
|
+
var snoozeVariant = z34.object({ action: z34.literal("snooze"), entryId: z34.string().max(200) });
|
|
15832
|
+
var declineVariant = z34.object({ action: z34.literal("decline"), entryId: z34.string().max(200), reason: z34.string().min(1).max(2e3) });
|
|
15833
|
+
var answerVariant = z34.object({ action: z34.literal("answer"), entryId: z34.string().max(200), text: z34.string().max(2e3).optional(), answerEntryId: z34.string().max(200).optional() });
|
|
15834
|
+
var forceCloseVariant = z34.object({ action: z34.literal("force-close"), entryId: z34.string().max(200), reason: z34.string().min(1).max(2e3) });
|
|
15835
|
+
var questionActionUnion = z34.discriminatedUnion("action", [
|
|
15836
|
+
createVariant,
|
|
15837
|
+
adoptVariant,
|
|
15838
|
+
assignVariant,
|
|
15839
|
+
snoozeVariant,
|
|
15840
|
+
declineVariant,
|
|
15841
|
+
answerVariant,
|
|
15842
|
+
forceCloseVariant
|
|
15843
|
+
]);
|
|
15844
|
+
var QUESTION_ACTION_SPECS = {
|
|
15845
|
+
create: { params: ["text", "domain", "blocks"], description: "text is required; domain/blocks are optional." },
|
|
15846
|
+
adopt: { params: ["entryId", "domain", "blocks"], description: "entryId is required (the already-captured tension); domain/blocks are optional." },
|
|
15847
|
+
assign: { params: ["entryId", "toRole", "toPerson"], description: "entryId is required; omit toRole/toPerson to self-confirm, or supply one/both to reassign." },
|
|
15848
|
+
snooze: { params: ["entryId"], description: "entryId is required." },
|
|
15849
|
+
decline: { params: ["entryId", "reason"], description: "entryId and reason are both required." },
|
|
15850
|
+
answer: { params: ["entryId", "text", "answerEntryId"], description: "entryId is required; supply exactly one of text or answerEntryId." },
|
|
15851
|
+
"force-close": { params: ["entryId", "reason"], description: "entryId and reason are both required." }
|
|
15852
|
+
};
|
|
15853
|
+
var HUMAN_ID_PATTERN = /^[A-Za-z]+-\d+$/;
|
|
15854
|
+
async function resolveEntryRef(raw, label) {
|
|
15855
|
+
if (!HUMAN_ID_PATTERN.test(raw)) return raw;
|
|
15856
|
+
const entry = await kernelQuery("chain.getEntry", { entryId: raw });
|
|
15857
|
+
if (!entry) throw new Error(`${label} '${raw}' not found.`);
|
|
15858
|
+
return entry._id;
|
|
15859
|
+
}
|
|
15860
|
+
async function displayName(id) {
|
|
15861
|
+
if (!id) return void 0;
|
|
15862
|
+
try {
|
|
15863
|
+
const entry = await kernelQuery("chain.getEntry", { id });
|
|
15864
|
+
return entry?.name;
|
|
15865
|
+
} catch {
|
|
15866
|
+
return void 0;
|
|
15867
|
+
}
|
|
15868
|
+
}
|
|
15869
|
+
function domainListSuffix(domainSlugs) {
|
|
15870
|
+
if (!domainSlugs) return "";
|
|
15871
|
+
return domainSlugs.length > 0 ? ` Domains in this workspace: ${domainSlugs.join(", ")}.` : " No domains exist in this workspace yet.";
|
|
15872
|
+
}
|
|
15873
|
+
async function renderCreateAdoptConfirmation(result2, isAdopt) {
|
|
15874
|
+
const lead = isAdopt ? `${result2.entryId} is now a routed question,` : "Question created and";
|
|
15875
|
+
let base;
|
|
15876
|
+
if (result2.status === "unrouted") {
|
|
15877
|
+
const roleName = await displayName(result2.suggestedRoleId) ?? result2.suggestedRoleId;
|
|
15878
|
+
base = isAdopt ? `${lead} suggested to ${roleName}.` : `${lead} suggested to ${roleName}. Confirm with: pb question assign ${result2.entryId}`;
|
|
15879
|
+
} else {
|
|
15880
|
+
const leadName = await displayName(result2.ownerRoleId) ?? result2.ownerRoleId;
|
|
15881
|
+
base = isAdopt ? `${result2.entryId} is now a routed question. Nothing here says who owns it, so it has gone to ${leadName}.` : `Question created. Nothing here says who owns it, so it has gone to ${leadName}.`;
|
|
15882
|
+
}
|
|
15883
|
+
const blocksHint = " If something is blocked on this, add blocks:[<entry-id>...].";
|
|
15884
|
+
const nudge = result2.nudge ? ` ${result2.nudge}` : "";
|
|
15885
|
+
return `${base}${domainListSuffix(result2.domainSlugs)}${blocksHint}${nudge}`;
|
|
15886
|
+
}
|
|
15887
|
+
async function handleQuestionCreate(text, domain, blocks) {
|
|
15888
|
+
requireWriteAccess();
|
|
15889
|
+
try {
|
|
15890
|
+
const resolvedBlocks = blocks ? await Promise.all(blocks.map((b) => resolveEntryRef(b, "Blocked entry"))) : void 0;
|
|
15891
|
+
const result2 = await kernelMutation("question.create", { text, domainSlug: domain, blocks: resolvedBlocks });
|
|
15892
|
+
const message = await renderCreateAdoptConfirmation(result2, false);
|
|
15893
|
+
const next = result2.status === "unrouted" ? [{ tool: "question", description: "Confirm or reassign the suggestion", parameters: { action: "assign", entryId: result2.entryId } }] : void 0;
|
|
15894
|
+
return successResult(message, message, result2, next);
|
|
15895
|
+
} catch (err) {
|
|
15896
|
+
return questionErrorResult(err, "create", { text, domain, blocks });
|
|
15897
|
+
}
|
|
15898
|
+
}
|
|
15899
|
+
async function handleQuestionAdopt(entryId, domain, blocks) {
|
|
15900
|
+
requireWriteAccess();
|
|
15901
|
+
try {
|
|
15902
|
+
const resolvedEntryId = await resolveEntryRef(entryId, "Entry");
|
|
15903
|
+
const resolvedBlocks = blocks ? await Promise.all(blocks.map((b) => resolveEntryRef(b, "Blocked entry"))) : void 0;
|
|
15904
|
+
const result2 = await kernelMutation("question.adopt", { entryId: resolvedEntryId, domainSlug: domain, blocks: resolvedBlocks });
|
|
15905
|
+
const message = await renderCreateAdoptConfirmation(result2, true);
|
|
15906
|
+
return successResult(message, message, result2);
|
|
15907
|
+
} catch (err) {
|
|
15908
|
+
return questionErrorResult(err, "adopt", { entryId, domain, blocks });
|
|
15909
|
+
}
|
|
15910
|
+
}
|
|
15911
|
+
async function handleQuestionAssign(entryId, toRole, toPerson) {
|
|
15912
|
+
requireWriteAccess();
|
|
15913
|
+
try {
|
|
15914
|
+
const resolvedEntryId = await resolveEntryRef(entryId, "Entry");
|
|
15915
|
+
const resolvedToRoleId = toRole ? await resolveEntryRef(toRole, "Role") : void 0;
|
|
15916
|
+
const result2 = await kernelMutation("question.assign", { entryId: resolvedEntryId, toRoleId: resolvedToRoleId, toPersonId: toPerson });
|
|
15917
|
+
const isReassign = Boolean(toRole || toPerson);
|
|
15918
|
+
let message;
|
|
15919
|
+
if (isReassign) {
|
|
15920
|
+
const who = toPerson ? result2.ownerPersonName ?? result2.ownerPersonId ?? "the target" : await displayName(result2.ownerRoleId) ?? result2.ownerRoleId ?? "the target";
|
|
15921
|
+
message = `${entryId} reassigned to ${who}. They'll see it at their next session.`;
|
|
15922
|
+
} else {
|
|
15923
|
+
message = `${entryId} is yours now.`;
|
|
15924
|
+
}
|
|
15925
|
+
return successResult(message, message, result2);
|
|
15926
|
+
} catch (err) {
|
|
15927
|
+
return questionErrorResult(err, "assign", { entryId, toRole, toPerson });
|
|
15928
|
+
}
|
|
15929
|
+
}
|
|
15930
|
+
async function handleQuestionSnooze(entryId) {
|
|
15931
|
+
requireWriteAccess();
|
|
15932
|
+
try {
|
|
15933
|
+
const resolvedEntryId = await resolveEntryRef(entryId, "Entry");
|
|
15934
|
+
const result2 = await kernelMutation("question.snooze", { entryId: resolvedEntryId });
|
|
15935
|
+
let message;
|
|
15936
|
+
if (result2.snoozeUntil) {
|
|
15937
|
+
message = `Snoozed until ${new Date(result2.snoozeUntil).toISOString().slice(0, 10)}. ${result2.snoozeCount ?? "?"} of 2 snoozes used.`;
|
|
15938
|
+
} else if (result2.status === "escalated") {
|
|
15939
|
+
const leadName = await displayName(result2.ownerRoleId) ?? "the team lead";
|
|
15940
|
+
message = `You have used both snoozes on this question \u2014 it has been escalated to ${leadName}.`;
|
|
15941
|
+
} else {
|
|
15942
|
+
message = "No snoozes left \u2014 answer it or force-close it.";
|
|
15943
|
+
}
|
|
15944
|
+
return successResult(message, message, result2);
|
|
15945
|
+
} catch (err) {
|
|
15946
|
+
return questionErrorResult(err, "snooze", { entryId });
|
|
15947
|
+
}
|
|
15948
|
+
}
|
|
15949
|
+
async function handleQuestionDecline(entryId, reason) {
|
|
15950
|
+
requireWriteAccess();
|
|
15951
|
+
try {
|
|
15952
|
+
const resolvedEntryId = await resolveEntryRef(entryId, "Entry");
|
|
15953
|
+
const result2 = await kernelMutation("question.decline", { entryId: resolvedEntryId, reason });
|
|
15954
|
+
let message;
|
|
15955
|
+
if (result2.status === "processed") {
|
|
15956
|
+
message = "Declined and closed.";
|
|
15957
|
+
} else if (result2.isRoot === true) {
|
|
15958
|
+
message = `Declined \u2014 but ${result2.dependentsCount ?? 0} item(s) still depend on it, so it stays with you. Answer it or force-close it.`;
|
|
15959
|
+
} else if (result2.isRoot === false) {
|
|
15960
|
+
const leadName = await displayName(result2.ownerRoleId) ?? result2.ownerRoleId;
|
|
15961
|
+
message = `Declined and escalated to ${leadName}.`;
|
|
15962
|
+
} else if (result2.suggestedRoleId) {
|
|
15963
|
+
const roleName = await displayName(result2.suggestedRoleId) ?? result2.suggestedRoleId;
|
|
15964
|
+
message = `Passed. ${entryId} is now suggested to ${roleName}.`;
|
|
15965
|
+
} else if (result2.ownerRoleId) {
|
|
15966
|
+
const leadName = await displayName(result2.ownerRoleId) ?? result2.ownerRoleId;
|
|
15967
|
+
message = `Passed. Nobody else here owns this, so it has gone to ${leadName}.`;
|
|
15968
|
+
} else {
|
|
15969
|
+
message = "Declined.";
|
|
15970
|
+
}
|
|
15971
|
+
return successResult(message, message, result2);
|
|
15972
|
+
} catch (err) {
|
|
15973
|
+
return questionErrorResult(err, "decline", { entryId, reason });
|
|
15974
|
+
}
|
|
15975
|
+
}
|
|
15976
|
+
async function handleQuestionAnswer(entryId, text, answerEntryId) {
|
|
15977
|
+
if (text === void 0 && answerEntryId === void 0) {
|
|
15978
|
+
return backpressureResult("question", "answer", QUESTION_ACTION_SPECS.answer, "Give the answer as text, or point at an entry with --entry <id>.");
|
|
15979
|
+
}
|
|
15980
|
+
if (text !== void 0 && answerEntryId !== void 0) {
|
|
15981
|
+
return backpressureResult("question", "answer", QUESTION_ACTION_SPECS.answer, "Use either answer text or --entry, not both.");
|
|
15982
|
+
}
|
|
15983
|
+
requireWriteAccess();
|
|
15984
|
+
try {
|
|
15985
|
+
const resolvedEntryId = await resolveEntryRef(entryId, "Entry");
|
|
15986
|
+
const resolvedAnswerEntryId = answerEntryId ? await resolveEntryRef(answerEntryId, "Answer entry") : void 0;
|
|
15987
|
+
const result2 = await kernelMutation("question.answer", { entryId: resolvedEntryId, text, answerEntryId: resolvedAnswerEntryId });
|
|
15988
|
+
const askerName = result2.askerName ?? "The asker";
|
|
15989
|
+
const pendingNote = result2.answerPending ? " The answer is a draft awaiting accept." : "";
|
|
15990
|
+
const message = result2.idempotent ? `${entryId} is already answered \u2014 no change.` : `Answered. ${askerName} will see the outcome at their next session.${pendingNote}`;
|
|
15991
|
+
return successResult(message, message, result2);
|
|
15992
|
+
} catch (err) {
|
|
15993
|
+
return questionErrorResult(err, "answer", { entryId, text, answerEntryId });
|
|
15994
|
+
}
|
|
15995
|
+
}
|
|
15996
|
+
async function handleQuestionForceClose(entryId, reason) {
|
|
15997
|
+
requireWriteAccess();
|
|
15998
|
+
try {
|
|
15999
|
+
const resolvedEntryId = await resolveEntryRef(entryId, "Entry");
|
|
16000
|
+
const result2 = await kernelMutation("question.forceClose", { entryId: resolvedEntryId, reason });
|
|
16001
|
+
const message = result2.idempotent ? `${entryId} is already closed \u2014 no change.` : "Force-closed. Reason recorded.";
|
|
16002
|
+
return successResult(message, message, result2);
|
|
16003
|
+
} catch (err) {
|
|
16004
|
+
return questionErrorResult(err, "force-close", { entryId, reason });
|
|
16005
|
+
}
|
|
16006
|
+
}
|
|
16007
|
+
function questionErrorResult(err, action, params = {}) {
|
|
16008
|
+
const classified = classifyError(err);
|
|
16009
|
+
const definedParams = Object.fromEntries(Object.entries(params).filter(([, v]) => v !== void 0));
|
|
16010
|
+
return failureResult(
|
|
16011
|
+
classified.message,
|
|
16012
|
+
classified.code,
|
|
16013
|
+
classified.message,
|
|
16014
|
+
classified.recovery ?? "Use action='create' with no --domain, or `pb question --help` / this tool's description, to find valid domains and next steps.",
|
|
16015
|
+
classified.availableActions ?? [{ tool: "question", description: `Retry ${action}`, parameters: { action, ...definedParams } }],
|
|
16016
|
+
classified.diagnostics
|
|
16017
|
+
);
|
|
16018
|
+
}
|
|
16019
|
+
function registerQuestionTools(server) {
|
|
16020
|
+
const questionHandlers = {
|
|
16021
|
+
create: (data) => handleQuestionCreate(data.text, data.domain, data.blocks),
|
|
16022
|
+
adopt: (data) => handleQuestionAdopt(data.entryId, data.domain, data.blocks),
|
|
16023
|
+
assign: (data) => handleQuestionAssign(data.entryId, data.toRole, data.toPerson),
|
|
16024
|
+
snooze: (data) => handleQuestionSnooze(data.entryId),
|
|
16025
|
+
decline: (data) => handleQuestionDecline(data.entryId, data.reason),
|
|
16026
|
+
answer: (data) => handleQuestionAnswer(data.entryId, data.text, data.answerEntryId),
|
|
16027
|
+
"force-close": (data) => handleQuestionForceClose(data.entryId, data.reason)
|
|
16028
|
+
};
|
|
16029
|
+
server.registerTool(
|
|
16030
|
+
"question",
|
|
16031
|
+
{
|
|
16032
|
+
title: "Question",
|
|
16033
|
+
description: "Routed questions \u2014 a question with no owner routes itself to the owning role, the team lead when ambiguous, and up the team-lead chain until someone owns it. Seven write actions:\n\n- **create**: create a new routed question from text. Domains are discovered by using this tool \u2014 an unresolvable `domain` refusal lists the workspace's real slugs; there is no fixed list to memorize.\n- **adopt**: attach routing to a tension already captured (e.g. via `capture` with `collection: tensions`) \u2014 the real birth path for most questions. Without adopt, a captured tension stays invisible to routing forever.\n- **assign**: confirm ('take it', no `toRole`/`toPerson`) or reassign (`toRole`/`toPerson`) ownership.\n- **snooze**: defer a routed/escalated question \u2014 max 2, then it escalates to the team lead.\n- **decline**: pass on a suggested question (never closes it), or \u2014 as the owner/team lead \u2014 decline an owned one (reason required; closes it, or escalates it, depending on dependents).\n- **answer**: close the loop. Creates the answering entry itself from `text`, or points at an existing one via `answerEntryId` \u2014 never both.\n- **force-close**: Team-Lead-only escape hatch (reason required) for a question that cannot otherwise be resolved.\n\n`blocks` (repeatable, on create/adopt) records what a question is holding up \u2014 pass entry ids it blocks.",
|
|
16034
|
+
inputSchema: questionSchema,
|
|
16035
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false }
|
|
16036
|
+
},
|
|
16037
|
+
thinWrapper(async (args) => {
|
|
16038
|
+
const parsed = parseOrFail(questionSchema, args);
|
|
16039
|
+
if (!parsed.ok) return parsed.result;
|
|
16040
|
+
const { action } = parsed.data;
|
|
16041
|
+
return runWithToolContext(
|
|
16042
|
+
{ tool: "question", action },
|
|
16043
|
+
() => dispatchDiscriminated("question", questionActionUnion, parsed.data, QUESTION_ACTION_SPECS, questionHandlers)
|
|
16044
|
+
);
|
|
16045
|
+
})
|
|
16046
|
+
);
|
|
16047
|
+
}
|
|
16048
|
+
|
|
15812
16049
|
// src/resources/index.ts
|
|
15813
16050
|
import { existsSync as existsSync3 } from "fs";
|
|
15814
16051
|
import { dirname, join, resolve as resolve4 } from "path";
|
|
@@ -15818,7 +16055,7 @@ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
15818
16055
|
// src/resources/agentCheatsheet.ts
|
|
15819
16056
|
var AGENT_CHEATSHEET = `# Product Brain \u2014 Agent Cheatsheet
|
|
15820
16057
|
|
|
15821
|
-
## Core Tools (Serves
|
|
16058
|
+
## Core Tools (Serves 13 default compound tools; 3 more (chain, chain-review, map) behind PB_MODULES=gitchain)
|
|
15822
16059
|
| Tool | Purpose | Key params |
|
|
15823
16060
|
|---|---|---|
|
|
15824
16061
|
| \`orient\` | Workspace context, governance, active bets, session start | \`action\`: start, task, record-activation |
|
|
@@ -15833,6 +16070,7 @@ var AGENT_CHEATSHEET = `# Product Brain \u2014 Agent Cheatsheet
|
|
|
15833
16070
|
| \`workspace\` | Check / whoami / status / self-test, usage, governance proposals | \`action\`: check, whoami, status, audit, self-test, usage, proposals-* |
|
|
15834
16071
|
| \`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 |
|
|
15835
16072
|
| \`shape\` | Review / disposition write-shape (single-concern split) advisories | \`action\`: list, show, agree, dismiss |
|
|
16073
|
+
| \`question\` | Routed questions \u2014 create/adopt, own, snooze, decline, answer, force-close | \`action\`: create, adopt, assign, snooze, decline, answer, force-close |
|
|
15836
16074
|
|
|
15837
16075
|
## Collection Prefixes
|
|
15838
16076
|
GLO (glossary), BR (business-rules), PRI (principles), STD (standards),
|
|
@@ -16330,12 +16568,12 @@ ${entry.labels.map((l) => `- ${l.name ?? l.slug}`).join("\n")}`);
|
|
|
16330
16568
|
}
|
|
16331
16569
|
|
|
16332
16570
|
// src/prompts/index.ts
|
|
16333
|
-
import { z as
|
|
16571
|
+
import { z as z35 } from "zod/v3";
|
|
16334
16572
|
function registerPrompts(server) {
|
|
16335
16573
|
server.prompt(
|
|
16336
16574
|
"review-against-rules",
|
|
16337
16575
|
"Review code or a design decision against all business rules for a given domain. Fetches the rules and asks you to do a structured compliance review.",
|
|
16338
|
-
{ domain:
|
|
16576
|
+
{ domain: z35.string().describe("Business rule domain (e.g. 'Identity & Access', 'Governance & Decision-Making')") },
|
|
16339
16577
|
async ({ domain }) => {
|
|
16340
16578
|
const entries = await kernelQuery("chain.listEntries", { collectionSlug: "business-rules" });
|
|
16341
16579
|
const rules = entries.filter((e) => e.data?.domain === domain);
|
|
@@ -16388,7 +16626,7 @@ Provide a structured review with a compliance status for each rule (COMPLIANT /
|
|
|
16388
16626
|
server.prompt(
|
|
16389
16627
|
"name-check",
|
|
16390
16628
|
"Check variable names, field names, or API names against the glossary for terminology alignment. Flags drift from canonical terms.",
|
|
16391
|
-
{ names:
|
|
16629
|
+
{ names: z35.string().describe("Comma-separated list of names to check (e.g. 'vendor_id, compliance_level, formulator_type')") },
|
|
16392
16630
|
async ({ names }) => {
|
|
16393
16631
|
const terms = await kernelQuery("chain.listEntries", { collectionSlug: "glossary" });
|
|
16394
16632
|
const glossaryContext = terms.map(
|
|
@@ -16424,7 +16662,7 @@ Format as a table: Name | Status | Canonical Form | Action Needed`
|
|
|
16424
16662
|
server.prompt(
|
|
16425
16663
|
"draft-decision-record",
|
|
16426
16664
|
"Draft a structured decision record from a description of what was decided. Includes context from recent decisions and relevant rules.",
|
|
16427
|
-
{ context:
|
|
16665
|
+
{ context: z35.string().describe("Description of the decision (e.g. 'We decided to use MRSL v3.1 as the conformance baseline because...')") },
|
|
16428
16666
|
async ({ context }) => {
|
|
16429
16667
|
const recentDecisions = await kernelQuery("chain.listEntries", { collectionSlug: "decisions" });
|
|
16430
16668
|
const sorted = [...recentDecisions].sort((a, b) => (b.data?.date ?? "") > (a.data?.date ?? "") ? 1 : -1).slice(0, 5);
|
|
@@ -16462,8 +16700,8 @@ After drafting, I can log it using the capture tool with collection "decisions".
|
|
|
16462
16700
|
"draft-rule-from-context",
|
|
16463
16701
|
"Draft a new business rule from an observation or discovery made while coding. Fetches existing rules for the domain to ensure consistency.",
|
|
16464
16702
|
{
|
|
16465
|
-
observation:
|
|
16466
|
-
domain:
|
|
16703
|
+
observation: z35.string().describe("What you observed or discovered (e.g. 'Suppliers can have multiple org types in Gateway')"),
|
|
16704
|
+
domain: z35.string().describe("Which domain this rule belongs to (e.g. 'Governance & Decision-Making')")
|
|
16467
16705
|
},
|
|
16468
16706
|
async ({ observation, domain }) => {
|
|
16469
16707
|
const allRules = await kernelQuery("chain.listEntries", { collectionSlug: "business-rules" });
|
|
@@ -16529,7 +16767,7 @@ var INSTRUCTIONS = [
|
|
|
16529
16767
|
"- **Self-documenting**: orient and server instructions teach agents how PB works. Cursor rules supplement but don't replace.",
|
|
16530
16768
|
"",
|
|
16531
16769
|
"### Tool Rename Notice (WP-484 S3, one release)",
|
|
16532
|
-
"Serves
|
|
16770
|
+
"Serves 13 default compound tools (WP-559 adds `shape` \u2014 review/disposition for write-shape advisories; WP-638 S3 adds `question` \u2014 routed-question routing/ownership/lifecycle); 3 more (chain, chain-review, map) behind PB_MODULES=gitchain.",
|
|
16533
16771
|
"Old standalone names are gone \u2014 no alias window (ASM-43: zero external consumers). Map: `start_pb`\u2192`orient action=start`, `record_activation`\u2192`orient action=record-activation`, `session-wrapup`\u2192`session action=wrapup-review|wrapup-commit`, `facilitate`\u2192`session action=resume|commit-constellation`, `update-entry`\u2192`entries action=update`, `commit-entry`\u2192`entries action=commit`, `get-history`\u2192`entries action=history`, `move-entry`\u2192`entries action=move`, `verify-entry`\u2192`entries action=verify`, `graph`\u2192`relations action=find|suggest`, `documents`\u2192`context action=last-verified-brief`, `labels`\u2192`collections action=label-*`, `verify`\u2192`quality action=verify-chain`, `audit`\u2192`quality action=audit`, `health`\u2192`workspace action=check|whoami|status|self-test`, `get-usage-summary`\u2192`workspace action=usage`, `governance-proposals`\u2192`workspace action=proposals-*`, `skills`\u2192`workflows action=load-skill`, `chain-version`\u2192`chain action=version.*`, `chain-branch`\u2192`chain-review action=branch.*`, `create-audience-map-set`/`map-slot`/`map-version`/`map-suggest`\u2192`map action=create-audience-set|slot.*|version.*|suggest`.",
|
|
16534
16772
|
"",
|
|
16535
16773
|
"## Your Workspace Principles",
|
|
@@ -16599,6 +16837,7 @@ function createProductBrainServer() {
|
|
|
16599
16837
|
registerWorkspaceTools(server);
|
|
16600
16838
|
registerFeedbackTool(server);
|
|
16601
16839
|
registerShapeTools(server);
|
|
16840
|
+
registerQuestionTools(server);
|
|
16602
16841
|
if (enabledModules.has("gitchain")) registerGitChainTools(server);
|
|
16603
16842
|
if (enabledModules.has("gitchain")) registerMapTools(server);
|
|
16604
16843
|
registerResources(server);
|
|
@@ -16739,4 +16978,4 @@ export {
|
|
|
16739
16978
|
createProductBrainServer,
|
|
16740
16979
|
initFeatureFlags
|
|
16741
16980
|
};
|
|
16742
|
-
//# sourceMappingURL=chunk-
|
|
16981
|
+
//# sourceMappingURL=chunk-3NAU25R5.js.map
|