@productbrain/mcp 0.0.1-beta.149 → 0.0.1-beta.150

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.
@@ -6,14 +6,21 @@ import {
6
6
  trackCaptureQualityHints,
7
7
  trackCaptureRelationSuggestions,
8
8
  trackChainEntryCommitted,
9
+ trackCollectionClassified,
10
+ trackFieldGuidanceApplied,
11
+ trackFieldQualityWarning,
9
12
  trackKnowledgeGap,
10
13
  trackQualityCheck,
11
14
  trackQualityVerdict,
12
15
  trackToolCall
13
- } from "./chunk-MOPOQUJP.js";
16
+ } from "./chunk-X3S5UTTZ.js";
14
17
 
15
18
  // src/auth.ts
16
19
  import { AsyncLocalStorage } from "async_hooks";
20
+ import { createHash } from "crypto";
21
+ function hashKey(key) {
22
+ return createHash("sha256").update(key).digest("hex").slice(0, 16);
23
+ }
17
24
  var requestStore = new AsyncLocalStorage();
18
25
  function runWithAuth(auth, fn) {
19
26
  return requestStore.run(auth, fn);
@@ -996,6 +1003,21 @@ var FIELD_TYPE_DEFAULTS = {
996
1003
  "date": null
997
1004
  };
998
1005
 
1006
+ // src/lib/formatFieldGuidance.ts
1007
+ function formatFieldGuidance(fields) {
1008
+ const guidedFields = fields.filter((f) => f.writingGuidance);
1009
+ if (guidedFields.length === 0) return "";
1010
+ const lines = ["## Writing Guidance"];
1011
+ for (const field of guidedFields) {
1012
+ const displayName = field.label || field.key;
1013
+ lines.push(`- ${displayName}: ${field.writingGuidance}`);
1014
+ if (field.writingExamples && field.writingExamples.length > 0) {
1015
+ lines.push(` Examples: ${field.writingExamples.join(" | ")}`);
1016
+ }
1017
+ }
1018
+ return lines.join("\n");
1019
+ }
1020
+
999
1021
  // src/lib/collectionCache.ts
1000
1022
  var cachedCollections = null;
1001
1023
  var cachedBySlug = null;
@@ -1908,6 +1930,20 @@ function buildClassifierMeta(resolved, overrides = {}) {
1908
1930
  ...overrides
1909
1931
  };
1910
1932
  }
1933
+ function emitClassificationTelemetry(resolved, workspaceId, opts) {
1934
+ const topAlt = resolved.alternatives?.[0] ?? null;
1935
+ trackCollectionClassified(workspaceId, {
1936
+ collection_slug: resolved.collection,
1937
+ thinking_layer: resolved.thinkingLayer ?? null,
1938
+ confidence: resolved.confidence,
1939
+ classified_by: resolved.classifiedBy,
1940
+ confidence_tier: resolved.tier,
1941
+ alternative_slug: topAlt?.collection ?? null,
1942
+ alternative_confidence: topAlt?.confidence ?? null,
1943
+ explicit_collection_provided: opts.explicitCollectionProvided,
1944
+ auto_routed: opts.autoRouted
1945
+ });
1946
+ }
1911
1947
  async function resolveCaptureCollection(params) {
1912
1948
  const {
1913
1949
  collection,
@@ -1929,6 +1965,10 @@ async function resolveCaptureCollection(params) {
1929
1965
  overrideCommand: `capture collection="${resolved2.collection}"`
1930
1966
  }
1931
1967
  });
1968
+ emitClassificationTelemetry(resolved2, workspaceId, {
1969
+ explicitCollectionProvided: true,
1970
+ autoRouted: false
1971
+ });
1932
1972
  if (!agrees) {
1933
1973
  trackClassifierTelemetry({
1934
1974
  workspaceId,
@@ -1975,6 +2015,10 @@ async function resolveCaptureCollection(params) {
1975
2015
  }
1976
2016
  const autoRoute = resolved.tier !== "low";
1977
2017
  const classifierMeta = buildClassifierMeta(resolved, { autoRouted: autoRoute });
2018
+ emitClassificationTelemetry(resolved, workspaceId, {
2019
+ explicitCollectionProvided,
2020
+ autoRouted: autoRoute
2021
+ });
1978
2022
  if (!autoRoute) {
1979
2023
  trackClassifierTelemetry({
1980
2024
  workspaceId,
@@ -2260,7 +2304,8 @@ Or use \`collections action=list\` to see available collections.`
2260
2304
  const colMeta = await getCollectionBySlug(resolvedCollection);
2261
2305
  const isBetCapture = canonicalKey === "work_package" || colMeta?.defaultCanonicalKey === "work_package";
2262
2306
  let entryWarnings = [];
2263
- const shouldDecompose = (resolvedCollection === "strategy" || isBetCapture) && description.trim().length > 0;
2307
+ const isStrategyCapture = colMeta?.thinkingLayer === "strategy";
2308
+ const shouldDecompose = (isStrategyCapture || isBetCapture) && description.trim().length > 0;
2264
2309
  if (shouldDecompose) {
2265
2310
  try {
2266
2311
  const decomposed = await mcpCall(
@@ -2270,7 +2315,8 @@ Or use \`collections action=list\` to see available collections.`
2270
2315
  entryName: name,
2271
2316
  description,
2272
2317
  existingData: data,
2273
- ...isBetCapture ? { canonicalKey: "work_package" } : {}
2318
+ ...isBetCapture ? { canonicalKey: "work_package" } : {},
2319
+ ...colMeta?.thinkingLayer ? { thinkingLayer: colMeta.thinkingLayer } : {}
2274
2320
  }
2275
2321
  );
2276
2322
  if (decomposed?.decomposed) {
@@ -2630,6 +2676,16 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2630
2676
  lines.push("");
2631
2677
  lines.push(`_To improve: \`update-entry entryId="${finalEntryId}"\` to fill missing fields._`);
2632
2678
  }
2679
+ const fieldGuidanceSection = formatFieldGuidance(col.fields ?? []);
2680
+ if (fieldGuidanceSection) {
2681
+ lines.push("");
2682
+ lines.push(fieldGuidanceSection);
2683
+ const guidedFieldCount = (col.fields ?? []).filter((f) => f.writingGuidance).length;
2684
+ trackFieldGuidanceApplied(wsCtx.workspaceId, {
2685
+ collection: resolvedCollection,
2686
+ guided_field_count: guidedFieldCount
2687
+ });
2688
+ }
2633
2689
  if (formativeHints.length > 0) {
2634
2690
  lines.push("");
2635
2691
  lines.push("## Quality Hints (advisory)");
@@ -2852,6 +2908,12 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2852
2908
  classifiedBy = "explicit";
2853
2909
  } else {
2854
2910
  const resolved = classificationMap.get(entryIdx);
2911
+ if (resolved) {
2912
+ emitClassificationTelemetry(resolved, wsCtx.workspaceId, {
2913
+ explicitCollectionProvided: false,
2914
+ autoRouted: resolved.tier !== "low"
2915
+ });
2916
+ }
2855
2917
  if (!resolved || resolved.tier === "low") {
2856
2918
  skippedLowConfidence.push({
2857
2919
  index: entryIdx,
@@ -2933,7 +2995,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2933
2995
  }
2934
2996
  }
2935
2997
  const batchIsBet = resolvedSlug === "work-packages" || entry.canonicalKey === "work_package";
2936
- const shouldDecomposeBatch = (resolvedSlug === "strategy" || batchIsBet) && entry.description?.trim();
2998
+ const batchIsStrategy = col?.thinkingLayer === "strategy";
2999
+ const shouldDecomposeBatch = (batchIsStrategy || batchIsBet) && entry.description?.trim();
2937
3000
  let batchDecomposeWarning;
2938
3001
  if (shouldDecomposeBatch) {
2939
3002
  try {
@@ -2944,7 +3007,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2944
3007
  entryName: entry.name,
2945
3008
  description: entry.description,
2946
3009
  existingData: data,
2947
- ...batchIsBet ? { canonicalKey: "work_package" } : {}
3010
+ ...batchIsBet ? { canonicalKey: "work_package" } : {},
3011
+ ...col?.thinkingLayer ? { thinkingLayer: col.thinkingLayer } : {}
2948
3012
  }
2949
3013
  );
2950
3014
  if (decomposed?.decomposed) {
@@ -3855,6 +3919,20 @@ ${formatted}` }],
3855
3919
  }
3856
3920
  }
3857
3921
  }
3922
+ const guidanceWarnings = result?.guidanceWarnings;
3923
+ if (guidanceWarnings && guidanceWarnings.length > 0) {
3924
+ lines.push("");
3925
+ lines.push("## Field Guidance Warnings (advisory)");
3926
+ for (const w of guidanceWarnings) {
3927
+ lines.push(`- **${w.field}:** ${w.message}`);
3928
+ }
3929
+ lines.push(`_These are advisory \u2014 the entry was committed. Use \`update-entry entryId="${entryId}"\` to address them._`);
3930
+ const uniqueTypes = [...new Set(guidanceWarnings.map((w) => w.warningType))];
3931
+ trackFieldQualityWarning(wsCtx.workspaceId, {
3932
+ warning_count: guidanceWarnings.length,
3933
+ warning_types: uniqueTypes
3934
+ });
3935
+ }
3858
3936
  const epistemic = deriveEpistemicStatus(toEpistemicInput(entry));
3859
3937
  if (epistemic && (epistemic.level === "hypothesis" || epistemic.level === "untested")) {
3860
3938
  lines.push("");
@@ -5930,7 +6008,7 @@ function formatTimeAgo(ms) {
5930
6008
 
5931
6009
  // src/tools/collections.ts
5932
6010
  import { z as z8 } from "zod";
5933
- var COLLECTIONS_ACTIONS = ["list", "create", "update", "describe", "audit"];
6011
+ var COLLECTIONS_ACTIONS = ["list", "create", "update", "describe", "audit", "export"];
5934
6012
  var qualityCriterionSchema = z8.object({
5935
6013
  field: z8.string().describe("Entry data field key this criterion applies to, e.g. 'description', 'owner'"),
5936
6014
  rule: z8.enum(["required", "min_length", "pattern"]).describe("'required': field must be non-empty (blocks commit). 'min_length': minimum string length (warns). 'pattern': regex match (warns)."),
@@ -5956,7 +6034,7 @@ var fieldSchema = z8.object({
5956
6034
  });
5957
6035
  var collectionsSchema = z8.object({
5958
6036
  action: z8.enum(COLLECTIONS_ACTIONS).describe(
5959
- "'list': browse all collections. 'create': create a new collection. 'update': update an existing collection. 'describe': full documentation for one collection \u2014 fields, option guides, usage guidance, examples. 'audit': health report for all collections \u2014 missing classification, icon, displayHint coverage, and field schema gaps."
6037
+ "'list': browse all collections. 'create': create a new collection. 'update': update an existing collection. 'describe': full documentation for one collection \u2014 fields, option guides, usage guidance, examples. 'audit': health report for all collections \u2014 missing classification, icon, displayHint coverage, and field schema gaps. 'export': full system_collection_definitions export with classification metadata (thinkingLayer, classificationPriority, classificationCheck, classificationSignals, governanceRole, timelineRole, canBeElementOf, descriptionFieldKey). Admin only."
5960
6038
  ),
5961
6039
  slug: z8.string().optional().describe("URL-safe identifier for create/update, e.g. 'glossary', 'tech-debt'"),
5962
6040
  name: z8.string().optional().describe("Display name for create, or new name for update"),
@@ -5979,7 +6057,7 @@ function registerCollectionsTools(server) {
5979
6057
  "collections",
5980
6058
  {
5981
6059
  title: "Collections",
5982
- description: "Manage knowledge collections. Four actions:\n\n- **list**: Browse all collections \u2014 glossary, business rules, tracking events, etc. Returns slug, name, description, and field schema. Use before capture to see what exists.\n- **describe**: Full documentation for a single collection. Returns field help text, option decision guides, usage guidance, examples, and cross-references. Use when you need to understand a collection's purpose, field semantics, or option values.\n- **create**: Create a new collection. Provide slug, name, and field schema. Use when setting up a workspace or tracking a new type of knowledge.\n- **update**: Update an existing collection's name, description, purpose, icon, navGroup, or fields. Only provide the fields you want to change.\n- **audit**: Health report for all workspace collections. Checks classification metadata, icon presence, displayHint coverage per field, and field schema gaps vs system definitions. Returns total collections, count with issues, and per-collection issue list.",
6060
+ description: "Manage knowledge collections. Four actions:\n\n- **list**: Browse all collections \u2014 glossary, business rules, tracking events, etc. Returns slug, name, description, and field schema. Use before capture to see what exists.\n- **describe**: Full documentation for a single collection. Returns field help text, option decision guides, usage guidance, examples, and cross-references. Use when you need to understand a collection's purpose, field semantics, or option values.\n- **create**: Create a new collection. Provide slug, name, and field schema. Use when setting up a workspace or tracking a new type of knowledge.\n- **update**: Update an existing collection's name, description, purpose, icon, navGroup, or fields. Only provide the fields you want to change.\n- **audit**: Health report for all workspace collections. Checks classification metadata, icon presence, displayHint coverage per field, and field schema gaps vs system definitions. Returns total collections, count with issues, and per-collection issue list.\n- **export**: Full system_collection_definitions export as JSON. Includes all classification metadata (thinkingLayer, classificationPriority, classificationCheck, timelineRole, governanceRole, etc.). Admin only.",
5983
6061
  inputSchema: collectionsSchema,
5984
6062
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
5985
6063
  },
@@ -6016,6 +6094,9 @@ function registerCollectionsTools(server) {
6016
6094
  if (action === "audit") {
6017
6095
  return handleAudit();
6018
6096
  }
6097
+ if (action === "export") {
6098
+ return handleExport();
6099
+ }
6019
6100
  return unknownAction(action, COLLECTIONS_ACTIONS);
6020
6101
  });
6021
6102
  })
@@ -6282,6 +6363,16 @@ All collections are healthy.`);
6282
6363
  )
6283
6364
  };
6284
6365
  }
6366
+ async function handleExport() {
6367
+ const definitions = await mcpQuery("chain.exportDefinitions");
6368
+ return {
6369
+ content: [{ type: "text", text: JSON.stringify(definitions, null, 2) }],
6370
+ structuredContent: success(
6371
+ `Exported ${definitions.length} system collection definitions with full classification metadata.`,
6372
+ { definitions, total: definitions.length }
6373
+ )
6374
+ };
6375
+ }
6285
6376
 
6286
6377
  // src/tools/labels.ts
6287
6378
  import { z as z9 } from "zod";
@@ -15214,6 +15305,7 @@ function createProductBrainServer() {
15214
15305
  }
15215
15306
 
15216
15307
  export {
15308
+ hashKey,
15217
15309
  runWithAuth,
15218
15310
  getAgentSessionId,
15219
15311
  startAgentSession,
@@ -15226,4 +15318,4 @@ export {
15226
15318
  SERVER_VERSION,
15227
15319
  createProductBrainServer
15228
15320
  };
15229
- //# sourceMappingURL=chunk-H7XBZ4H3.js.map
15321
+ //# sourceMappingURL=chunk-ML7BPLBX.js.map