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

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,78 @@ 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
+ var _server = null;
471
+ var _writeTools = [];
472
+ function initToolSurface(server) {
473
+ _server = server;
474
+ }
475
+ function trackWriteTool(tool) {
476
+ _writeTools.push(tool);
477
+ }
478
+ function setWriteToolsEnabled(enabled) {
479
+ let changed = false;
480
+ for (const tool of _writeTools) {
481
+ if (tool.enabled !== enabled) {
482
+ tool.enabled = enabled;
483
+ changed = true;
484
+ }
485
+ }
486
+ if (changed && _server) {
487
+ _server.sendToolListChanged();
488
+ }
489
+ }
490
+
396
491
  // src/tools/smart-capture.ts
397
492
  var AREA_KEYWORDS = {
398
493
  "Architecture": ["convex", "schema", "database", "migration", "api", "backend", "infrastructure", "scaling", "performance"],
@@ -436,7 +531,7 @@ var COMMON_CHECKS = {
436
531
  id: "has-relations",
437
532
  label: "At least 1 relation created",
438
533
  check: (ctx) => ctx.linksCreated.length >= 1,
439
- suggestion: () => "Use `suggest-links` and `relate-entries` to add more connections."
534
+ suggestion: () => "Use `graph action=suggest` and `relations action=create` to add more connections."
440
535
  },
441
536
  diverseRelations: {
442
537
  id: "diverse-relations",
@@ -697,6 +792,44 @@ var PROFILES = /* @__PURE__ */ new Map([
697
792
  COMMON_CHECKS.hasDescription
698
793
  ]
699
794
  }],
795
+ ["principles", {
796
+ governedDraft: true,
797
+ descriptionField: "description",
798
+ defaults: [
799
+ { key: "severity", value: "high" },
800
+ { key: "category", value: "infer" }
801
+ ],
802
+ recommendedRelationTypes: ["governs", "informs", "references", "related_to"],
803
+ inferField: (ctx) => {
804
+ const fields = {};
805
+ const area = inferArea(`${ctx.name} ${ctx.description}`);
806
+ if (area) {
807
+ const categoryMap = {
808
+ "Architecture": "Engineering",
809
+ "Chain": "Product",
810
+ "AI & MCP Integration": "Engineering",
811
+ "Developer Experience": "Engineering",
812
+ "Governance & Decision-Making": "Business",
813
+ "Analytics & Tracking": "Product",
814
+ "Security": "Engineering"
815
+ };
816
+ fields.category = categoryMap[area] ?? "Product";
817
+ }
818
+ return fields;
819
+ },
820
+ qualityChecks: [
821
+ COMMON_CHECKS.clearName,
822
+ COMMON_CHECKS.hasDescription,
823
+ COMMON_CHECKS.hasRelations,
824
+ COMMON_CHECKS.hasType,
825
+ {
826
+ id: "has-rationale",
827
+ label: "Rationale provided \u2014 why this principle matters",
828
+ check: (ctx) => typeof ctx.data.rationale === "string" && ctx.data.rationale.length > 20,
829
+ suggestion: () => "Explain why this principle exists and what goes wrong without it."
830
+ }
831
+ ]
832
+ }],
700
833
  ["standards", {
701
834
  governedDraft: true,
702
835
  descriptionField: "description",
@@ -871,7 +1004,7 @@ async function checkEntryQuality(entryId) {
871
1004
  const failedChecks = quality.checks.filter((c) => !c.passed && c.suggestion);
872
1005
  if (failedChecks.length > 0) {
873
1006
  lines.push("");
874
- lines.push(`_To improve: use \`update-entry\` to fill missing fields, or \`relate-entries\` to add connections._`);
1007
+ lines.push(`_To improve: use \`update-entry\` to fill missing fields, or \`relations action=create\` to add connections._`);
875
1008
  }
876
1009
  }
877
1010
  return { text: lines.join("\n"), quality };
@@ -887,22 +1020,48 @@ var GOVERNED_COLLECTIONS = /* @__PURE__ */ new Set([
887
1020
  var AUTO_LINK_CONFIDENCE_THRESHOLD = 35;
888
1021
  var MAX_AUTO_LINKS = 5;
889
1022
  var MAX_SUGGESTIONS = 5;
1023
+ var captureSchema = z.object({
1024
+ collection: z.string().describe("Collection slug, e.g. 'tensions', 'business-rules', 'glossary', 'decisions'"),
1025
+ name: z.string().describe("Display name \u2014 be specific (e.g. 'Convex adjacency list won't scale for graph traversal')"),
1026
+ description: z.string().describe("Full context \u2014 what's happening, why it matters, what you observed"),
1027
+ context: z.string().optional().describe("Optional additional context (e.g. 'Observed during context gather calls taking 700ms+')"),
1028
+ entryId: z.string().optional().describe("Optional custom entry ID (e.g. 'TEN-my-id'). Auto-generated if omitted."),
1029
+ canonicalKey: z.string().optional().describe("Semantic type (e.g. 'decision', 'tension', 'vision'). Auto-assigned from collection if omitted."),
1030
+ 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.")
1031
+ });
1032
+ var batchCaptureSchema = z.object({
1033
+ entries: z.array(z.object({
1034
+ collection: z.string().describe("Collection slug"),
1035
+ name: z.string().describe("Display name"),
1036
+ description: z.string().describe("Full context / definition"),
1037
+ entryId: z.string().optional().describe("Optional custom entry ID")
1038
+ })).min(1).max(50).describe("Array of entries to capture")
1039
+ });
1040
+ var captureOutputSchema = z.object({
1041
+ entryId: z.string(),
1042
+ collection: z.string(),
1043
+ name: z.string(),
1044
+ status: z.literal("draft"),
1045
+ qualityScore: z.number().optional(),
1046
+ qualityVerdict: z.record(z.unknown()).optional()
1047
+ });
1048
+ var batchCaptureOutputSchema = z.object({
1049
+ captured: z.array(z.object({
1050
+ entryId: z.string(),
1051
+ collection: z.string(),
1052
+ name: z.string()
1053
+ })),
1054
+ total: z.number(),
1055
+ failed: z.number()
1056
+ });
890
1057
  function registerSmartCaptureTools(server) {
891
- server.registerTool(
1058
+ const captureTool = server.registerTool(
892
1059
  "capture",
893
1060
  {
894
1061
  title: "Capture",
895
1062
  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 }
1063
+ inputSchema: captureSchema.shape,
1064
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
906
1065
  },
907
1066
  async ({ collection, name, description, context, entryId, canonicalKey, data: userData }) => {
908
1067
  requireWriteAccess();
@@ -917,10 +1076,10 @@ function registerSmartCaptureTools(server) {
917
1076
 
918
1077
  **To create it**, run:
919
1078
  \`\`\`
920
- create-collection slug="${collection}" name="${displayName}" description="..."
1079
+ collections action=create slug="${collection}" name="${displayName}" description="..."
921
1080
  \`\`\`
922
1081
 
923
- Or use \`list-collections\` to see available collections.`
1082
+ Or use \`collections action=list\` to see available collections.`
924
1083
  }]
925
1084
  };
926
1085
  }
@@ -996,7 +1155,7 @@ Or use \`list-collections\` to see available collections.`
996
1155
 
997
1156
  ${msg}
998
1157
 
999
- Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify it.`
1158
+ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to modify it.`
1000
1159
  }]
1001
1160
  };
1002
1161
  }
@@ -1135,7 +1294,7 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1135
1294
  }
1136
1295
  if (linksSuggested.length > 0) {
1137
1296
  lines.push("");
1138
- lines.push("## Suggested links (review and use relate-entries)");
1297
+ lines.push("## Suggested links (review and use `relations action=create`)");
1139
1298
  for (let i = 0; i < linksSuggested.length; i++) {
1140
1299
  const s = linksSuggested[i];
1141
1300
  const preview = s.preview ? ` \u2014 ${s.preview}` : "";
@@ -1154,7 +1313,7 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1154
1313
  if (isBetOrGoal && !hasStrategyLink) {
1155
1314
  lines.push("");
1156
1315
  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.`
1316
+ `**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
1317
  );
1159
1318
  await recordSessionActivity({ strategyLinkWarnedForEntryId: internalId });
1160
1319
  }
@@ -1168,7 +1327,7 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1168
1327
  for (const w of contradictionWarnings) {
1169
1328
  lines.push(`- ${w.name} (${w.collection}, ${w.entryId}) \u2014 has 'governs' relation to ${w.governsCount} entries`);
1170
1329
  }
1171
- lines.push("Run gather-context on these entries before committing.");
1330
+ lines.push("Run `context action=gather` on these entries before committing.");
1172
1331
  }
1173
1332
  if (coachingSection) {
1174
1333
  lines.push("");
@@ -1177,7 +1336,7 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1177
1336
  lines.push("");
1178
1337
  lines.push("## Next Steps");
1179
1338
  const eid = finalEntryId || "(check entry ID)";
1180
- lines.push(`1. **Connect it:** \`suggest-links entryId="${eid}"\` \u2014 discover what this should link to`);
1339
+ lines.push(`1. **Connect it:** \`graph action=suggest entryId="${eid}"\` \u2014 discover what this should link to`);
1181
1340
  lines.push(`2. **Commit it:** \`commit-entry entryId="${eid}"\` \u2014 promote from draft to SSOT on the Chain`);
1182
1341
  if (failedChecks.length > 0) {
1183
1342
  lines.push(`3. **Improve quality:** \`update-entry entryId="${eid}"\` \u2014 fill missing fields`);
@@ -1196,37 +1355,51 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1196
1355
  }
1197
1356
  const toolResult = {
1198
1357
  content: [{ type: "text", text: lines.join("\n") }],
1199
- structuredContent: verdictResult?.verdict ? { qualityVerdict: verdictResult.verdict, source: verdictResult.source ?? "heuristic" } : void 0
1358
+ structuredContent: {
1359
+ entryId: finalEntryId,
1360
+ collection,
1361
+ name,
1362
+ status: "draft",
1363
+ qualityScore: quality.score,
1364
+ qualityVerdict: verdictResult?.verdict ? { ...verdictResult.verdict, source: verdictResult.source ?? "heuristic" } : void 0
1365
+ }
1200
1366
  };
1201
1367
  return toolResult;
1202
1368
  }
1203
1369
  );
1204
- server.registerTool(
1370
+ trackWriteTool(captureTool);
1371
+ const batchCaptureTool = server.registerTool(
1205
1372
  "batch-capture",
1206
1373
  {
1207
1374
  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 }
1375
+ 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.",
1376
+ inputSchema: batchCaptureSchema.shape,
1377
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
1218
1378
  },
1219
1379
  async ({ entries }) => {
1220
1380
  requireWriteAccess();
1221
1381
  const agentId = getAgentSessionId();
1222
1382
  const createdBy = agentId ? `agent:${agentId}` : "capture";
1223
1383
  const results = [];
1384
+ await server.sendLoggingMessage({
1385
+ level: "info",
1386
+ data: `Batch capturing ${entries.length} entries...`,
1387
+ logger: "product-brain"
1388
+ });
1224
1389
  const allCollections = await mcpQuery("chain.listCollections");
1225
1390
  const collCache = /* @__PURE__ */ new Map();
1226
1391
  for (const c of allCollections) collCache.set(c.slug, c);
1227
1392
  const collIdToSlug = /* @__PURE__ */ new Map();
1228
1393
  for (const c of allCollections) collIdToSlug.set(c._id, c.slug);
1229
- for (const entry of entries) {
1394
+ for (let entryIdx = 0; entryIdx < entries.length; entryIdx++) {
1395
+ const entry = entries[entryIdx];
1396
+ if (entryIdx > 0 && entryIdx % 5 === 0) {
1397
+ await server.sendLoggingMessage({
1398
+ level: "info",
1399
+ data: `Captured ${entryIdx}/${entries.length} entries...`,
1400
+ logger: "product-brain"
1401
+ });
1402
+ }
1230
1403
  const profile = PROFILES.get(entry.collection) ?? FALLBACK_PROFILE;
1231
1404
  const col = collCache.get(entry.collection);
1232
1405
  if (!col) {
@@ -1320,6 +1493,11 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1320
1493
  }
1321
1494
  const created = results.filter((r) => r.ok);
1322
1495
  const failed = results.filter((r) => !r.ok);
1496
+ await server.sendLoggingMessage({
1497
+ level: "info",
1498
+ data: `Batch complete. ${created.length} succeeded, ${failed.length} failed.`,
1499
+ logger: "product-brain"
1500
+ });
1323
1501
  const totalAutoLinks = created.reduce((sum, r) => sum + r.autoLinks, 0);
1324
1502
  const byCollection = /* @__PURE__ */ new Map();
1325
1503
  for (const r of created) {
@@ -1356,120 +1534,21 @@ Use \`get-entry\` to inspect the existing entry, or \`update-entry\` to modify i
1356
1534
  if (entryIds.length > 0) {
1357
1535
  lines.push("");
1358
1536
  lines.push("## Next Steps");
1359
- lines.push(`- **Connect:** Run \`suggest-links\` on key entries to build the knowledge graph`);
1537
+ lines.push(`- **Connect:** Run \`graph action=suggest\` on key entries to build the knowledge graph`);
1360
1538
  lines.push(`- **Commit:** Use \`commit-entry\` to promote drafts to SSOT`);
1361
- lines.push(`- **Quality:** Run \`quality-check\` on individual entries to assess completeness`);
1539
+ lines.push(`- **Quality:** Run \`quality action=check\` on individual entries to assess completeness`);
1362
1540
  }
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
- }
1397
- }
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
- }
1541
+ return {
1542
+ content: [{ type: "text", text: lines.join("\n") }],
1543
+ structuredContent: {
1544
+ captured: created.map((r) => ({ entryId: r.entryId, collection: r.collection, name: r.name })),
1545
+ total: created.length,
1546
+ failed: failed.length
1417
1547
  }
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") }] };
1548
+ };
1465
1549
  }
1466
1550
  );
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;
1551
+ trackWriteTool(batchCaptureTool);
1473
1552
  }
1474
1553
  var STOP_WORDS = /* @__PURE__ */ new Set([
1475
1554
  "the",
@@ -1560,7 +1639,7 @@ async function runContradictionCheck(name, description) {
1560
1639
  const text = `${name} ${description}`.toLowerCase();
1561
1640
  const keyTerms = text.split(/\s+/).filter((w) => w.length >= 4 && !STOP_WORDS.has(w)).slice(0, 8);
1562
1641
  if (keyTerms.length === 0) return warnings;
1563
- const searchQuery = keyTerms.slice(0, 3).join(" ");
1642
+ const searchQuery = keyTerms.slice(0, 5).join(" ");
1564
1643
  const [govResults, archResults] = await Promise.all([
1565
1644
  mcpQuery("chain.searchEntries", { query: searchQuery, collectionSlug: "business-rules" }),
1566
1645
  mcpQuery("chain.searchEntries", { query: searchQuery, collectionSlug: "architecture" })
@@ -1569,7 +1648,7 @@ async function runContradictionCheck(name, description) {
1569
1648
  for (const entry of allGov) {
1570
1649
  const entryText = `${entry.name} ${entry.data?.description ?? ""}`.toLowerCase();
1571
1650
  const matched = keyTerms.filter((t) => entryText.includes(t));
1572
- if (matched.length < 2) continue;
1651
+ if (matched.length < 3) continue;
1573
1652
  let governsCount = 0;
1574
1653
  try {
1575
1654
  const relations = await mcpQuery("chain.listEntryRelations", {
@@ -1667,9 +1746,11 @@ function formatRubricVerdictSection(verdict) {
1667
1746
 
1668
1747
  export {
1669
1748
  runWithAuth,
1749
+ runWithToolContext,
1670
1750
  getAgentSessionId,
1671
1751
  isSessionOriented,
1672
1752
  setSessionOriented,
1753
+ getApiKeyScope,
1673
1754
  startAgentSession,
1674
1755
  closeAgentSession,
1675
1756
  orphanAgentSession,
@@ -1685,10 +1766,20 @@ export {
1685
1766
  requireActiveSession,
1686
1767
  requireWriteAccess,
1687
1768
  recoverSessionState,
1769
+ extractPreview,
1770
+ translateStaleToolNames,
1771
+ initToolSurface,
1772
+ trackWriteTool,
1773
+ setWriteToolsEnabled,
1688
1774
  formatQualityReport,
1689
1775
  checkEntryQuality,
1776
+ captureSchema,
1777
+ batchCaptureSchema,
1778
+ captureOutputSchema,
1779
+ batchCaptureOutputSchema,
1690
1780
  registerSmartCaptureTools,
1691
1781
  runContradictionCheck,
1692
- formatRubricCoaching
1782
+ formatRubricCoaching,
1783
+ formatRubricVerdictSection
1693
1784
  };
1694
- //# sourceMappingURL=chunk-P5KVCIYN.js.map
1785
+ //# sourceMappingURL=chunk-3JSR5JCU.js.map