@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.
- package/dist/{chunk-P5KVCIYN.js → chunk-3JSR5JCU.js} +241 -150
- package/dist/chunk-3JSR5JCU.js.map +1 -0
- package/dist/{chunk-IUSCWY4O.js → chunk-AZHB7KMP.js} +4964 -3554
- package/dist/chunk-AZHB7KMP.js.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/http.js +2 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/{setup-4RSCIDNZ.js → setup-S2B5LCXQ.js} +9 -9
- package/dist/setup-S2B5LCXQ.js.map +1 -0
- package/dist/smart-capture-X7BWHT4P.js +26 -0
- package/package.json +1 -1
- package/dist/chunk-IUSCWY4O.js.map +0 -1
- package/dist/chunk-P5KVCIYN.js.map +0 -1
- package/dist/setup-4RSCIDNZ.js.map +0 -1
- package/dist/smart-capture-TLGJDCEQ.js +0 -16
- /package/dist/{smart-capture-TLGJDCEQ.js.map → smart-capture-X7BWHT4P.js.map} +0 -0
|
@@ -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 `
|
|
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 `
|
|
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 `
|
|
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
|
|
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 \`
|
|
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
|
-
|
|
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
|
|
1079
|
+
collections action=create slug="${collection}" name="${displayName}" description="..."
|
|
921
1080
|
\`\`\`
|
|
922
1081
|
|
|
923
|
-
Or use \`list
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
1209
|
-
inputSchema:
|
|
1210
|
-
|
|
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 (
|
|
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
|
|
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
|
|
1539
|
+
lines.push(`- **Quality:** Run \`quality action=check\` on individual entries to assess completeness`);
|
|
1362
1540
|
}
|
|
1363
|
-
return {
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
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
|
-
}
|
|
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,
|
|
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 <
|
|
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-
|
|
1785
|
+
//# sourceMappingURL=chunk-3JSR5JCU.js.map
|