@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.
- package/dist/{chunk-IUSCWY4O.js → chunk-THAYIBTL.js} +5319 -3575
- package/dist/chunk-THAYIBTL.js.map +1 -0
- package/dist/{chunk-P5KVCIYN.js → chunk-UOT3O5FN.js} +244 -150
- package/dist/chunk-UOT3O5FN.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-6TQM35CV.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-6TQM35CV.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,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
|
|
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 \`
|
|
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
|
-
|
|
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
|
|
1076
|
+
collections action=create slug="${collection}" name="${displayName}" description="..."
|
|
921
1077
|
\`\`\`
|
|
922
1078
|
|
|
923
|
-
Or use \`list
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
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 }
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
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
|
-
}
|
|
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,
|
|
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 <
|
|
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-
|
|
1788
|
+
//# sourceMappingURL=chunk-UOT3O5FN.js.map
|