@productbrain/mcp 0.0.1-beta.30 → 0.0.1-beta.32

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.
@@ -1,5 +1,4 @@
1
1
  import {
2
- trackQualityCheck,
3
2
  trackQualityVerdict,
4
3
  trackToolCall
5
4
  } from "./chunk-SJ2ODB3Y.js";
@@ -7,6 +6,9 @@ import {
7
6
  // src/tools/smart-capture.ts
8
7
  import { z } from "zod";
9
8
 
9
+ // src/client.ts
10
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
11
+
10
12
  // src/auth.ts
11
13
  import { AsyncLocalStorage } from "async_hooks";
12
14
  var requestStore = new AsyncLocalStorage();
@@ -58,6 +60,13 @@ function evictStale() {
58
60
  }
59
61
 
60
62
  // src/client.ts
63
+ var toolContextStore = new AsyncLocalStorage2();
64
+ function runWithToolContext(ctx, fn) {
65
+ return toolContextStore.run(ctx, fn);
66
+ }
67
+ function getToolContext() {
68
+ return toolContextStore.getStore() ?? null;
69
+ }
61
70
  var DEFAULT_CLOUD_URL = "https://trustworthy-kangaroo-277.convex.site";
62
71
  var CACHE_TTL_MS = 6e4;
63
72
  var CACHEABLE_FNS = [
@@ -130,6 +139,9 @@ function isSessionOriented() {
130
139
  function setSessionOriented(value) {
131
140
  state().sessionOriented = value;
132
141
  }
142
+ function getApiKeyScope() {
143
+ return state().apiKeyScope;
144
+ }
133
145
  async function startAgentSession() {
134
146
  const workspaceId = await getWorkspaceId();
135
147
  const s = state();
@@ -144,6 +156,7 @@ async function startAgentSession() {
144
156
  s.apiKeyScope = result.toolsScope;
145
157
  s.sessionOriented = false;
146
158
  s.sessionClosed = false;
159
+ resetTouchThrottle();
147
160
  return result;
148
161
  }
149
162
  async function closeAgentSession() {
@@ -174,14 +187,22 @@ async function orphanAgentSession() {
174
187
  s.sessionOriented = false;
175
188
  }
176
189
  }
190
+ var _lastTouchAt = 0;
191
+ var TOUCH_THROTTLE_MS = 5e3;
177
192
  function touchSessionActivity() {
178
193
  const s = state();
179
194
  if (!s.agentSessionId) return;
195
+ const now = Date.now();
196
+ if (now - _lastTouchAt < TOUCH_THROTTLE_MS) return;
197
+ _lastTouchAt = now;
180
198
  mcpCall("agent.touchSession", {
181
199
  sessionId: s.agentSessionId
182
200
  }).catch(() => {
183
201
  });
184
202
  }
203
+ function resetTouchThrottle() {
204
+ _lastTouchAt = 0;
205
+ }
185
206
  async function recordSessionActivity(activity) {
186
207
  const s = state();
187
208
  if (!s.agentSessionId) return;
@@ -218,8 +239,10 @@ function shouldLogAudit(status) {
218
239
  function audit(fn, status, durationMs, errorMsg) {
219
240
  const ts = (/* @__PURE__ */ new Date()).toISOString();
220
241
  const workspace = state().workspaceId ?? "unresolved";
242
+ const toolCtx = getToolContext();
221
243
  const entry = { ts, fn, workspace, status, durationMs };
222
244
  if (errorMsg) entry.error = errorMsg;
245
+ if (toolCtx) entry.toolContext = toolCtx;
223
246
  auditBuffer.push(entry);
224
247
  if (auditBuffer.length > AUDIT_BUFFER_SIZE) auditBuffer.shift();
225
248
  trackToolCall(fn, status, durationMs, workspace, errorMsg);
@@ -341,12 +364,12 @@ function requireActiveSession() {
341
364
  const s = state();
342
365
  if (!s.agentSessionId) {
343
366
  throw new Error(
344
- "Active session required (SOS-iszqu7). Call `agent-start` then `orient` first."
367
+ "Active session required (SOS-iszqu7). Call `session action=start` then `orient` first."
345
368
  );
346
369
  }
347
370
  if (s.sessionClosed) {
348
371
  throw new Error(
349
- "Session has been closed (SOS-iszqu7). Start a new session with `agent-start`."
372
+ "Session has been closed (SOS-iszqu7). Start a new session with `session action=start`."
350
373
  );
351
374
  }
352
375
  if (!s.sessionOriented) {
@@ -359,7 +382,7 @@ function requireWriteAccess() {
359
382
  const s = state();
360
383
  if (!s.agentSessionId) {
361
384
  throw new Error(
362
- "Agent session required for write operations. Call `agent-start` first."
385
+ "Agent session required for write operations. Call `session action=start` first."
363
386
  );
364
387
  }
365
388
  if (s.sessionClosed) {
@@ -393,6 +416,62 @@ async function recoverSessionState() {
393
416
  }
394
417
  }
395
418
 
419
+ // src/tools/knowledge-helpers.ts
420
+ function extractPreview(data, maxLen) {
421
+ if (!data || typeof data !== "object") return "";
422
+ const d = data;
423
+ const raw = d.description ?? d.canonical ?? d.detail ?? d.rule ?? "";
424
+ if (typeof raw !== "string" || !raw) return "";
425
+ return raw.length > maxLen ? raw.substring(0, maxLen) + "..." : raw;
426
+ }
427
+ var TOOL_NAME_MIGRATIONS = /* @__PURE__ */ new Map([
428
+ ["list-entries", 'entries action="list"'],
429
+ ["get-entry", 'entries action="get"'],
430
+ ["batch-get", 'entries action="batch"'],
431
+ ["search", 'entries action="search"'],
432
+ ["relate-entries", 'relations action="create"'],
433
+ ["batch-relate", 'relations action="batch-create"'],
434
+ ["find-related", 'graph action="find"'],
435
+ ["suggest-links", 'graph action="suggest"'],
436
+ ["gather-context", 'context action="gather"'],
437
+ ["get-build-context", 'context action="build"'],
438
+ ["list-collections", 'collections action="list"'],
439
+ ["create-collection", 'collections action="create"'],
440
+ ["update-collection", 'collections action="update"'],
441
+ ["agent-start", 'session action="start"'],
442
+ ["agent-close", 'session action="close"'],
443
+ ["agent-status", 'session action="status"'],
444
+ ["workspace-status", 'health action="status"'],
445
+ ["mcp-audit", 'health action="audit"'],
446
+ ["quality-check", 'quality action="check"'],
447
+ ["re-evaluate", 'quality action="re-evaluate"'],
448
+ ["list-workflows", 'workflows action="list"'],
449
+ ["workflow-checkpoint", 'workflows action="checkpoint"'],
450
+ ["wrapup", "session-wrapup"],
451
+ ["finish", "session-wrapup"]
452
+ ]);
453
+ function translateStaleToolNames(text) {
454
+ const found = [];
455
+ for (const [old, current] of TOOL_NAME_MIGRATIONS) {
456
+ const pattern = new RegExp(`\\b${old.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "g");
457
+ if (pattern.test(text)) {
458
+ found.push(`\`${old}\` \u2192 \`${current}\``);
459
+ }
460
+ }
461
+ if (found.length === 0) return null;
462
+ return `
463
+
464
+ ---
465
+ _Tool name translations (these references use deprecated names):_
466
+ ${found.map((f) => `- ${f}`).join("\n")}`;
467
+ }
468
+
469
+ // src/tool-surface.ts
470
+ function initToolSurface(_server) {
471
+ }
472
+ function trackWriteTool(_tool) {
473
+ }
474
+
396
475
  // src/tools/smart-capture.ts
397
476
  var AREA_KEYWORDS = {
398
477
  "Architecture": ["convex", "schema", "database", "migration", "api", "backend", "infrastructure", "scaling", "performance"],
@@ -436,7 +515,7 @@ var COMMON_CHECKS = {
436
515
  id: "has-relations",
437
516
  label: "At least 1 relation created",
438
517
  check: (ctx) => ctx.linksCreated.length >= 1,
439
- suggestion: () => "Use `suggest-links` and `relate-entries` to add more connections."
518
+ suggestion: () => "Use `graph action=suggest` and `relations action=create` to add more connections."
440
519
  },
441
520
  diverseRelations: {
442
521
  id: "diverse-relations",
@@ -677,6 +756,19 @@ var PROFILES = /* @__PURE__ */ new Map([
677
756
  COMMON_CHECKS.diverseRelations
678
757
  ]
679
758
  }],
759
+ ["bets", {
760
+ // BET-chain-native-constellation, DEC-70 (structuredContent), STA-1 (constellation pattern)
761
+ governedDraft: false,
762
+ descriptionField: "problem",
763
+ defaults: [],
764
+ recommendedRelationTypes: ["part_of", "constrains", "informs", "depends_on", "related_to"],
765
+ qualityChecks: [
766
+ COMMON_CHECKS.clearName,
767
+ COMMON_CHECKS.hasDescription,
768
+ COMMON_CHECKS.hasRelations,
769
+ COMMON_CHECKS.hasType
770
+ ]
771
+ }],
680
772
  ["maps", {
681
773
  governedDraft: false,
682
774
  descriptionField: "description",
@@ -697,6 +789,44 @@ var PROFILES = /* @__PURE__ */ new Map([
697
789
  COMMON_CHECKS.hasDescription
698
790
  ]
699
791
  }],
792
+ ["principles", {
793
+ governedDraft: true,
794
+ descriptionField: "description",
795
+ defaults: [
796
+ { key: "severity", value: "high" },
797
+ { key: "category", value: "infer" }
798
+ ],
799
+ recommendedRelationTypes: ["governs", "informs", "references", "related_to"],
800
+ inferField: (ctx) => {
801
+ const fields = {};
802
+ const area = inferArea(`${ctx.name} ${ctx.description}`);
803
+ if (area) {
804
+ const categoryMap = {
805
+ "Architecture": "Engineering",
806
+ "Chain": "Product",
807
+ "AI & MCP Integration": "Engineering",
808
+ "Developer Experience": "Engineering",
809
+ "Governance & Decision-Making": "Business",
810
+ "Analytics & Tracking": "Product",
811
+ "Security": "Engineering"
812
+ };
813
+ fields.category = categoryMap[area] ?? "Product";
814
+ }
815
+ return fields;
816
+ },
817
+ qualityChecks: [
818
+ COMMON_CHECKS.clearName,
819
+ COMMON_CHECKS.hasDescription,
820
+ COMMON_CHECKS.hasRelations,
821
+ COMMON_CHECKS.hasType,
822
+ {
823
+ id: "has-rationale",
824
+ label: "Rationale provided \u2014 why this principle matters",
825
+ check: (ctx) => typeof ctx.data.rationale === "string" && ctx.data.rationale.length > 20,
826
+ suggestion: () => "Explain why this principle exists and what goes wrong without it."
827
+ }
828
+ ]
829
+ }],
700
830
  ["standards", {
701
831
  governedDraft: true,
702
832
  descriptionField: "description",
@@ -871,7 +1001,7 @@ async function checkEntryQuality(entryId) {
871
1001
  const failedChecks = quality.checks.filter((c) => !c.passed && c.suggestion);
872
1002
  if (failedChecks.length > 0) {
873
1003
  lines.push("");
874
- lines.push(`_To improve: use \`update-entry\` to fill missing fields, or \`relate-entries\` to add connections._`);
1004
+ lines.push(`_To improve: use \`update-entry\` to fill missing fields, or \`relations action=create\` to add connections._`);
875
1005
  }
876
1006
  }
877
1007
  return { text: lines.join("\n"), quality };
@@ -887,22 +1017,48 @@ var GOVERNED_COLLECTIONS = /* @__PURE__ */ new Set([
887
1017
  var AUTO_LINK_CONFIDENCE_THRESHOLD = 35;
888
1018
  var MAX_AUTO_LINKS = 5;
889
1019
  var MAX_SUGGESTIONS = 5;
1020
+ var captureSchema = z.object({
1021
+ collection: z.string().describe("Collection slug, e.g. 'tensions', 'business-rules', 'glossary', 'decisions'"),
1022
+ name: z.string().describe("Display name \u2014 be specific (e.g. 'Convex adjacency list won't scale for graph traversal')"),
1023
+ description: z.string().describe("Full context \u2014 what's happening, why it matters, what you observed"),
1024
+ context: z.string().optional().describe("Optional additional context (e.g. 'Observed during context gather calls taking 700ms+')"),
1025
+ entryId: z.string().optional().describe("Optional custom entry ID (e.g. 'TEN-my-id'). Auto-generated if omitted."),
1026
+ canonicalKey: z.string().optional().describe("Semantic type (e.g. 'decision', 'tension', 'vision'). Auto-assigned from collection if omitted."),
1027
+ data: z.record(z.unknown()).optional().describe("Explicit field values when you know the schema (e.g. canonical_key, cardinality_rule, required_fields). Merged with inferred values; user-provided wins.")
1028
+ });
1029
+ var batchCaptureSchema = z.object({
1030
+ entries: z.array(z.object({
1031
+ collection: z.string().describe("Collection slug"),
1032
+ name: z.string().describe("Display name"),
1033
+ description: z.string().describe("Full context / definition"),
1034
+ entryId: z.string().optional().describe("Optional custom entry ID")
1035
+ })).min(1).max(50).describe("Array of entries to capture")
1036
+ });
1037
+ var captureOutputSchema = z.object({
1038
+ entryId: z.string(),
1039
+ collection: z.string(),
1040
+ name: z.string(),
1041
+ status: z.literal("draft"),
1042
+ qualityScore: z.number().optional(),
1043
+ qualityVerdict: z.record(z.unknown()).optional()
1044
+ });
1045
+ var batchCaptureOutputSchema = z.object({
1046
+ captured: z.array(z.object({
1047
+ entryId: z.string(),
1048
+ collection: z.string(),
1049
+ name: z.string()
1050
+ })),
1051
+ total: z.number(),
1052
+ failed: z.number()
1053
+ });
890
1054
  function registerSmartCaptureTools(server) {
891
- server.registerTool(
1055
+ const captureTool = server.registerTool(
892
1056
  "capture",
893
1057
  {
894
1058
  title: "Capture",
895
1059
  description: "The single tool for creating knowledge entries. Creates an entry, auto-links related entries, and returns a quality scorecard \u2014 all in one call. Provide a collection, name, and description \u2014 everything else is inferred or auto-filled.\n\nSupported collections with smart profiles: tensions, business-rules, glossary, decisions, features, audiences, strategy, standards, maps, chains, tracking-events.\nAll other collections get an ENT-{random} ID and sensible defaults.\n\n**Explicit data:** When you know the schema, pass `data: { field: value }` to set fields directly. Top-level `name` and `description` always win for those fields. `data` wins over inference for all other fields.\n\nAlways creates as 'draft' for governed collections. Use `update-entry` for post-creation adjustments.",
896
- inputSchema: {
897
- collection: z.string().describe("Collection slug, e.g. 'tensions', 'business-rules', 'glossary', 'decisions'"),
898
- name: z.string().describe("Display name \u2014 be specific (e.g. 'Convex adjacency list won't scale for graph traversal')"),
899
- description: z.string().describe("Full context \u2014 what's happening, why it matters, what you observed"),
900
- context: z.string().optional().describe("Optional additional context (e.g. 'Observed during gather-context calls taking 700ms+')"),
901
- entryId: z.string().optional().describe("Optional custom entry ID (e.g. 'TEN-my-id'). Auto-generated if omitted."),
902
- canonicalKey: z.string().optional().describe("Semantic type (e.g. 'decision', 'tension', 'vision'). Auto-assigned from collection if omitted."),
903
- data: z.record(z.unknown()).optional().describe("Explicit field values when you know the schema (e.g. canonical_key, cardinality_rule, required_fields). Merged with inferred values; user-provided wins.")
904
- },
905
- annotations: { destructiveHint: false }
1060
+ inputSchema: captureSchema.shape,
1061
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
906
1062
  },
907
1063
  async ({ collection, name, description, context, entryId, canonicalKey, data: userData }) => {
908
1064
  requireWriteAccess();
@@ -917,10 +1073,10 @@ function registerSmartCaptureTools(server) {
917
1073
 
918
1074
  **To create it**, run:
919
1075
  \`\`\`
920
- create-collection slug="${collection}" name="${displayName}" description="..."
1076
+ collections action=create slug="${collection}" name="${displayName}" description="..."
921
1077
  \`\`\`
922
1078
 
923
- Or use \`list-collections\` to see available collections.`
1079
+ Or use \`collections action=list\` to see available collections.`
924
1080
  }]
925
1081
  };
926
1082
  }
@@ -996,7 +1152,7 @@ Or use \`list-collections\` to see available collections.`
996
1152
 
997
1153
  ${msg}
998
1154
 
999
- Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify it.`
1155
+ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to modify it.`
1000
1156
  }]
1001
1157
  };
1002
1158
  }
@@ -1125,6 +1281,12 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1125
1281
  `**${name}** added to \`${collection}\` as \`${status}\``,
1126
1282
  `**Workspace:** ${wsCtx.workspaceSlug} (${wsCtx.workspaceId})`
1127
1283
  ];
1284
+ const appUrl = process.env.PRODUCTBRAIN_APP_URL ?? "https://productbrain.io";
1285
+ const studioUrl = collection === "bets" ? `${appUrl.replace(/\/$/, "")}/w/${wsCtx.workspaceSlug}/studio/${internalId}` : void 0;
1286
+ if (studioUrl) {
1287
+ lines.push("");
1288
+ lines.push(`**View in Studio:** ${studioUrl}`);
1289
+ }
1128
1290
  if (linksCreated.length > 0) {
1129
1291
  lines.push("");
1130
1292
  lines.push(`## Auto-linked (${linksCreated.length})`);
@@ -1135,7 +1297,7 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1135
1297
  }
1136
1298
  if (linksSuggested.length > 0) {
1137
1299
  lines.push("");
1138
- lines.push("## Suggested links (review and use relate-entries)");
1300
+ lines.push("## Suggested links (review and use `relations action=create`)");
1139
1301
  for (let i = 0; i < linksSuggested.length; i++) {
1140
1302
  const s = linksSuggested[i];
1141
1303
  const preview = s.preview ? ` \u2014 ${s.preview}` : "";
@@ -1154,7 +1316,7 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1154
1316
  if (isBetOrGoal && !hasStrategyLink) {
1155
1317
  lines.push("");
1156
1318
  lines.push(
1157
- `**Strategy link:** This ${collection === "bets" ? "bet" : "goal"} doesn't connect to any strategy entry. Consider linking before commit. Use \`suggest-links entryId="${finalEntryId}"\` to find strategy entries to connect to.`
1319
+ `**Strategy link:** This ${collection === "bets" ? "bet" : "goal"} doesn't connect to any strategy entry. Consider linking before commit. Use \`graph action=suggest entryId="${finalEntryId}"\` to find strategy entries to connect to.`
1158
1320
  );
1159
1321
  await recordSessionActivity({ strategyLinkWarnedForEntryId: internalId });
1160
1322
  }
@@ -1168,7 +1330,7 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1168
1330
  for (const w of contradictionWarnings) {
1169
1331
  lines.push(`- ${w.name} (${w.collection}, ${w.entryId}) \u2014 has 'governs' relation to ${w.governsCount} entries`);
1170
1332
  }
1171
- lines.push("Run gather-context on these entries before committing.");
1333
+ lines.push("Run `context action=gather` on these entries before committing.");
1172
1334
  }
1173
1335
  if (coachingSection) {
1174
1336
  lines.push("");
@@ -1177,7 +1339,7 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1177
1339
  lines.push("");
1178
1340
  lines.push("## Next Steps");
1179
1341
  const eid = finalEntryId || "(check entry ID)";
1180
- lines.push(`1. **Connect it:** \`suggest-links entryId="${eid}"\` \u2014 discover what this should link to`);
1342
+ lines.push(`1. **Connect it:** \`graph action=suggest entryId="${eid}"\` \u2014 discover what this should link to`);
1181
1343
  lines.push(`2. **Commit it:** \`commit-entry entryId="${eid}"\` \u2014 promote from draft to SSOT on the Chain`);
1182
1344
  if (failedChecks.length > 0) {
1183
1345
  lines.push(`3. **Improve quality:** \`update-entry entryId="${eid}"\` \u2014 fill missing fields`);
@@ -1196,37 +1358,52 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1196
1358
  }
1197
1359
  const toolResult = {
1198
1360
  content: [{ type: "text", text: lines.join("\n") }],
1199
- structuredContent: verdictResult?.verdict ? { qualityVerdict: verdictResult.verdict, source: verdictResult.source ?? "heuristic" } : void 0
1361
+ structuredContent: {
1362
+ entryId: finalEntryId,
1363
+ collection,
1364
+ name,
1365
+ status: "draft",
1366
+ qualityScore: quality.score,
1367
+ qualityVerdict: verdictResult?.verdict ? { ...verdictResult.verdict, source: verdictResult.source ?? "heuristic" } : void 0,
1368
+ ...studioUrl && { studioUrl }
1369
+ }
1200
1370
  };
1201
1371
  return toolResult;
1202
1372
  }
1203
1373
  );
1204
- server.registerTool(
1374
+ trackWriteTool(captureTool);
1375
+ const batchCaptureTool = server.registerTool(
1205
1376
  "batch-capture",
1206
1377
  {
1207
1378
  title: "Batch Capture",
1208
- description: "Create multiple knowledge entries in one call. Ideal for workspace setup, document ingestion, or any scenario where you need to capture many entries at once.\n\nEach entry is created independently \u2014 if one fails, the others still succeed. Returns a compact summary instead of per-entry quality scorecards.\n\nAuto-linking runs per entry but contradiction checks and readiness hints are skipped for speed. Use `quality-check` on individual entries afterward if needed.",
1209
- inputSchema: {
1210
- entries: z.array(z.object({
1211
- collection: z.string().describe("Collection slug"),
1212
- name: z.string().describe("Display name"),
1213
- description: z.string().describe("Full context / definition"),
1214
- entryId: z.string().optional().describe("Optional custom entry ID")
1215
- })).min(1).max(50).describe("Array of entries to capture")
1216
- },
1217
- annotations: { destructiveHint: false }
1379
+ description: "Create multiple knowledge entries in one call. Ideal for workspace setup, document ingestion, or any scenario where you need to capture many entries at once.\n\nEach entry is created independently \u2014 if one fails, the others still succeed. Returns a compact summary instead of per-entry quality scorecards.\n\nAuto-linking runs per entry but contradiction checks and readiness hints are skipped for speed. Use `quality action=check` on individual entries afterward if needed.",
1380
+ inputSchema: batchCaptureSchema.shape,
1381
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
1218
1382
  },
1219
1383
  async ({ entries }) => {
1220
1384
  requireWriteAccess();
1221
1385
  const agentId = getAgentSessionId();
1222
1386
  const createdBy = agentId ? `agent:${agentId}` : "capture";
1223
1387
  const results = [];
1388
+ await server.sendLoggingMessage({
1389
+ level: "info",
1390
+ data: `Batch capturing ${entries.length} entries...`,
1391
+ logger: "product-brain"
1392
+ });
1224
1393
  const allCollections = await mcpQuery("chain.listCollections");
1225
1394
  const collCache = /* @__PURE__ */ new Map();
1226
1395
  for (const c of allCollections) collCache.set(c.slug, c);
1227
1396
  const collIdToSlug = /* @__PURE__ */ new Map();
1228
1397
  for (const c of allCollections) collIdToSlug.set(c._id, c.slug);
1229
- for (const entry of entries) {
1398
+ for (let entryIdx = 0; entryIdx < entries.length; entryIdx++) {
1399
+ const entry = entries[entryIdx];
1400
+ if (entryIdx > 0 && entryIdx % 5 === 0) {
1401
+ await server.sendLoggingMessage({
1402
+ level: "info",
1403
+ data: `Captured ${entryIdx}/${entries.length} entries...`,
1404
+ logger: "product-brain"
1405
+ });
1406
+ }
1230
1407
  const profile = PROFILES.get(entry.collection) ?? FALLBACK_PROFILE;
1231
1408
  const col = collCache.get(entry.collection);
1232
1409
  if (!col) {
@@ -1320,6 +1497,11 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1320
1497
  }
1321
1498
  const created = results.filter((r) => r.ok);
1322
1499
  const failed = results.filter((r) => !r.ok);
1500
+ await server.sendLoggingMessage({
1501
+ level: "info",
1502
+ data: `Batch complete. ${created.length} succeeded, ${failed.length} failed.`,
1503
+ logger: "product-brain"
1504
+ });
1323
1505
  const totalAutoLinks = created.reduce((sum, r) => sum + r.autoLinks, 0);
1324
1506
  const byCollection = /* @__PURE__ */ new Map();
1325
1507
  for (const r of created) {
@@ -1356,120 +1538,21 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1356
1538
  if (entryIds.length > 0) {
1357
1539
  lines.push("");
1358
1540
  lines.push("## Next Steps");
1359
- lines.push(`- **Connect:** Run \`suggest-links\` on key entries to build the knowledge graph`);
1541
+ lines.push(`- **Connect:** Run \`graph action=suggest\` on key entries to build the knowledge graph`);
1360
1542
  lines.push(`- **Commit:** Use \`commit-entry\` to promote drafts to SSOT`);
1361
- lines.push(`- **Quality:** Run \`quality-check\` on individual entries to assess completeness`);
1362
- }
1363
- return { content: [{ type: "text", text: lines.join("\n") }] };
1364
- }
1365
- );
1366
- server.registerTool(
1367
- "quality-check",
1368
- {
1369
- title: "Quality Check",
1370
- description: "Score an existing knowledge entry against collection-specific quality criteria. Returns a scorecard (X/10) with specific, actionable suggestions for improvement \u2014 including concrete link suggestions from graph analysis when relations are missing.\n\nChecks: name clarity, description completeness, relation connectedness, and collection-specific fields.\n\nUse after creating entries to assess their quality, or to audit existing entries.",
1371
- inputSchema: {
1372
- entryId: z.string().describe("Entry ID to check, e.g. 'TEN-graph-db', 'GT-019', 'SOS-006'")
1373
- },
1374
- annotations: { readOnlyHint: true }
1375
- },
1376
- async ({ entryId }) => {
1377
- const result = await checkEntryQuality(entryId);
1378
- const needsRelations = result.quality.checks.some(
1379
- (c) => !c.passed && (c.id === "has-relations" || c.id === "diverse-relations")
1380
- );
1381
- if (needsRelations) {
1382
- try {
1383
- const suggestions = await mcpQuery("chain.graphSuggestLinks", {
1384
- entryId,
1385
- maxHops: 2,
1386
- limit: 3
1387
- });
1388
- if (suggestions?.suggestions?.length > 0) {
1389
- const linkHints = suggestions.suggestions.map((s) => ` \u2192 \`relate-entries from='${entryId}' to='${s.entryId}' type='${s.recommendedRelationType}'\` \u2014 ${s.name} [${s.collectionSlug}] (${s.score}/100)`).join("\n");
1390
- result.text += `
1391
-
1392
- ## Suggested Links to Improve Quality
1393
- ${linkHints}`;
1394
- }
1395
- } catch {
1396
- }
1543
+ lines.push(`- **Quality:** Run \`quality action=check\` on individual entries to assess completeness`);
1397
1544
  }
1398
- try {
1399
- const verdict = await mcpQuery("quality.getLatestVerdictForEntry", { entryId });
1400
- if (verdict && verdict.criteria?.length > 0) {
1401
- result.text += "\n\n" + formatRubricVerdictSection(verdict);
1402
- try {
1403
- const wsForTracking = await getWorkspaceContext();
1404
- trackQualityCheck(wsForTracking.workspaceId, {
1405
- entry_id: entryId,
1406
- entry_type: verdict.canonicalKey ?? "",
1407
- tier: verdict.tier,
1408
- passed: verdict.passed,
1409
- source: verdict.source,
1410
- llm_status: verdict.llmStatus,
1411
- llm_duration_ms: verdict.llmDurationMs,
1412
- llm_error: verdict.llmError,
1413
- has_roger_martin: !!verdict.rogerMartin
1414
- });
1415
- } catch {
1416
- }
1545
+ return {
1546
+ content: [{ type: "text", text: lines.join("\n") }],
1547
+ structuredContent: {
1548
+ captured: created.map((r) => ({ entryId: r.entryId, collection: r.collection, name: r.name })),
1549
+ total: created.length,
1550
+ failed: failed.length
1417
1551
  }
1418
- } catch {
1419
- }
1420
- return { content: [{ type: "text", text: result.text }] };
1421
- }
1422
- );
1423
- server.registerTool(
1424
- "re-evaluate",
1425
- {
1426
- title: "Re-evaluate Entry Quality",
1427
- description: "Trigger a fresh quality evaluation on an existing entry without creating a new one. Useful for canary testing or re-assessing after edits. Returns the heuristic verdict immediately and schedules LLM evaluation in the background.",
1428
- inputSchema: {
1429
- entryId: z.string().describe("Entry ID to re-evaluate, e.g. 'VIS-001', 'STR-012'"),
1430
- context: z.enum(["capture", "commit", "review"]).default("review").describe("Evaluation context")
1431
- }
1432
- },
1433
- async ({ entryId, context }) => {
1434
- const ws = await getWorkspaceContext();
1435
- const result = await mcpMutation("quality.reEvaluateEntry", {
1436
- entryId,
1437
- context: context ?? "review"
1438
- });
1439
- if (!result) {
1440
- return { content: [{ type: "text", text: `Entry \`${entryId}\` not found or has no rubric.` }] };
1441
- }
1442
- const llmScheduled = result.verdict.tier !== "passive";
1443
- const lines = [`Re-evaluated \`${entryId}\` in \`${context ?? "review"}\` context.`];
1444
- lines.push("");
1445
- lines.push(formatRubricVerdictSection({
1446
- ...result.verdict,
1447
- source: result.source,
1448
- llmStatus: llmScheduled ? "pending" : "skipped"
1449
- }));
1450
- try {
1451
- trackQualityVerdict(ws.workspaceId, {
1452
- entry_id: entryId,
1453
- entry_type: result.verdict.canonicalKey ?? "",
1454
- tier: result.verdict.tier,
1455
- context: context ?? "review",
1456
- passed: result.verdict.passed,
1457
- source: "heuristic",
1458
- criteria_total: result.verdict.criteria?.length ?? 0,
1459
- criteria_failed: result.verdict.criteria?.filter((c) => !c.passed).length ?? 0,
1460
- llm_scheduled: llmScheduled
1461
- });
1462
- } catch {
1463
- }
1464
- return { content: [{ type: "text", text: lines.join("\n") }] };
1552
+ };
1465
1553
  }
1466
1554
  );
1467
- }
1468
- function extractPreview(data, maxLen) {
1469
- if (!data || typeof data !== "object") return "";
1470
- const raw = data.description ?? data.canonical ?? data.detail ?? data.rule ?? "";
1471
- if (typeof raw !== "string" || !raw) return "";
1472
- return raw.length > maxLen ? raw.substring(0, maxLen) + "..." : raw;
1555
+ trackWriteTool(batchCaptureTool);
1473
1556
  }
1474
1557
  var STOP_WORDS = /* @__PURE__ */ new Set([
1475
1558
  "the",
@@ -1560,7 +1643,7 @@ async function runContradictionCheck(name, description) {
1560
1643
  const text = `${name} ${description}`.toLowerCase();
1561
1644
  const keyTerms = text.split(/\s+/).filter((w) => w.length >= 4 && !STOP_WORDS.has(w)).slice(0, 8);
1562
1645
  if (keyTerms.length === 0) return warnings;
1563
- const searchQuery = keyTerms.slice(0, 3).join(" ");
1646
+ const searchQuery = keyTerms.slice(0, 5).join(" ");
1564
1647
  const [govResults, archResults] = await Promise.all([
1565
1648
  mcpQuery("chain.searchEntries", { query: searchQuery, collectionSlug: "business-rules" }),
1566
1649
  mcpQuery("chain.searchEntries", { query: searchQuery, collectionSlug: "architecture" })
@@ -1569,7 +1652,7 @@ async function runContradictionCheck(name, description) {
1569
1652
  for (const entry of allGov) {
1570
1653
  const entryText = `${entry.name} ${entry.data?.description ?? ""}`.toLowerCase();
1571
1654
  const matched = keyTerms.filter((t) => entryText.includes(t));
1572
- if (matched.length < 2) continue;
1655
+ if (matched.length < 3) continue;
1573
1656
  let governsCount = 0;
1574
1657
  try {
1575
1658
  const relations = await mcpQuery("chain.listEntryRelations", {
@@ -1667,9 +1750,11 @@ function formatRubricVerdictSection(verdict) {
1667
1750
 
1668
1751
  export {
1669
1752
  runWithAuth,
1753
+ runWithToolContext,
1670
1754
  getAgentSessionId,
1671
1755
  isSessionOriented,
1672
1756
  setSessionOriented,
1757
+ getApiKeyScope,
1673
1758
  startAgentSession,
1674
1759
  closeAgentSession,
1675
1760
  orphanAgentSession,
@@ -1685,10 +1770,19 @@ export {
1685
1770
  requireActiveSession,
1686
1771
  requireWriteAccess,
1687
1772
  recoverSessionState,
1773
+ extractPreview,
1774
+ translateStaleToolNames,
1775
+ initToolSurface,
1776
+ trackWriteTool,
1688
1777
  formatQualityReport,
1689
1778
  checkEntryQuality,
1779
+ captureSchema,
1780
+ batchCaptureSchema,
1781
+ captureOutputSchema,
1782
+ batchCaptureOutputSchema,
1690
1783
  registerSmartCaptureTools,
1691
1784
  runContradictionCheck,
1692
- formatRubricCoaching
1785
+ formatRubricCoaching,
1786
+ formatRubricVerdictSection
1693
1787
  };
1694
- //# sourceMappingURL=chunk-P5KVCIYN.js.map
1788
+ //# sourceMappingURL=chunk-UOT3O5FN.js.map