@productbrain/mcp 0.0.1-beta.39 → 0.0.1-beta.40
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-XCKGFYDP.js → chunk-6WVRGNJU.js} +966 -351
- package/dist/chunk-6WVRGNJU.js.map +1 -0
- package/dist/{chunk-7VJP2IMS.js → chunk-M264FY2V.js} +492 -36
- package/dist/chunk-M264FY2V.js.map +1 -0
- package/dist/{chunk-TB24VJ4Z.js → chunk-P7ABQEFK.js} +33 -1
- package/dist/{chunk-TB24VJ4Z.js.map → chunk-P7ABQEFK.js.map} +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/http.js +6 -3
- package/dist/http.js.map +1 -1
- package/dist/index.js +15 -14
- package/dist/index.js.map +1 -1
- package/dist/{setup-AJCCLPQP.js → setup-RSGAAKJB.js} +2 -2
- package/dist/{smart-capture-E53YEHHO.js → smart-capture-GH4CXVVX.js} +13 -3
- package/package.json +1 -1
- package/dist/chunk-7VJP2IMS.js.map +0 -1
- package/dist/chunk-XCKGFYDP.js.map +0 -1
- /package/dist/{setup-AJCCLPQP.js.map → setup-RSGAAKJB.js.map} +0 -0
- /package/dist/{smart-capture-E53YEHHO.js.map → smart-capture-GH4CXVVX.js.map} +0 -0
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
trackCaptureClassifierAutoRouted,
|
|
3
|
+
trackCaptureClassifierEvaluated,
|
|
4
|
+
trackCaptureClassifierFallback,
|
|
2
5
|
trackQualityVerdict,
|
|
3
6
|
trackToolCall
|
|
4
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-P7ABQEFK.js";
|
|
5
8
|
|
|
6
9
|
// src/tools/smart-capture.ts
|
|
7
10
|
import { z } from "zod";
|
|
@@ -27,6 +30,7 @@ function newKeyState() {
|
|
|
27
30
|
workspaceSlug: null,
|
|
28
31
|
workspaceName: null,
|
|
29
32
|
workspaceCreatedAt: null,
|
|
33
|
+
workspaceGovernanceMode: null,
|
|
30
34
|
agentSessionId: null,
|
|
31
35
|
apiKeyId: null,
|
|
32
36
|
apiKeyScope: "readwrite",
|
|
@@ -112,6 +116,7 @@ var _stdioState = {
|
|
|
112
116
|
workspaceSlug: null,
|
|
113
117
|
workspaceName: null,
|
|
114
118
|
workspaceCreatedAt: null,
|
|
119
|
+
workspaceGovernanceMode: null,
|
|
115
120
|
agentSessionId: null,
|
|
116
121
|
apiKeyId: null,
|
|
117
122
|
apiKeyScope: "readwrite",
|
|
@@ -328,6 +333,7 @@ async function resolveWorkspaceWithRetry(maxRetries = 2) {
|
|
|
328
333
|
s.workspaceSlug = workspace.slug;
|
|
329
334
|
s.workspaceName = workspace.name;
|
|
330
335
|
s.workspaceCreatedAt = workspace.createdAt ?? null;
|
|
336
|
+
s.workspaceGovernanceMode = workspace.governanceMode ?? "open";
|
|
331
337
|
if (workspace.keyScope) s.apiKeyScope = workspace.keyScope;
|
|
332
338
|
if (workspace.keyId) s.apiKeyId = workspace.keyId;
|
|
333
339
|
return s.workspaceId;
|
|
@@ -352,7 +358,8 @@ async function getWorkspaceContext() {
|
|
|
352
358
|
workspaceId,
|
|
353
359
|
workspaceSlug: s.workspaceSlug ?? "unknown",
|
|
354
360
|
workspaceName: s.workspaceName ?? "unknown",
|
|
355
|
-
createdAt: s.workspaceCreatedAt
|
|
361
|
+
createdAt: s.workspaceCreatedAt,
|
|
362
|
+
governanceMode: s.workspaceGovernanceMode ?? "open"
|
|
356
363
|
};
|
|
357
364
|
}
|
|
358
365
|
async function mcpQuery(fn, args = {}) {
|
|
@@ -475,6 +482,189 @@ function initToolSurface(_server) {
|
|
|
475
482
|
function trackWriteTool(_tool) {
|
|
476
483
|
}
|
|
477
484
|
|
|
485
|
+
// src/featureFlags.ts
|
|
486
|
+
import { PostHog } from "posthog-node";
|
|
487
|
+
var client = null;
|
|
488
|
+
var LOCAL_OVERRIDES = {
|
|
489
|
+
// Uncomment for local dev without PostHog:
|
|
490
|
+
// "workspace-full-surface": false,
|
|
491
|
+
// "chainwork-enabled": true,
|
|
492
|
+
// "active-intelligence-shaping": true, // ENT-59: Opus-powered investigation during shaping
|
|
493
|
+
// "capture-without-thinking": true, // BET-73: collection-optional capture classifier
|
|
494
|
+
};
|
|
495
|
+
function initFeatureFlags(posthogClient) {
|
|
496
|
+
if (posthogClient) {
|
|
497
|
+
client = posthogClient;
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const apiKey = process.env.POSTHOG_MCP_KEY || process.env.PUBLIC_POSTHOG_KEY;
|
|
501
|
+
if (!apiKey) {
|
|
502
|
+
client = null;
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
client = new PostHog(apiKey, {
|
|
506
|
+
host: process.env.PUBLIC_POSTHOG_HOST || "https://eu.i.posthog.com",
|
|
507
|
+
flushAt: 1,
|
|
508
|
+
flushInterval: 5e3,
|
|
509
|
+
featureFlagsPollingInterval: 3e4
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
async function isFeatureEnabled(flag, workspaceId, workspaceSlug) {
|
|
513
|
+
if (process.env.FEATURE_KILL_SWITCH === "true") return false;
|
|
514
|
+
if (flag in LOCAL_OVERRIDES) return LOCAL_OVERRIDES[flag];
|
|
515
|
+
if (!client) return false;
|
|
516
|
+
try {
|
|
517
|
+
const primary = await client.isFeatureEnabled(flag, workspaceId, {
|
|
518
|
+
groups: { workspace: workspaceId },
|
|
519
|
+
groupProperties: {
|
|
520
|
+
workspace: {
|
|
521
|
+
workspace_id: workspaceId,
|
|
522
|
+
slug: workspaceSlug ?? ""
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
if (primary) return true;
|
|
527
|
+
if (workspaceSlug && workspaceSlug !== workspaceId) {
|
|
528
|
+
const secondary = await client.isFeatureEnabled(flag, workspaceSlug, {
|
|
529
|
+
groups: { workspace: workspaceSlug },
|
|
530
|
+
groupProperties: {
|
|
531
|
+
workspace: {
|
|
532
|
+
workspace_id: workspaceId,
|
|
533
|
+
slug: workspaceSlug
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
return secondary ?? false;
|
|
538
|
+
}
|
|
539
|
+
return primary ?? false;
|
|
540
|
+
} catch {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/tools/smart-capture-routing.ts
|
|
546
|
+
var CLASSIFIER_AUTO_ROUTE_THRESHOLD = 70;
|
|
547
|
+
var CLASSIFIER_AMBIGUITY_MARGIN = 15;
|
|
548
|
+
var STARTER_COLLECTIONS = ["decisions", "tensions", "glossary", "insights", "bets"];
|
|
549
|
+
var SIGNAL_WEIGHT = 10;
|
|
550
|
+
var MAX_MATCHES_PER_SIGNAL = 2;
|
|
551
|
+
var MAX_REASON_COUNT = 3;
|
|
552
|
+
var STARTER_COLLECTION_SIGNALS = {
|
|
553
|
+
decisions: [
|
|
554
|
+
"decide",
|
|
555
|
+
"decision",
|
|
556
|
+
"chose",
|
|
557
|
+
"chosen",
|
|
558
|
+
"choice",
|
|
559
|
+
"resolved",
|
|
560
|
+
"we will",
|
|
561
|
+
"we should",
|
|
562
|
+
"approved"
|
|
563
|
+
],
|
|
564
|
+
tensions: [
|
|
565
|
+
"problem",
|
|
566
|
+
"issue",
|
|
567
|
+
"blocked",
|
|
568
|
+
"blocker",
|
|
569
|
+
"friction",
|
|
570
|
+
"pain",
|
|
571
|
+
"risk",
|
|
572
|
+
"constraint",
|
|
573
|
+
"bottleneck",
|
|
574
|
+
"struggle"
|
|
575
|
+
],
|
|
576
|
+
glossary: [
|
|
577
|
+
"definition",
|
|
578
|
+
"define",
|
|
579
|
+
"term",
|
|
580
|
+
"means",
|
|
581
|
+
"refers to",
|
|
582
|
+
"is called",
|
|
583
|
+
"vocabulary",
|
|
584
|
+
"terminology"
|
|
585
|
+
],
|
|
586
|
+
insights: [
|
|
587
|
+
"insight",
|
|
588
|
+
"learned",
|
|
589
|
+
"observed",
|
|
590
|
+
"pattern",
|
|
591
|
+
"trend",
|
|
592
|
+
"signal",
|
|
593
|
+
"found that",
|
|
594
|
+
"evidence"
|
|
595
|
+
],
|
|
596
|
+
bets: [
|
|
597
|
+
"bet",
|
|
598
|
+
"appetite",
|
|
599
|
+
"scope",
|
|
600
|
+
"elements",
|
|
601
|
+
"rabbit hole",
|
|
602
|
+
"no-go",
|
|
603
|
+
"shape",
|
|
604
|
+
"shaping",
|
|
605
|
+
"done when"
|
|
606
|
+
]
|
|
607
|
+
};
|
|
608
|
+
function escapeRegExp(text) {
|
|
609
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
610
|
+
}
|
|
611
|
+
function countSignalMatches(text, signal) {
|
|
612
|
+
const trimmed = signal.trim().toLowerCase();
|
|
613
|
+
if (!trimmed) return 0;
|
|
614
|
+
const words = trimmed.split(/\s+/).map(escapeRegExp);
|
|
615
|
+
const pattern = `\\b${words.join("\\s+")}\\b`;
|
|
616
|
+
const regex = new RegExp(pattern, "g");
|
|
617
|
+
const matches = text.match(regex);
|
|
618
|
+
return matches?.length ?? 0;
|
|
619
|
+
}
|
|
620
|
+
function classifyStarterCollection(name, description) {
|
|
621
|
+
const text = `${name} ${description}`.toLowerCase();
|
|
622
|
+
const rawScores = [];
|
|
623
|
+
for (const collection of STARTER_COLLECTIONS) {
|
|
624
|
+
const signals = STARTER_COLLECTION_SIGNALS[collection];
|
|
625
|
+
const reasons = [];
|
|
626
|
+
let score = 0;
|
|
627
|
+
for (const signal of signals) {
|
|
628
|
+
const matches = countSignalMatches(text, signal);
|
|
629
|
+
if (matches <= 0) continue;
|
|
630
|
+
const cappedMatches = Math.min(matches, MAX_MATCHES_PER_SIGNAL);
|
|
631
|
+
score += cappedMatches * SIGNAL_WEIGHT;
|
|
632
|
+
if (reasons.length < MAX_REASON_COUNT) {
|
|
633
|
+
const capNote = matches > cappedMatches ? ` (capped at ${cappedMatches})` : "";
|
|
634
|
+
reasons.push(`matched "${signal}" x${matches}${capNote}`);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
rawScores.push({ collection, score, reasons });
|
|
638
|
+
}
|
|
639
|
+
rawScores.sort((a, b) => b.score - a.score);
|
|
640
|
+
const top = rawScores[0];
|
|
641
|
+
const second = rawScores[1];
|
|
642
|
+
if (!top || top.score <= 0) return null;
|
|
643
|
+
const margin = Math.max(0, top.score - (second?.score ?? 0));
|
|
644
|
+
const baseConfidence = Math.min(90, top.score);
|
|
645
|
+
const confidence = Math.min(99, baseConfidence + Math.min(20, margin));
|
|
646
|
+
return {
|
|
647
|
+
collection: top.collection,
|
|
648
|
+
topConfidence: confidence,
|
|
649
|
+
confidence,
|
|
650
|
+
reasons: top.reasons,
|
|
651
|
+
scoreMargin: margin,
|
|
652
|
+
candidates: rawScores.filter((candidate) => candidate.score > 0).slice(0, 3).map((candidate) => ({
|
|
653
|
+
collection: candidate.collection,
|
|
654
|
+
signalScore: Math.min(99, candidate.score),
|
|
655
|
+
confidence: Math.min(99, candidate.score)
|
|
656
|
+
}))
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
function shouldAutoRouteClassification(result) {
|
|
660
|
+
if (result.confidence < CLASSIFIER_AUTO_ROUTE_THRESHOLD) return false;
|
|
661
|
+
if (result.scoreMargin < CLASSIFIER_AMBIGUITY_MARGIN) return false;
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
function isClassificationAmbiguous(result) {
|
|
665
|
+
return result.scoreMargin < CLASSIFIER_AMBIGUITY_MARGIN;
|
|
666
|
+
}
|
|
667
|
+
|
|
478
668
|
// src/tools/smart-capture.ts
|
|
479
669
|
var AREA_KEYWORDS = {
|
|
480
670
|
"Architecture": ["convex", "schema", "database", "migration", "api", "backend", "infrastructure", "scaling", "performance"],
|
|
@@ -1020,8 +1210,9 @@ var GOVERNED_COLLECTIONS = /* @__PURE__ */ new Set([
|
|
|
1020
1210
|
var AUTO_LINK_CONFIDENCE_THRESHOLD = 35;
|
|
1021
1211
|
var MAX_AUTO_LINKS = 5;
|
|
1022
1212
|
var MAX_SUGGESTIONS = 5;
|
|
1213
|
+
var CAPTURE_WITHOUT_THINKING_FLAG = "capture-without-thinking";
|
|
1023
1214
|
var captureSchema = z.object({
|
|
1024
|
-
collection: z.string().describe("Collection slug, e.g. 'tensions', 'business-rules', 'glossary', 'decisions'"),
|
|
1215
|
+
collection: z.string().optional().describe("Collection slug, e.g. 'tensions', 'business-rules', 'glossary', 'decisions'. Optional when `capture-without-thinking` is enabled."),
|
|
1025
1216
|
name: z.string().describe("Display name \u2014 be specific (e.g. 'Convex adjacency list won't scale for graph traversal')"),
|
|
1026
1217
|
description: z.string().describe("Full context \u2014 what's happening, why it matters, what you observed"),
|
|
1027
1218
|
context: z.string().optional().describe("Optional additional context (e.g. 'Observed during context gather calls taking 700ms+')"),
|
|
@@ -1042,14 +1233,223 @@ var batchCaptureSchema = z.object({
|
|
|
1042
1233
|
entryId: z.string().optional().describe("Optional custom entry ID")
|
|
1043
1234
|
})).min(1).max(50).describe("Array of entries to capture")
|
|
1044
1235
|
});
|
|
1045
|
-
var
|
|
1236
|
+
var captureClassifierSchema = z.object({
|
|
1237
|
+
enabled: z.boolean(),
|
|
1238
|
+
autoRouted: z.boolean(),
|
|
1239
|
+
topConfidence: z.number(),
|
|
1240
|
+
// Backward-compatible alias for topConfidence.
|
|
1241
|
+
confidence: z.number(),
|
|
1242
|
+
reasons: z.array(z.string()),
|
|
1243
|
+
candidates: z.array(
|
|
1244
|
+
z.object({
|
|
1245
|
+
collection: z.enum(STARTER_COLLECTIONS),
|
|
1246
|
+
signalScore: z.number(),
|
|
1247
|
+
// Backward-compatible alias for signalScore.
|
|
1248
|
+
confidence: z.number()
|
|
1249
|
+
})
|
|
1250
|
+
)
|
|
1251
|
+
});
|
|
1252
|
+
function trackClassifierTelemetry(params) {
|
|
1253
|
+
const telemetry = {
|
|
1254
|
+
predicted_collection: params.predictedCollection,
|
|
1255
|
+
confidence: params.confidence,
|
|
1256
|
+
auto_routed: params.autoRouted,
|
|
1257
|
+
reason_category: params.reasonCategory,
|
|
1258
|
+
explicit_collection_provided: params.explicitCollectionProvided
|
|
1259
|
+
};
|
|
1260
|
+
trackCaptureClassifierEvaluated(params.workspaceId, telemetry);
|
|
1261
|
+
if (params.outcome === "auto-routed") {
|
|
1262
|
+
trackCaptureClassifierAutoRouted(params.workspaceId, telemetry);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
trackCaptureClassifierFallback(params.workspaceId, telemetry);
|
|
1266
|
+
}
|
|
1267
|
+
function buildCollectionRequiredResult() {
|
|
1268
|
+
return {
|
|
1269
|
+
content: [{
|
|
1270
|
+
type: "text",
|
|
1271
|
+
text: "Collection is required unless `capture-without-thinking` is enabled.\n\nProvide `collection` explicitly, or enable the feature flag for this workspace."
|
|
1272
|
+
}]
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
function buildClassifierUnknownResult() {
|
|
1276
|
+
return {
|
|
1277
|
+
content: [{
|
|
1278
|
+
type: "text",
|
|
1279
|
+
text: "I could not infer a collection confidently from this input.\n\nPlease provide `collection`, or rewrite with clearer intent (decision/problem/definition/insight/bet)."
|
|
1280
|
+
}],
|
|
1281
|
+
structuredContent: {
|
|
1282
|
+
classifier: {
|
|
1283
|
+
enabled: true,
|
|
1284
|
+
autoRouted: false,
|
|
1285
|
+
topConfidence: 0,
|
|
1286
|
+
confidence: 0,
|
|
1287
|
+
reasons: [],
|
|
1288
|
+
candidates: []
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
function buildProvisionedCollectionSuggestions(candidates) {
|
|
1294
|
+
return candidates.length ? candidates.map((c) => `- \`${c.collection}\` (${c.signalScore}% signal score)`).join("\n") : "- No provisioned starter collection candidates were inferred confidently.";
|
|
1295
|
+
}
|
|
1296
|
+
function buildUnsupportedProvisioningResult(classified, provisionedCandidates) {
|
|
1297
|
+
const suggestions = buildProvisionedCollectionSuggestions(provisionedCandidates);
|
|
1298
|
+
return {
|
|
1299
|
+
content: [{
|
|
1300
|
+
type: "text",
|
|
1301
|
+
text: `Collection inference is not safe to auto-route yet.
|
|
1302
|
+
|
|
1303
|
+
Predicted collection \`${classified.collection}\` is not provisioned/supported for auto-routing in this workspace.
|
|
1304
|
+
Reason: ${classified.reasons.join("; ") || "low signal"}
|
|
1305
|
+
|
|
1306
|
+
Choose one of these provisioned starter collections and retry with \`collection\`:
|
|
1307
|
+
${suggestions}
|
|
1308
|
+
|
|
1309
|
+
Correction path: rerun with explicit \`collection\`.`
|
|
1310
|
+
}],
|
|
1311
|
+
structuredContent: {
|
|
1312
|
+
classifier: {
|
|
1313
|
+
enabled: true,
|
|
1314
|
+
autoRouted: false,
|
|
1315
|
+
topConfidence: classified.topConfidence,
|
|
1316
|
+
confidence: classified.confidence,
|
|
1317
|
+
reasons: classified.reasons,
|
|
1318
|
+
candidates: provisionedCandidates
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
function buildAmbiguousRouteResult(classified, classifierMeta, ambiguousRoute) {
|
|
1324
|
+
const suggestions = buildProvisionedCollectionSuggestions(classifierMeta.candidates);
|
|
1325
|
+
return {
|
|
1326
|
+
content: [{
|
|
1327
|
+
type: "text",
|
|
1328
|
+
text: "Collection inference is not safe to auto-route yet.\n\n" + (ambiguousRoute ? "Routing held because intent is ambiguous across top candidates.\n\n" : "") + `Predicted: \`${classified.collection}\` (${classified.topConfidence}% top confidence)
|
|
1329
|
+
Reason: ${classified.reasons.join("; ") || "low signal"}
|
|
1330
|
+
|
|
1331
|
+
Choose one of these and retry with \`collection\`:
|
|
1332
|
+
${suggestions}
|
|
1333
|
+
|
|
1334
|
+
Correction path: if this was close, rerun with your chosen \`collection\`.`
|
|
1335
|
+
}],
|
|
1336
|
+
structuredContent: {
|
|
1337
|
+
classifier: classifierMeta
|
|
1338
|
+
}
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
async function getProvisionedStarterCollectionCandidates(classified, supportedStarterCollections) {
|
|
1342
|
+
const allCollections = await mcpQuery("chain.listCollections");
|
|
1343
|
+
const provisionedStarterCollections = new Set(
|
|
1344
|
+
(allCollections ?? []).map((collection) => collection.slug).filter(
|
|
1345
|
+
(slug) => supportedStarterCollections.has(slug)
|
|
1346
|
+
)
|
|
1347
|
+
);
|
|
1348
|
+
const provisionedCandidates = classified.candidates.filter(
|
|
1349
|
+
(candidate) => provisionedStarterCollections.has(candidate.collection)
|
|
1350
|
+
);
|
|
1351
|
+
return { provisionedStarterCollections, provisionedCandidates };
|
|
1352
|
+
}
|
|
1353
|
+
async function resolveCaptureCollection(params) {
|
|
1354
|
+
const {
|
|
1355
|
+
collection,
|
|
1356
|
+
name,
|
|
1357
|
+
description,
|
|
1358
|
+
classifierFlagOn,
|
|
1359
|
+
supportedStarterCollections,
|
|
1360
|
+
workspaceId,
|
|
1361
|
+
explicitCollectionProvided
|
|
1362
|
+
} = params;
|
|
1363
|
+
if (collection) {
|
|
1364
|
+
return { resolvedCollection: collection };
|
|
1365
|
+
}
|
|
1366
|
+
if (!classifierFlagOn) {
|
|
1367
|
+
return { earlyResult: buildCollectionRequiredResult() };
|
|
1368
|
+
}
|
|
1369
|
+
const classified = classifyStarterCollection(name, description);
|
|
1370
|
+
if (!classified) {
|
|
1371
|
+
trackClassifierTelemetry({
|
|
1372
|
+
workspaceId,
|
|
1373
|
+
predictedCollection: "unknown",
|
|
1374
|
+
confidence: 0,
|
|
1375
|
+
autoRouted: false,
|
|
1376
|
+
reasonCategory: "low-confidence",
|
|
1377
|
+
explicitCollectionProvided,
|
|
1378
|
+
outcome: "fallback"
|
|
1379
|
+
});
|
|
1380
|
+
return { earlyResult: buildClassifierUnknownResult() };
|
|
1381
|
+
}
|
|
1382
|
+
const { provisionedStarterCollections, provisionedCandidates } = await getProvisionedStarterCollectionCandidates(classified, supportedStarterCollections);
|
|
1383
|
+
if (!provisionedStarterCollections.has(classified.collection)) {
|
|
1384
|
+
trackClassifierTelemetry({
|
|
1385
|
+
workspaceId,
|
|
1386
|
+
predictedCollection: classified.collection,
|
|
1387
|
+
confidence: classified.confidence,
|
|
1388
|
+
autoRouted: false,
|
|
1389
|
+
reasonCategory: "non-provisioned",
|
|
1390
|
+
explicitCollectionProvided,
|
|
1391
|
+
outcome: "fallback"
|
|
1392
|
+
});
|
|
1393
|
+
return {
|
|
1394
|
+
earlyResult: buildUnsupportedProvisioningResult(classified, provisionedCandidates)
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
const autoRoute = shouldAutoRouteClassification(classified);
|
|
1398
|
+
const ambiguousRoute = isClassificationAmbiguous(classified);
|
|
1399
|
+
const classifierMeta = {
|
|
1400
|
+
enabled: true,
|
|
1401
|
+
autoRouted: autoRoute,
|
|
1402
|
+
topConfidence: classified.topConfidence,
|
|
1403
|
+
confidence: classified.confidence,
|
|
1404
|
+
reasons: classified.reasons,
|
|
1405
|
+
candidates: provisionedCandidates
|
|
1406
|
+
};
|
|
1407
|
+
if (!autoRoute) {
|
|
1408
|
+
trackClassifierTelemetry({
|
|
1409
|
+
workspaceId,
|
|
1410
|
+
predictedCollection: classified.collection,
|
|
1411
|
+
confidence: classified.confidence,
|
|
1412
|
+
autoRouted: false,
|
|
1413
|
+
reasonCategory: ambiguousRoute ? "ambiguous" : "low-confidence",
|
|
1414
|
+
explicitCollectionProvided,
|
|
1415
|
+
outcome: "fallback"
|
|
1416
|
+
});
|
|
1417
|
+
return {
|
|
1418
|
+
classifierMeta,
|
|
1419
|
+
earlyResult: buildAmbiguousRouteResult(classified, classifierMeta, ambiguousRoute)
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
trackClassifierTelemetry({
|
|
1423
|
+
workspaceId,
|
|
1424
|
+
predictedCollection: classified.collection,
|
|
1425
|
+
confidence: classified.confidence,
|
|
1426
|
+
autoRouted: true,
|
|
1427
|
+
reasonCategory: "auto-routed",
|
|
1428
|
+
explicitCollectionProvided,
|
|
1429
|
+
outcome: "auto-routed"
|
|
1430
|
+
});
|
|
1431
|
+
return {
|
|
1432
|
+
resolvedCollection: classified.collection,
|
|
1433
|
+
classifierMeta
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
var captureSuccessOutputSchema = z.object({
|
|
1046
1437
|
entryId: z.string(),
|
|
1047
1438
|
collection: z.string(),
|
|
1048
1439
|
name: z.string(),
|
|
1049
1440
|
status: z.enum(["draft", "committed"]),
|
|
1050
|
-
qualityScore: z.number()
|
|
1051
|
-
qualityVerdict: z.record(z.unknown()).optional()
|
|
1052
|
-
|
|
1441
|
+
qualityScore: z.number(),
|
|
1442
|
+
qualityVerdict: z.record(z.unknown()).optional(),
|
|
1443
|
+
classifier: captureClassifierSchema.optional(),
|
|
1444
|
+
studioUrl: z.string().optional()
|
|
1445
|
+
}).strict();
|
|
1446
|
+
var captureClassifierOnlyOutputSchema = z.object({
|
|
1447
|
+
classifier: captureClassifierSchema
|
|
1448
|
+
}).strict();
|
|
1449
|
+
var captureOutputSchema = z.union([
|
|
1450
|
+
captureSuccessOutputSchema,
|
|
1451
|
+
captureClassifierOnlyOutputSchema
|
|
1452
|
+
]);
|
|
1053
1453
|
var batchCaptureOutputSchema = z.object({
|
|
1054
1454
|
captured: z.array(z.object({
|
|
1055
1455
|
entryId: z.string(),
|
|
@@ -1057,31 +1457,64 @@ var batchCaptureOutputSchema = z.object({
|
|
|
1057
1457
|
name: z.string()
|
|
1058
1458
|
})),
|
|
1059
1459
|
total: z.number(),
|
|
1060
|
-
failed: z.number()
|
|
1460
|
+
failed: z.number(),
|
|
1461
|
+
failedEntries: z.array(z.object({
|
|
1462
|
+
index: z.number(),
|
|
1463
|
+
collection: z.string(),
|
|
1464
|
+
name: z.string(),
|
|
1465
|
+
error: z.string()
|
|
1466
|
+
})).optional()
|
|
1061
1467
|
});
|
|
1062
1468
|
function registerSmartCaptureTools(server) {
|
|
1469
|
+
const supportedStarterCollections = new Set(
|
|
1470
|
+
STARTER_COLLECTIONS.filter((slug) => PROFILES.has(slug))
|
|
1471
|
+
);
|
|
1063
1472
|
const captureTool = server.registerTool(
|
|
1064
1473
|
"capture",
|
|
1065
1474
|
{
|
|
1066
1475
|
title: "Capture",
|
|
1067
|
-
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
|
|
1476
|
+
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 name and description; `collection` is optional when `capture-without-thinking` is enabled.\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\n**Compound capture:** Pass `links` to create relations in the same call (skips auto-link discovery). Pass `autoCommit: true` to promote the entry from draft to SSOT immediately after linking. Governed collections (glossary, business-rules, principles, standards, strategy, features) will warn but still commit \u2014 use only when you're certain.\n\nAlways creates as 'draft' unless `autoCommit` is true. Use `update-entry` for post-creation adjustments.",
|
|
1068
1477
|
inputSchema: captureSchema.shape,
|
|
1069
1478
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
1070
1479
|
},
|
|
1071
1480
|
async ({ collection, name, description, context, entryId, canonicalKey, data: userData, links, autoCommit }) => {
|
|
1072
1481
|
requireWriteAccess();
|
|
1073
|
-
const
|
|
1074
|
-
const
|
|
1482
|
+
const wsCtx = await getWorkspaceContext();
|
|
1483
|
+
const explicitCollectionProvided = typeof collection === "string" && collection.trim().length > 0;
|
|
1484
|
+
const classifierFlagOn = await isFeatureEnabled(
|
|
1485
|
+
CAPTURE_WITHOUT_THINKING_FLAG,
|
|
1486
|
+
wsCtx.workspaceId,
|
|
1487
|
+
wsCtx.workspaceSlug
|
|
1488
|
+
);
|
|
1489
|
+
const resolution = await resolveCaptureCollection({
|
|
1490
|
+
collection,
|
|
1491
|
+
name,
|
|
1492
|
+
description,
|
|
1493
|
+
classifierFlagOn,
|
|
1494
|
+
supportedStarterCollections,
|
|
1495
|
+
workspaceId: wsCtx.workspaceId,
|
|
1496
|
+
explicitCollectionProvided
|
|
1497
|
+
});
|
|
1498
|
+
if (resolution.earlyResult) {
|
|
1499
|
+
return resolution.earlyResult;
|
|
1500
|
+
}
|
|
1501
|
+
const resolvedCollection = resolution.resolvedCollection;
|
|
1502
|
+
const classifierMeta = resolution.classifierMeta;
|
|
1503
|
+
if (!resolvedCollection) {
|
|
1504
|
+
return buildCollectionRequiredResult();
|
|
1505
|
+
}
|
|
1506
|
+
const profile = PROFILES.get(resolvedCollection) ?? FALLBACK_PROFILE;
|
|
1507
|
+
const col = await mcpQuery("chain.getCollection", { slug: resolvedCollection });
|
|
1075
1508
|
if (!col) {
|
|
1076
|
-
const displayName =
|
|
1509
|
+
const displayName = resolvedCollection.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
1077
1510
|
return {
|
|
1078
1511
|
content: [{
|
|
1079
1512
|
type: "text",
|
|
1080
|
-
text: `Collection \`${
|
|
1513
|
+
text: `Collection \`${resolvedCollection}\` not found.
|
|
1081
1514
|
|
|
1082
1515
|
**To create it**, run:
|
|
1083
1516
|
\`\`\`
|
|
1084
|
-
collections action=create slug="${
|
|
1517
|
+
collections action=create slug="${resolvedCollection}" name="${displayName}" description="..."
|
|
1085
1518
|
\`\`\`
|
|
1086
1519
|
|
|
1087
1520
|
Or use \`collections action=list\` to see available collections.`
|
|
@@ -1110,7 +1543,7 @@ Or use \`collections action=list\` to see available collections.`
|
|
|
1110
1543
|
}
|
|
1111
1544
|
if (profile.inferField) {
|
|
1112
1545
|
const inferred = profile.inferField({
|
|
1113
|
-
collection,
|
|
1546
|
+
collection: resolvedCollection,
|
|
1114
1547
|
name,
|
|
1115
1548
|
description,
|
|
1116
1549
|
context,
|
|
@@ -1138,7 +1571,7 @@ Or use \`collections action=list\` to see available collections.`
|
|
|
1138
1571
|
try {
|
|
1139
1572
|
const agentId = getAgentSessionId();
|
|
1140
1573
|
const result = await mcpMutation("chain.createEntry", {
|
|
1141
|
-
collectionSlug:
|
|
1574
|
+
collectionSlug: resolvedCollection,
|
|
1142
1575
|
entryId: entryId ?? void 0,
|
|
1143
1576
|
name,
|
|
1144
1577
|
status,
|
|
@@ -1179,7 +1612,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
1179
1612
|
const collMap = /* @__PURE__ */ new Map();
|
|
1180
1613
|
for (const c of allCollections) collMap.set(c._id, c.slug);
|
|
1181
1614
|
const candidates = (searchResults ?? []).filter((r) => r.entryId !== finalEntryId && r._id !== internalId).map((r) => {
|
|
1182
|
-
const conf = computeLinkConfidence(r, name, description,
|
|
1615
|
+
const conf = computeLinkConfidence(r, name, description, resolvedCollection, collMap.get(r.collectionId) ?? "unknown");
|
|
1183
1616
|
return {
|
|
1184
1617
|
...r,
|
|
1185
1618
|
collSlug: collMap.get(r.collectionId) ?? "unknown",
|
|
@@ -1191,7 +1624,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
1191
1624
|
if (linksCreated.length >= MAX_AUTO_LINKS) break;
|
|
1192
1625
|
if (c.confidence < AUTO_LINK_CONFIDENCE_THRESHOLD) break;
|
|
1193
1626
|
if (!c.entryId || !finalEntryId) continue;
|
|
1194
|
-
const { type: relationType, reason: relationReason } = inferRelationType(
|
|
1627
|
+
const { type: relationType, reason: relationReason } = inferRelationType(resolvedCollection, c.collSlug, profile);
|
|
1195
1628
|
try {
|
|
1196
1629
|
await mcpMutation("chain.createEntryRelation", {
|
|
1197
1630
|
fromEntryId: finalEntryId,
|
|
@@ -1246,7 +1679,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
1246
1679
|
}
|
|
1247
1680
|
}
|
|
1248
1681
|
const captureCtx = {
|
|
1249
|
-
collection,
|
|
1682
|
+
collection: resolvedCollection,
|
|
1250
1683
|
name,
|
|
1251
1684
|
description,
|
|
1252
1685
|
context,
|
|
@@ -1294,7 +1727,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
1294
1727
|
const failedCount = (v.criteria ?? []).filter((c) => !c.passed).length;
|
|
1295
1728
|
trackQualityVerdict(wsForTracking.workspaceId, {
|
|
1296
1729
|
entry_id: finalEntryId,
|
|
1297
|
-
entry_type: v.canonicalKey ??
|
|
1730
|
+
entry_type: v.canonicalKey ?? resolvedCollection,
|
|
1298
1731
|
tier: v.tier,
|
|
1299
1732
|
context: "capture",
|
|
1300
1733
|
passed: v.passed,
|
|
@@ -1306,9 +1739,10 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
1306
1739
|
} catch {
|
|
1307
1740
|
}
|
|
1308
1741
|
}
|
|
1742
|
+
const shouldAutoCommit = autoCommit === true || autoCommit === void 0 && wsCtx.governanceMode === "open";
|
|
1309
1743
|
let finalStatus = "draft";
|
|
1310
1744
|
let commitError = null;
|
|
1311
|
-
if (
|
|
1745
|
+
if (shouldAutoCommit && finalEntryId) {
|
|
1312
1746
|
try {
|
|
1313
1747
|
await mcpMutation("chain.commitEntry", { entryId: finalEntryId });
|
|
1314
1748
|
finalStatus = "committed";
|
|
@@ -1317,14 +1751,22 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
1317
1751
|
commitError = e instanceof Error ? e.message : "unknown error";
|
|
1318
1752
|
}
|
|
1319
1753
|
}
|
|
1320
|
-
const wsCtx = await getWorkspaceContext();
|
|
1321
1754
|
const lines = [
|
|
1322
1755
|
`# Captured: ${finalEntryId || name}`,
|
|
1323
|
-
`**${name}** added to \`${
|
|
1756
|
+
`**${name}** added to \`${resolvedCollection}\` as \`${finalStatus}\``,
|
|
1324
1757
|
`**Workspace:** ${wsCtx.workspaceSlug} (${wsCtx.workspaceId})`
|
|
1325
1758
|
];
|
|
1759
|
+
if (classifierMeta?.autoRouted) {
|
|
1760
|
+
lines.push("");
|
|
1761
|
+
lines.push("## Collection routing");
|
|
1762
|
+
lines.push(`Auto-routed to \`${resolvedCollection}\` (${classifierMeta.topConfidence}% top confidence).`);
|
|
1763
|
+
if (classifierMeta.reasons.length > 0) {
|
|
1764
|
+
lines.push(`Reason: ${classifierMeta.reasons.join("; ")}.`);
|
|
1765
|
+
}
|
|
1766
|
+
lines.push("Correction path: rerun capture with explicit `collection` if this routing is wrong.");
|
|
1767
|
+
}
|
|
1326
1768
|
const appUrl = process.env.PRODUCTBRAIN_APP_URL ?? "https://productbrain.io";
|
|
1327
|
-
const studioUrl =
|
|
1769
|
+
const studioUrl = resolvedCollection === "bets" ? `${appUrl.replace(/\/$/, "")}/w/${wsCtx.workspaceSlug}/studio/${internalId}` : void 0;
|
|
1328
1770
|
if (studioUrl) {
|
|
1329
1771
|
lines.push("");
|
|
1330
1772
|
lines.push(`**View in Studio:** ${studioUrl}`);
|
|
@@ -1343,16 +1785,21 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
1343
1785
|
for (const r of userLinkResults) lines.push(`- ${r.label}`);
|
|
1344
1786
|
}
|
|
1345
1787
|
if (finalStatus === "committed") {
|
|
1788
|
+
const wasAutoCommitted = autoCommit === void 0 && wsCtx.governanceMode === "open";
|
|
1346
1789
|
lines.push("");
|
|
1347
1790
|
lines.push(`## Committed: ${finalEntryId}`);
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1791
|
+
if (wasAutoCommitted) {
|
|
1792
|
+
lines.push(`**${name}** added to your knowledge base.`);
|
|
1793
|
+
} else {
|
|
1794
|
+
lines.push(`**${name}** promoted to SSOT on the Chain.`);
|
|
1795
|
+
}
|
|
1796
|
+
if (GOVERNED_COLLECTIONS.has(resolvedCollection)) {
|
|
1797
|
+
lines.push(`_Note: \`${resolvedCollection}\` is a governed collection \u2014 ensure this entry has been reviewed._`);
|
|
1351
1798
|
}
|
|
1352
1799
|
} else if (commitError) {
|
|
1353
1800
|
lines.push("");
|
|
1354
1801
|
lines.push("## Commit failed");
|
|
1355
|
-
lines.push(`Error: ${commitError}. Entry
|
|
1802
|
+
lines.push(`Error: ${commitError}. Entry saved as draft \u2014 use \`commit-entry entryId="${finalEntryId}"\` to promote when ready.`);
|
|
1356
1803
|
}
|
|
1357
1804
|
if (linksSuggested.length > 0) {
|
|
1358
1805
|
lines.push("");
|
|
@@ -1370,12 +1817,12 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
1370
1817
|
lines.push("");
|
|
1371
1818
|
lines.push(`_To improve: \`update-entry entryId="${finalEntryId}"\` to fill missing fields._`);
|
|
1372
1819
|
}
|
|
1373
|
-
const isBetOrGoal =
|
|
1820
|
+
const isBetOrGoal = resolvedCollection === "bets" || resolvedCK === "bet" || resolvedCK === "goal";
|
|
1374
1821
|
const hasStrategyLink = linksCreated.some((l) => l.targetCollection === "strategy");
|
|
1375
1822
|
if (isBetOrGoal && !hasStrategyLink) {
|
|
1376
1823
|
lines.push("");
|
|
1377
1824
|
lines.push(
|
|
1378
|
-
`**Strategy link:** This ${
|
|
1825
|
+
`**Strategy link:** This ${resolvedCollection === "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.`
|
|
1379
1826
|
);
|
|
1380
1827
|
await recordSessionActivity({ strategyLinkWarnedForEntryId: internalId });
|
|
1381
1828
|
}
|
|
@@ -1428,11 +1875,12 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
|
|
|
1428
1875
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
1429
1876
|
structuredContent: {
|
|
1430
1877
|
entryId: finalEntryId,
|
|
1431
|
-
collection,
|
|
1878
|
+
collection: resolvedCollection,
|
|
1432
1879
|
name,
|
|
1433
1880
|
status: finalStatus,
|
|
1434
1881
|
qualityScore: quality.score,
|
|
1435
1882
|
qualityVerdict: verdictResult?.verdict ? { ...verdictResult.verdict, source: verdictResult.source ?? "heuristic" } : void 0,
|
|
1883
|
+
...classifierMeta && { classifier: classifierMeta },
|
|
1436
1884
|
...studioUrl && { studioUrl }
|
|
1437
1885
|
}
|
|
1438
1886
|
};
|
|
@@ -1717,11 +2165,13 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
1717
2165
|
"still",
|
|
1718
2166
|
"where"
|
|
1719
2167
|
]);
|
|
2168
|
+
function tokenizeText(input) {
|
|
2169
|
+
return input.toLowerCase().replace(/[^\p{L}\p{N}\s]+/gu, " ").split(/\s+/).filter(Boolean);
|
|
2170
|
+
}
|
|
1720
2171
|
async function runContradictionCheck(name, description) {
|
|
1721
2172
|
const warnings = [];
|
|
1722
2173
|
try {
|
|
1723
|
-
const
|
|
1724
|
-
const keyTerms = text.split(/\s+/).filter((w) => w.length >= 4 && !STOP_WORDS.has(w)).slice(0, 8);
|
|
2174
|
+
const keyTerms = tokenizeText(`${name} ${description}`).filter((w) => w.length >= 4 && !STOP_WORDS.has(w)).slice(0, 8);
|
|
1725
2175
|
if (keyTerms.length === 0) return warnings;
|
|
1726
2176
|
const searchQuery = keyTerms.slice(0, 5).join(" ");
|
|
1727
2177
|
const [govResults, archResults] = await Promise.all([
|
|
@@ -1730,8 +2180,8 @@ async function runContradictionCheck(name, description) {
|
|
|
1730
2180
|
]);
|
|
1731
2181
|
const allGov = [...govResults ?? [], ...archResults ?? []].slice(0, 5);
|
|
1732
2182
|
for (const entry of allGov) {
|
|
1733
|
-
const
|
|
1734
|
-
const matched = keyTerms.filter((t) =>
|
|
2183
|
+
const entryTokens = new Set(tokenizeText(`${entry.name} ${entry.data?.description ?? ""}`));
|
|
2184
|
+
const matched = keyTerms.filter((t) => entryTokens.has(t));
|
|
1735
2185
|
if (matched.length < 3) continue;
|
|
1736
2186
|
let governsCount = 0;
|
|
1737
2187
|
try {
|
|
@@ -1854,10 +2304,16 @@ export {
|
|
|
1854
2304
|
translateStaleToolNames,
|
|
1855
2305
|
initToolSurface,
|
|
1856
2306
|
trackWriteTool,
|
|
2307
|
+
initFeatureFlags,
|
|
2308
|
+
CLASSIFIER_AUTO_ROUTE_THRESHOLD,
|
|
2309
|
+
STARTER_COLLECTIONS,
|
|
2310
|
+
classifyStarterCollection,
|
|
2311
|
+
isClassificationAmbiguous,
|
|
1857
2312
|
formatQualityReport,
|
|
1858
2313
|
checkEntryQuality,
|
|
1859
2314
|
captureSchema,
|
|
1860
2315
|
batchCaptureSchema,
|
|
2316
|
+
captureClassifierSchema,
|
|
1861
2317
|
captureOutputSchema,
|
|
1862
2318
|
batchCaptureOutputSchema,
|
|
1863
2319
|
registerSmartCaptureTools,
|
|
@@ -1865,4 +2321,4 @@ export {
|
|
|
1865
2321
|
formatRubricCoaching,
|
|
1866
2322
|
formatRubricVerdictSection
|
|
1867
2323
|
};
|
|
1868
|
-
//# sourceMappingURL=chunk-
|
|
2324
|
+
//# sourceMappingURL=chunk-M264FY2V.js.map
|