@productbrain/mcp 0.0.1-beta.7808 → 0.0.1-beta.7814
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.
|
@@ -400,6 +400,15 @@ function formatOwnerLine(owner, options = {}) {
|
|
|
400
400
|
}
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
+
// src/lib/proseBody.ts
|
|
404
|
+
function readEntryBody(data, proseKey) {
|
|
405
|
+
const body = data?.body;
|
|
406
|
+
if (typeof body === "string" && body.trim().length > 0) return body;
|
|
407
|
+
if (!data || !proseKey) return void 0;
|
|
408
|
+
const legacy = data[proseKey];
|
|
409
|
+
return typeof legacy === "string" ? legacy : void 0;
|
|
410
|
+
}
|
|
411
|
+
|
|
403
412
|
// src/tools/knowledge-helpers.ts
|
|
404
413
|
var EPISTEMIC_COLLECTIONS = /* @__PURE__ */ new Set(["insights", "assumptions"]);
|
|
405
414
|
function deriveEpistemicStatus(entry) {
|
|
@@ -481,8 +490,8 @@ function formatOwnerLine2(owner) {
|
|
|
481
490
|
function extractPreview(data, maxLen) {
|
|
482
491
|
if (!data || typeof data !== "object") return "";
|
|
483
492
|
const d = data;
|
|
484
|
-
const raw = d
|
|
485
|
-
if (
|
|
493
|
+
const raw = readEntryBody(d, "description") ?? readEntryBody(d, "canonical") ?? readEntryBody(d, "detail") ?? readEntryBody(d, "rule") ?? "";
|
|
494
|
+
if (!raw) return "";
|
|
486
495
|
return raw.length > maxLen ? raw.substring(0, maxLen) + "..." : raw;
|
|
487
496
|
}
|
|
488
497
|
var TOOL_NAME_MIGRATIONS = /* @__PURE__ */ new Map([
|
|
@@ -2731,7 +2740,7 @@ async function runContradictionCheck(name, description, excludeEntryId, excludeD
|
|
|
2731
2740
|
return true;
|
|
2732
2741
|
}).slice(0, 5);
|
|
2733
2742
|
for (const entry of allGov) {
|
|
2734
|
-
const entryTokens = new Set(tokenizeText(`${entry.name} ${entry.data
|
|
2743
|
+
const entryTokens = new Set(tokenizeText(`${entry.name} ${readEntryBody(entry.data, "description") ?? ""}`));
|
|
2735
2744
|
const matched = keyTerms.filter((t) => entryTokens.has(t));
|
|
2736
2745
|
if (matched.length < 3) continue;
|
|
2737
2746
|
let governsCount = 0;
|
|
@@ -2994,7 +3003,7 @@ async function handleCommitEntry({ entryId, preview, steeringOverrideReason, coh
|
|
|
2994
3003
|
if (!entry) {
|
|
2995
3004
|
return notFoundResult(entryId, `Entry '${entryId}' not found. Try search to find the right ID.`);
|
|
2996
3005
|
}
|
|
2997
|
-
const descField = entry.data
|
|
3006
|
+
const descField = readEntryBody(entry.data, "description") ?? readEntryBody(entry.data, "why") ?? readEntryBody(entry.data, "rationale") ?? readEntryBody(entry.data, "canonical") ?? "";
|
|
2998
3007
|
const preflight = await runConflictPreflight(
|
|
2999
3008
|
entry.name,
|
|
3000
3009
|
descField,
|
|
@@ -14251,6 +14260,42 @@ async function buildOrientationResourceText(kernelQuery2, agentSessionId) {
|
|
|
14251
14260
|
return buildOrientationMarkdown(collections, standards, businessRules, principles, directionLines, ruleLines);
|
|
14252
14261
|
}
|
|
14253
14262
|
|
|
14263
|
+
// src/resources/terminologyResource.ts
|
|
14264
|
+
async function buildTerminologyResourceText() {
|
|
14265
|
+
const [glossaryResult, standardsResult] = await Promise.allSettled([
|
|
14266
|
+
kernelQuery("chain.listEntries", { collectionSlug: "glossary" }),
|
|
14267
|
+
kernelQuery("chain.listEntries", { collectionSlug: "standards" })
|
|
14268
|
+
]);
|
|
14269
|
+
const lines = ["# Product Brain \u2014 Terminology"];
|
|
14270
|
+
if (glossaryResult.status === "fulfilled") {
|
|
14271
|
+
const glossary = glossaryResult.value ?? [];
|
|
14272
|
+
if (glossary.length > 0) {
|
|
14273
|
+
const terms = glossary.map((t) => `- **${t.name}** (${t.entryId ?? "\u2014"}) [${t.status}]: ${readEntryBody(t.data, "canonical") ?? readEntryBody(t.data, "description") ?? ""}`).join("\n");
|
|
14274
|
+
lines.push(`## Glossary (${glossary.length} terms)
|
|
14275
|
+
|
|
14276
|
+
${terms}`);
|
|
14277
|
+
} else {
|
|
14278
|
+
lines.push("## Glossary\n\nNo glossary terms yet. Use `capture` with collection `glossary` to add terms.");
|
|
14279
|
+
}
|
|
14280
|
+
} else {
|
|
14281
|
+
lines.push("## Glossary\n\nCould not load glossary \u2014 use `entries action=list collection=glossary` to browse manually.");
|
|
14282
|
+
}
|
|
14283
|
+
if (standardsResult.status === "fulfilled") {
|
|
14284
|
+
const standards = standardsResult.value ?? [];
|
|
14285
|
+
if (standards.length > 0) {
|
|
14286
|
+
const stds = standards.map((s) => `- **${s.name}** (${s.entryId ?? "\u2014"}) [${s.status}]: ${readEntryBody(s.data, "description") ?? ""}`).join("\n");
|
|
14287
|
+
lines.push(`## Standards (${standards.length} entries)
|
|
14288
|
+
|
|
14289
|
+
${stds}`);
|
|
14290
|
+
} else {
|
|
14291
|
+
lines.push("## Standards\n\nNo standards yet. Use `capture` with collection `standards` to add standards.");
|
|
14292
|
+
}
|
|
14293
|
+
} else {
|
|
14294
|
+
lines.push("## Standards\n\nCould not load standards \u2014 use `entries action=list collection=standards` to browse manually.");
|
|
14295
|
+
}
|
|
14296
|
+
return lines.join("\n\n---\n\n");
|
|
14297
|
+
}
|
|
14298
|
+
|
|
14254
14299
|
// src/resources/index.ts
|
|
14255
14300
|
var MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
14256
14301
|
var UI_VIEW_BASE_CANDIDATES = [
|
|
@@ -14294,42 +14339,9 @@ function registerResources(server) {
|
|
|
14294
14339
|
server.resource(
|
|
14295
14340
|
"chain-terminology",
|
|
14296
14341
|
"productbrain://terminology",
|
|
14297
|
-
async (uri) => {
|
|
14298
|
-
|
|
14299
|
-
|
|
14300
|
-
kernelQuery("chain.listEntries", { collectionSlug: "standards" })
|
|
14301
|
-
]);
|
|
14302
|
-
const lines = ["# Product Brain \u2014 Terminology"];
|
|
14303
|
-
if (glossaryResult.status === "fulfilled") {
|
|
14304
|
-
const glossary = glossaryResult.value ?? [];
|
|
14305
|
-
if (glossary.length > 0) {
|
|
14306
|
-
const terms = glossary.map((t) => `- **${t.name}** (${t.entryId ?? "\u2014"}) [${t.status}]: ${t.data?.canonical ?? t.data?.description ?? ""}`).join("\n");
|
|
14307
|
-
lines.push(`## Glossary (${glossary.length} terms)
|
|
14308
|
-
|
|
14309
|
-
${terms}`);
|
|
14310
|
-
} else {
|
|
14311
|
-
lines.push("## Glossary\n\nNo glossary terms yet. Use `capture` with collection `glossary` to add terms.");
|
|
14312
|
-
}
|
|
14313
|
-
} else {
|
|
14314
|
-
lines.push("## Glossary\n\nCould not load glossary \u2014 use `entries action=list collection=glossary` to browse manually.");
|
|
14315
|
-
}
|
|
14316
|
-
if (standardsResult.status === "fulfilled") {
|
|
14317
|
-
const standards = standardsResult.value ?? [];
|
|
14318
|
-
if (standards.length > 0) {
|
|
14319
|
-
const stds = standards.map((s) => `- **${s.name}** (${s.entryId ?? "\u2014"}) [${s.status}]: ${s.data?.description ?? ""}`).join("\n");
|
|
14320
|
-
lines.push(`## Standards (${standards.length} entries)
|
|
14321
|
-
|
|
14322
|
-
${stds}`);
|
|
14323
|
-
} else {
|
|
14324
|
-
lines.push("## Standards\n\nNo standards yet. Use `capture` with collection `standards` to add standards.");
|
|
14325
|
-
}
|
|
14326
|
-
} else {
|
|
14327
|
-
lines.push("## Standards\n\nCould not load standards \u2014 use `entries action=list collection=standards` to browse manually.");
|
|
14328
|
-
}
|
|
14329
|
-
return {
|
|
14330
|
-
contents: [{ uri: uri.href, text: lines.join("\n\n---\n\n"), mimeType: "text/markdown" }]
|
|
14331
|
-
};
|
|
14332
|
-
}
|
|
14342
|
+
async (uri) => ({
|
|
14343
|
+
contents: [{ uri: uri.href, text: await buildTerminologyResourceText(), mimeType: "text/markdown" }]
|
|
14344
|
+
})
|
|
14333
14345
|
);
|
|
14334
14346
|
server.resource(
|
|
14335
14347
|
"chain-collections",
|
|
@@ -14610,43 +14622,38 @@ ${entry.labels.map((l) => `- ${l.name ?? l.slug}`).join("\n")}`);
|
|
|
14610
14622
|
|
|
14611
14623
|
// src/prompts/index.ts
|
|
14612
14624
|
import { z as z34 } from "zod/v3";
|
|
14613
|
-
function
|
|
14614
|
-
|
|
14615
|
-
|
|
14616
|
-
|
|
14617
|
-
{
|
|
14618
|
-
|
|
14619
|
-
|
|
14620
|
-
|
|
14621
|
-
|
|
14622
|
-
|
|
14623
|
-
|
|
14624
|
-
|
|
14625
|
-
|
|
14626
|
-
|
|
14627
|
-
|
|
14628
|
-
|
|
14629
|
-
|
|
14630
|
-
|
|
14631
|
-
]
|
|
14632
|
-
};
|
|
14633
|
-
}
|
|
14634
|
-
const rulesText = rules.map(
|
|
14635
|
-
(r) => `### ${r.entryId ?? ""}: ${r.name}
|
|
14625
|
+
async function buildReviewAgainstRulesPrompt(domain) {
|
|
14626
|
+
const entries = await kernelQuery("chain.listEntries", { collectionSlug: "business-rules" });
|
|
14627
|
+
const rules = entries.filter((e) => e.data?.domain === domain);
|
|
14628
|
+
if (rules.length === 0) {
|
|
14629
|
+
return {
|
|
14630
|
+
messages: [
|
|
14631
|
+
{
|
|
14632
|
+
role: "user",
|
|
14633
|
+
content: {
|
|
14634
|
+
type: "text",
|
|
14635
|
+
text: `No business rules found for domain "${domain}". Use \`entries action=list collection=business-rules\` to see available domains.`
|
|
14636
|
+
}
|
|
14637
|
+
}
|
|
14638
|
+
]
|
|
14639
|
+
};
|
|
14640
|
+
}
|
|
14641
|
+
const rulesText = rules.map(
|
|
14642
|
+
(r) => `### ${r.entryId ?? ""}: ${r.name}
|
|
14636
14643
|
Status: ${r.status} | Severity: ${r.data?.severity ?? "unknown"}
|
|
14637
|
-
Description: ${r.data
|
|
14644
|
+
Description: ${readEntryBody(r.data, "description") ?? ""}
|
|
14638
14645
|
Data Impact: ${r.data?.dataImpact ?? ""}
|
|
14639
14646
|
Platforms: ${(r.data?.platforms ?? []).join(", ")}
|
|
14640
14647
|
` + (r.data?.conflictWith ? `CONFLICT: ${r.data.conflictWith.rule} \u2014 ${r.data.conflictWith.nature}
|
|
14641
14648
|
` : "")
|
|
14642
|
-
|
|
14643
|
-
|
|
14644
|
-
|
|
14645
|
-
|
|
14646
|
-
|
|
14647
|
-
|
|
14648
|
-
|
|
14649
|
-
|
|
14649
|
+
).join("\n---\n\n");
|
|
14650
|
+
return {
|
|
14651
|
+
messages: [
|
|
14652
|
+
{
|
|
14653
|
+
role: "user",
|
|
14654
|
+
content: {
|
|
14655
|
+
type: "text",
|
|
14656
|
+
text: `Review the current code or design against the following business rules for the "${domain}" domain.
|
|
14650
14657
|
|
|
14651
14658
|
For each rule, assess:
|
|
14652
14659
|
1. Is the current implementation compliant?
|
|
@@ -14658,29 +14665,24 @@ Business Rules:
|
|
|
14658
14665
|
${rulesText}
|
|
14659
14666
|
|
|
14660
14667
|
Provide a structured review with a compliance status for each rule (COMPLIANT / AT RISK / VIOLATION / NOT APPLICABLE).`
|
|
14661
|
-
|
|
14662
|
-
|
|
14663
|
-
|
|
14664
|
-
|
|
14665
|
-
|
|
14666
|
-
|
|
14667
|
-
|
|
14668
|
-
|
|
14669
|
-
"
|
|
14670
|
-
{ names: z34.string().describe("Comma-separated list of names to check (e.g. 'vendor_id, compliance_level, formulator_type')") },
|
|
14671
|
-
async ({ names }) => {
|
|
14672
|
-
const terms = await kernelQuery("chain.listEntries", { collectionSlug: "glossary" });
|
|
14673
|
-
const glossaryContext = terms.map(
|
|
14674
|
-
(t) => `${t.name} (${t.entryId ?? ""}) [${t.status}]: ${t.data?.canonical ?? ""}` + (t.data?.confusedWith?.length > 0 ? ` \u2014 Often confused with: ${t.data.confusedWith.join(", ")}` : "") + (t.data?.codeMapping?.length > 0 ? `
|
|
14668
|
+
}
|
|
14669
|
+
}
|
|
14670
|
+
]
|
|
14671
|
+
};
|
|
14672
|
+
}
|
|
14673
|
+
async function buildNameCheckPrompt(names) {
|
|
14674
|
+
const terms = await kernelQuery("chain.listEntries", { collectionSlug: "glossary" });
|
|
14675
|
+
const glossaryContext = terms.map(
|
|
14676
|
+
(t) => `${t.name} (${t.entryId ?? ""}) [${t.status}]: ${readEntryBody(t.data, "canonical") ?? ""}` + (t.data?.confusedWith?.length > 0 ? ` \u2014 Often confused with: ${t.data.confusedWith.join(", ")}` : "") + (t.data?.codeMapping?.length > 0 ? `
|
|
14675
14677
|
Code mappings: ${t.data.codeMapping.map((m) => `${m.platform}:${m.field}`).join(", ")}` : "")
|
|
14676
|
-
|
|
14677
|
-
|
|
14678
|
-
|
|
14679
|
-
|
|
14680
|
-
|
|
14681
|
-
|
|
14682
|
-
|
|
14683
|
-
|
|
14678
|
+
).join("\n");
|
|
14679
|
+
return {
|
|
14680
|
+
messages: [
|
|
14681
|
+
{
|
|
14682
|
+
role: "user",
|
|
14683
|
+
content: {
|
|
14684
|
+
type: "text",
|
|
14685
|
+
text: `Check the following names against the glossary for terminology alignment:
|
|
14684
14686
|
|
|
14685
14687
|
Names to check: ${names}
|
|
14686
14688
|
|
|
@@ -14694,11 +14696,23 @@ For each name:
|
|
|
14694
14696
|
4. Flag any names that don't have a corresponding glossary term (might need one).
|
|
14695
14697
|
|
|
14696
14698
|
Format as a table: Name | Status | Canonical Form | Action Needed`
|
|
14697
|
-
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
|
|
14701
|
-
|
|
14699
|
+
}
|
|
14700
|
+
}
|
|
14701
|
+
]
|
|
14702
|
+
};
|
|
14703
|
+
}
|
|
14704
|
+
function registerPrompts(server) {
|
|
14705
|
+
server.prompt(
|
|
14706
|
+
"review-against-rules",
|
|
14707
|
+
"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.",
|
|
14708
|
+
{ domain: z34.string().describe("Business rule domain (e.g. 'Identity & Access', 'Governance & Decision-Making')") },
|
|
14709
|
+
async ({ domain }) => buildReviewAgainstRulesPrompt(domain)
|
|
14710
|
+
);
|
|
14711
|
+
server.prompt(
|
|
14712
|
+
"name-check",
|
|
14713
|
+
"Check variable names, field names, or API names against the glossary for terminology alignment. Flags drift from canonical terms.",
|
|
14714
|
+
{ names: z34.string().describe("Comma-separated list of names to check (e.g. 'vendor_id, compliance_level, formulator_type')") },
|
|
14715
|
+
async ({ names }) => buildNameCheckPrompt(names)
|
|
14702
14716
|
);
|
|
14703
14717
|
server.prompt(
|
|
14704
14718
|
"draft-decision-record",
|
|
@@ -14744,17 +14758,20 @@ After drafting, I can log it using the capture tool with collection "decisions".
|
|
|
14744
14758
|
observation: z34.string().describe("What you observed or discovered (e.g. 'Suppliers can have multiple org types in Gateway')"),
|
|
14745
14759
|
domain: z34.string().describe("Which domain this rule belongs to (e.g. 'Governance & Decision-Making')")
|
|
14746
14760
|
},
|
|
14747
|
-
async ({ observation, domain }) =>
|
|
14748
|
-
|
|
14749
|
-
|
|
14750
|
-
|
|
14751
|
-
|
|
14752
|
-
|
|
14753
|
-
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
|
|
14757
|
-
|
|
14761
|
+
async ({ observation, domain }) => buildDraftRuleFromContextPrompt(observation, domain)
|
|
14762
|
+
);
|
|
14763
|
+
}
|
|
14764
|
+
async function buildDraftRuleFromContextPrompt(observation, domain) {
|
|
14765
|
+
const allRules = await kernelQuery("chain.listEntries", { collectionSlug: "business-rules" });
|
|
14766
|
+
const existingRules = allRules.filter((r) => r.data?.domain === domain);
|
|
14767
|
+
const existingContext = existingRules.length > 0 ? existingRules.map((r) => `${r.entryId ?? ""}: ${r.name} [${r.status}] \u2014 ${readEntryBody(r.data, "description") ?? ""}`).join("\n") : "No existing rules for this domain.";
|
|
14768
|
+
return {
|
|
14769
|
+
messages: [
|
|
14770
|
+
{
|
|
14771
|
+
role: "user",
|
|
14772
|
+
content: {
|
|
14773
|
+
type: "text",
|
|
14774
|
+
text: `Draft a business rule based on this observation:
|
|
14758
14775
|
|
|
14759
14776
|
"${observation}"
|
|
14760
14777
|
|
|
@@ -14773,12 +14790,10 @@ Draft the rule with these fields (do not specify entryId \u2014 Convex assigns s
|
|
|
14773
14790
|
7. **data.relatedRules**: Any related existing rules
|
|
14774
14791
|
|
|
14775
14792
|
Make sure the rule is consistent with existing rules and doesn't contradict them. After drafting, I can create it using the capture tool with collection "business-rules".`
|
|
14776
|
-
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
}
|
|
14781
|
-
);
|
|
14793
|
+
}
|
|
14794
|
+
}
|
|
14795
|
+
]
|
|
14796
|
+
};
|
|
14782
14797
|
}
|
|
14783
14798
|
|
|
14784
14799
|
// src/server.ts
|
|
@@ -14901,4 +14916,4 @@ export {
|
|
|
14901
14916
|
createProductBrainServer,
|
|
14902
14917
|
initFeatureFlags
|
|
14903
14918
|
};
|
|
14904
|
-
//# sourceMappingURL=chunk-
|
|
14919
|
+
//# sourceMappingURL=chunk-5HVA3T22.js.map
|