@productbrain/mcp 0.0.1-beta.47 → 0.0.1-beta.49

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.
@@ -159,22 +159,27 @@ async function startAgentSession() {
159
159
  workspaceId,
160
160
  apiKeyId: s.apiKeyId
161
161
  });
162
+ if (s.agentSessionId) {
163
+ resetTouchThrottle(s.agentSessionId);
164
+ }
162
165
  s.agentSessionId = result.sessionId;
163
166
  s.apiKeyScope = result.toolsScope;
164
167
  s.sessionOriented = false;
165
168
  s.sessionClosed = false;
166
- resetTouchThrottle();
169
+ resetTouchThrottle(result.sessionId);
167
170
  return result;
168
171
  }
169
172
  async function closeAgentSession() {
170
173
  const s = state();
171
174
  if (!s.agentSessionId) return;
175
+ const sessionId = s.agentSessionId;
172
176
  try {
173
177
  await mcpCall("agent.closeSession", {
174
- sessionId: s.agentSessionId,
178
+ sessionId,
175
179
  status: "closed"
176
180
  });
177
181
  } finally {
182
+ resetTouchThrottle(sessionId);
178
183
  s.sessionClosed = true;
179
184
  s.agentSessionId = null;
180
185
  s.sessionOriented = false;
@@ -183,32 +188,40 @@ async function closeAgentSession() {
183
188
  async function orphanAgentSession() {
184
189
  const s = state();
185
190
  if (!s.agentSessionId) return;
191
+ const sessionId = s.agentSessionId;
186
192
  try {
187
193
  await mcpCall("agent.closeSession", {
188
- sessionId: s.agentSessionId,
194
+ sessionId,
189
195
  status: "orphaned"
190
196
  });
191
197
  } catch {
192
198
  } finally {
199
+ resetTouchThrottle(sessionId);
193
200
  s.agentSessionId = null;
194
201
  s.sessionOriented = false;
195
202
  }
196
203
  }
197
- var _lastTouchAt = 0;
204
+ var _lastTouchAtBySession = /* @__PURE__ */ new Map();
198
205
  var TOUCH_THROTTLE_MS = 5e3;
199
206
  function touchSessionActivity() {
200
207
  const s = state();
201
- if (!s.agentSessionId) return;
208
+ const sessionId = s.agentSessionId;
209
+ if (!sessionId) return;
202
210
  const now = Date.now();
203
- if (now - _lastTouchAt < TOUCH_THROTTLE_MS) return;
204
- _lastTouchAt = now;
211
+ const lastTouchAt = _lastTouchAtBySession.get(sessionId) ?? 0;
212
+ if (now - lastTouchAt < TOUCH_THROTTLE_MS) return;
213
+ _lastTouchAtBySession.set(sessionId, now);
205
214
  mcpCall("agent.touchSession", {
206
- sessionId: s.agentSessionId
215
+ sessionId
207
216
  }).catch(() => {
208
217
  });
209
218
  }
210
- function resetTouchThrottle() {
211
- _lastTouchAt = 0;
219
+ function resetTouchThrottle(sessionId) {
220
+ if (sessionId) {
221
+ _lastTouchAtBySession.delete(sessionId);
222
+ return;
223
+ }
224
+ _lastTouchAtBySession.clear();
212
225
  }
213
226
  async function recordSessionActivity(activity) {
214
227
  const s = state();
@@ -302,7 +315,14 @@ async function mcpCall(fn, args = {}) {
302
315
  setCached(fn, args, data);
303
316
  }
304
317
  const s = state();
305
- const TOUCH_EXCLUDED = /* @__PURE__ */ new Set(["agent.touchSession", "agent.startSession", "agent.markOriented"]);
318
+ const TOUCH_EXCLUDED = /* @__PURE__ */ new Set([
319
+ "agent.touchSession",
320
+ "agent.startSession",
321
+ "agent.markOriented",
322
+ "agent.recordActivity",
323
+ "agent.recordWrapup",
324
+ "agent.closeSession"
325
+ ]);
306
326
  if (s.agentSessionId && !TOUCH_EXCLUDED.has(fn)) {
307
327
  touchSessionActivity();
308
328
  }
@@ -428,6 +448,58 @@ async function recoverSessionState() {
428
448
  }
429
449
 
430
450
  // src/tools/knowledge-helpers.ts
451
+ var EPISTEMIC_COLLECTIONS = /* @__PURE__ */ new Set(["insights", "assumptions"]);
452
+ function deriveEpistemicStatus(entry) {
453
+ const coll = entry.collectionName?.toLowerCase();
454
+ if (!coll || !EPISTEMIC_COLLECTIONS.has(coll)) return null;
455
+ const relations = entry.relations ?? [];
456
+ const hasValidates = relations.some(
457
+ (r) => r.type === "validates" && r.direction === "incoming"
458
+ );
459
+ const hasInvalidates = relations.some(
460
+ (r) => r.type === "invalidates" && r.direction === "incoming"
461
+ );
462
+ const evidenceCount = relations.filter(
463
+ (r) => (r.type === "validates" || r.type === "invalidates") && r.direction === "incoming"
464
+ ).length;
465
+ if (coll === "assumptions") {
466
+ const ws2 = entry.workflowStatus;
467
+ if (ws2 === "invalidated" || hasInvalidates) {
468
+ return { level: "invalidated", reason: "Disproved by linked counter-evidence" };
469
+ }
470
+ if (ws2 === "validated" || hasValidates && ws2 !== "untested") {
471
+ return { level: "validated", reason: `${evidenceCount} evidence link${evidenceCount !== 1 ? "s" : ""}` };
472
+ }
473
+ if (ws2 === "testing") {
474
+ return { level: "testing", reason: "Experiment in progress" };
475
+ }
476
+ return { level: "untested", reason: "No evidence linked", action: 'Link evidence via `relations action=create type="validates"`' };
477
+ }
478
+ const ws = entry.workflowStatus;
479
+ if (ws === "validated" && hasValidates) {
480
+ return { level: "validated", reason: `${evidenceCount} evidence link${evidenceCount !== 1 ? "s" : ""}` };
481
+ }
482
+ if (ws === "evidenced" || hasValidates) {
483
+ return { level: "evidenced", reason: `${evidenceCount} evidence link${evidenceCount !== 1 ? "s" : ""}` };
484
+ }
485
+ return {
486
+ level: "hypothesis",
487
+ reason: "No linked evidence",
488
+ action: 'Link proof via `relations action=create type="validates"`'
489
+ };
490
+ }
491
+ function formatEpistemicLine(es) {
492
+ const icon = es.level === "validated" ? "\u2713" : es.level === "evidenced" ? "\u25CE" : es.level === "hypothesis" ? "\u25B3" : es.level === "untested" ? "?" : es.level === "testing" ? "\u25CE" : "\u2715";
493
+ const suffix = es.action ? ` ${es.action}` : "";
494
+ return `**Confidence:** ${icon} ${es.level} \u2014 ${es.reason}.${suffix}`;
495
+ }
496
+ function toEpistemicInput(entry) {
497
+ return {
498
+ collectionName: typeof entry.collectionName === "string" ? entry.collectionName : void 0,
499
+ workflowStatus: typeof entry.workflowStatus === "string" ? entry.workflowStatus : void 0,
500
+ relations: Array.isArray(entry.relations) ? entry.relations.map((r) => ({ type: r.type, direction: r.direction })) : void 0
501
+ };
502
+ }
431
503
  function extractPreview(data, maxLen) {
432
504
  if (!data || typeof data !== "object") return "";
433
505
  const d = data;
@@ -546,11 +618,27 @@ async function isFeatureEnabled(flag, workspaceId, workspaceSlug) {
546
618
  // src/tools/smart-capture-routing.ts
547
619
  var CLASSIFIER_AUTO_ROUTE_THRESHOLD = 70;
548
620
  var CLASSIFIER_AMBIGUITY_MARGIN = 15;
549
- var STARTER_COLLECTIONS = ["decisions", "tensions", "glossary", "insights", "bets"];
621
+ var CLASSIFIABLE_COLLECTIONS = [
622
+ "decisions",
623
+ "tensions",
624
+ "glossary",
625
+ "insights",
626
+ "bets",
627
+ "features",
628
+ "architecture",
629
+ "business-rules",
630
+ "tracking-events",
631
+ "landscape",
632
+ "standards",
633
+ "principles",
634
+ "assumptions"
635
+ ];
636
+ var STARTER_COLLECTIONS = CLASSIFIABLE_COLLECTIONS;
550
637
  var SIGNAL_WEIGHT = 10;
638
+ var MIN_SCORE_FLOOR = 10;
551
639
  var MAX_MATCHES_PER_SIGNAL = 2;
552
640
  var MAX_REASON_COUNT = 3;
553
- var STARTER_COLLECTION_SIGNALS = {
641
+ var COLLECTION_SIGNALS = {
554
642
  decisions: [
555
643
  "decide",
556
644
  "decision",
@@ -560,7 +648,15 @@ var STARTER_COLLECTION_SIGNALS = {
560
648
  "resolved",
561
649
  "we will",
562
650
  "we should",
563
- "approved"
651
+ "approved",
652
+ "replaces",
653
+ "instead of",
654
+ "go with",
655
+ "criteria",
656
+ "adopted",
657
+ "reposition",
658
+ "scoring framework",
659
+ "review"
564
660
  ],
565
661
  tensions: [
566
662
  "problem",
@@ -569,10 +665,20 @@ var STARTER_COLLECTION_SIGNALS = {
569
665
  "blocker",
570
666
  "friction",
571
667
  "pain",
572
- "risk",
573
- "constraint",
574
668
  "bottleneck",
575
- "struggle"
669
+ "struggle",
670
+ "missing",
671
+ "breaks",
672
+ "regression",
673
+ "unclear",
674
+ "no way to",
675
+ "scope creep",
676
+ "coupled",
677
+ "trapped",
678
+ "ambiguous",
679
+ "no batch",
680
+ "undetectable",
681
+ "coordination gap"
576
682
  ],
577
683
  glossary: [
578
684
  "definition",
@@ -582,28 +688,151 @@ var STARTER_COLLECTION_SIGNALS = {
582
688
  "refers to",
583
689
  "is called",
584
690
  "vocabulary",
585
- "terminology"
691
+ "terminology",
692
+ "a governance mechanism",
693
+ "a workspace",
694
+ "a tracked",
695
+ "the atom",
696
+ "the action of",
697
+ "the versioned",
698
+ "a field on",
699
+ "one of the",
700
+ "a constraint on",
701
+ "a hard data",
702
+ "a single"
586
703
  ],
587
704
  insights: [
588
705
  "insight",
589
706
  "learned",
590
707
  "observed",
591
- "pattern",
592
708
  "trend",
593
- "signal",
594
709
  "found that",
595
- "evidence"
710
+ "discovery",
711
+ "validates",
712
+ "saturates",
713
+ "convergence",
714
+ "signals from",
715
+ "converge",
716
+ "TAM"
596
717
  ],
597
718
  bets: [
598
- "bet",
599
719
  "appetite",
600
- "scope",
601
- "elements",
602
720
  "rabbit hole",
603
721
  "no-go",
604
- "shape",
605
- "shaping",
606
- "done when"
722
+ "shape up",
723
+ "done when",
724
+ "shaping session",
725
+ "from static",
726
+ "from storage",
727
+ "from passive",
728
+ "the chain learns",
729
+ "stage-aware",
730
+ "commit friction",
731
+ "interactive knowledge",
732
+ "capture without",
733
+ "front door",
734
+ "response envelope",
735
+ "knowledge organisation",
736
+ "graveyard",
737
+ "remote MCP",
738
+ "coaching service"
739
+ ],
740
+ features: [
741
+ "feature",
742
+ "capability",
743
+ "user can",
744
+ "navigation",
745
+ "palette",
746
+ "modal",
747
+ "smart capture",
748
+ "suggest-links",
749
+ "command palette",
750
+ "auto-commit",
751
+ "collection-optional",
752
+ "organisation intelligence",
753
+ "consolidation"
754
+ ],
755
+ architecture: [
756
+ "architecture",
757
+ "layer",
758
+ "data model",
759
+ "infrastructure",
760
+ "system design",
761
+ "L1",
762
+ "L2",
763
+ "L3",
764
+ "L4",
765
+ "L5",
766
+ "L6",
767
+ "L7",
768
+ "guard infrastructure",
769
+ "data layer",
770
+ "intelligence layer",
771
+ "MCP layer",
772
+ "core layer"
773
+ ],
774
+ "business-rules": [
775
+ "guard",
776
+ "enforce",
777
+ "integrity",
778
+ "prevents",
779
+ "excludes",
780
+ "permitted",
781
+ "policy",
782
+ "feature gate",
783
+ "must not",
784
+ "only permitted",
785
+ "closed enum",
786
+ "write guard",
787
+ "never imports",
788
+ "requires active session",
789
+ "readiness excludes"
790
+ ],
791
+ "tracking-events": [
792
+ "track",
793
+ "tracking",
794
+ "analytics",
795
+ "trigger",
796
+ "posthog",
797
+ "instrument",
798
+ "fires when"
799
+ ],
800
+ landscape: [
801
+ "competitor",
802
+ "alternative tool",
803
+ "alternative platform",
804
+ "competing product",
805
+ "landscape",
806
+ "comparison"
807
+ ],
808
+ standards: [
809
+ "standard",
810
+ "convention",
811
+ "trunk-based",
812
+ "alignment-first",
813
+ "structured bet",
814
+ "system fixes",
815
+ "patches"
816
+ ],
817
+ principles: [
818
+ "we believe",
819
+ "principle",
820
+ "compounds",
821
+ "philosophy",
822
+ "simplicity compounds",
823
+ "trust through",
824
+ "evidence over",
825
+ "compensate for",
826
+ "honest by default"
827
+ ],
828
+ assumptions: [
829
+ "assume",
830
+ "assumption",
831
+ "hypothesis",
832
+ "untested",
833
+ "we think",
834
+ "we assume",
835
+ "needs validation"
607
836
  ]
608
837
  };
609
838
  function escapeRegExp(text) {
@@ -618,11 +847,12 @@ function countSignalMatches(text, signal) {
618
847
  const matches = text.match(regex);
619
848
  return matches?.length ?? 0;
620
849
  }
621
- function classifyStarterCollection(name, description) {
622
- const text = `${name} ${description}`.toLowerCase();
850
+ var ENTRY_ID_PATTERN = /\b[A-Z]{2,}-\d+\b/g;
851
+ function classifyCollection(name, description) {
852
+ const text = `${name} ${description}`.replace(ENTRY_ID_PATTERN, "").toLowerCase();
623
853
  const rawScores = [];
624
- for (const collection of STARTER_COLLECTIONS) {
625
- const signals = STARTER_COLLECTION_SIGNALS[collection];
854
+ for (const collection of CLASSIFIABLE_COLLECTIONS) {
855
+ const signals = COLLECTION_SIGNALS[collection];
626
856
  const reasons = [];
627
857
  let score = 0;
628
858
  for (const signal of signals) {
@@ -640,8 +870,9 @@ function classifyStarterCollection(name, description) {
640
870
  rawScores.sort((a, b) => b.score - a.score);
641
871
  const top = rawScores[0];
642
872
  const second = rawScores[1];
643
- if (!top || top.score <= 0) return null;
873
+ if (!top || top.score < MIN_SCORE_FLOOR) return null;
644
874
  const margin = Math.max(0, top.score - (second?.score ?? 0));
875
+ if (margin === 0 && top.score <= MIN_SCORE_FLOOR) return null;
645
876
  const baseConfidence = Math.min(90, top.score);
646
877
  const confidence = Math.min(99, baseConfidence + Math.min(20, margin));
647
878
  return {
@@ -665,6 +896,7 @@ function shouldAutoRouteClassification(result) {
665
896
  function isClassificationAmbiguous(result) {
666
897
  return result.scoreMargin < CLASSIFIER_AMBIGUITY_MARGIN;
667
898
  }
899
+ var classifyStarterCollection = classifyCollection;
668
900
 
669
901
  // src/envelope.ts
670
902
  import { z } from "zod";
@@ -935,6 +1167,33 @@ var COMMON_CHECKS = {
935
1167
  suggestion: () => "Classify this entry with a canonical type for better context assembly. Use update-entry to set canonicalKey."
936
1168
  }
937
1169
  };
1170
+ var STRATEGY_CATEGORY_INFERENCE_THRESHOLD = 2;
1171
+ var STRATEGY_CATEGORY_SIGNALS = [
1172
+ ["business-model", [/pricing/, /revenue/, /unit economics/, /cost structure/, /monetiz/, /margin/, /per.?seat/, /subscription/, /freemium/]],
1173
+ ["vision", [/vision/, /aspirational/, /future state/, /world where/]],
1174
+ ["purpose", [/purpose/, /why we exist/, /mission/, /reason for being/]],
1175
+ ["goal", [/goal/, /metric/, /target/, /kpi/, /okr/, /critical number/, /measur/]],
1176
+ ["principle", [/we believe/, /guiding principle/, /core belief/, /philosophy/]],
1177
+ ["product-area", [/product area/, /module/, /surface/, /capability area/]],
1178
+ ["audience", [/audience/, /persona/, /user segment/, /icp/, /target market/]],
1179
+ ["insight", [/insight/, /we learned/, /we observed/, /pattern/]],
1180
+ ["opportunity", [/opportunity/, /whitespace/, /gap/, /underserved/]]
1181
+ ];
1182
+ function inferStrategyCategory(name, description) {
1183
+ const text = `${name} ${description}`.toLowerCase();
1184
+ let bestCategory = null;
1185
+ let bestScore = 0;
1186
+ for (const [cat, signals] of STRATEGY_CATEGORY_SIGNALS) {
1187
+ const score = signals.reduce((s, rx) => s + (rx.test(text) ? 1 : 0), 0);
1188
+ if (score > bestScore) {
1189
+ bestScore = score;
1190
+ bestCategory = cat;
1191
+ }
1192
+ }
1193
+ if (bestCategory && bestScore >= STRATEGY_CATEGORY_INFERENCE_THRESHOLD) return bestCategory;
1194
+ if (bestScore === 0) return "strategy";
1195
+ return null;
1196
+ }
938
1197
  var PROFILES = /* @__PURE__ */ new Map([
939
1198
  ["tensions", {
940
1199
  governedDraft: false,
@@ -1150,6 +1409,11 @@ var PROFILES = /* @__PURE__ */ new Map([
1150
1409
  descriptionField: "description",
1151
1410
  defaults: [],
1152
1411
  recommendedRelationTypes: ["informs", "governs", "belongs_to", "related_to"],
1412
+ inferField: (ctx) => {
1413
+ if (ctx.data?.category) return {};
1414
+ const category = inferStrategyCategory(ctx.name, ctx.description);
1415
+ return category ? { category } : {};
1416
+ },
1153
1417
  qualityChecks: [
1154
1418
  COMMON_CHECKS.clearName,
1155
1419
  COMMON_CHECKS.hasDescription,
@@ -1302,6 +1566,36 @@ var PROFILES = /* @__PURE__ */ new Map([
1302
1566
  suggestion: () => "Describe how this assumption could be tested or validated."
1303
1567
  }
1304
1568
  ]
1569
+ }],
1570
+ ["architecture", {
1571
+ governedDraft: true,
1572
+ descriptionField: "description",
1573
+ defaults: [],
1574
+ recommendedRelationTypes: ["belongs_to", "depends_on", "governs", "references", "related_to"],
1575
+ qualityChecks: [
1576
+ COMMON_CHECKS.clearName,
1577
+ COMMON_CHECKS.hasDescription,
1578
+ COMMON_CHECKS.hasRelations,
1579
+ COMMON_CHECKS.hasType,
1580
+ {
1581
+ id: "has-layer",
1582
+ label: "Architecture layer identified",
1583
+ check: (ctx) => !!ctx.data?.layer && String(ctx.data.layer).length > 0,
1584
+ suggestion: () => "Specify which architecture layer this belongs to (L1-L7)."
1585
+ }
1586
+ ]
1587
+ }],
1588
+ ["landscape", {
1589
+ governedDraft: false,
1590
+ descriptionField: "description",
1591
+ defaults: [],
1592
+ recommendedRelationTypes: ["references", "related_to", "fills_slot"],
1593
+ qualityChecks: [
1594
+ COMMON_CHECKS.clearName,
1595
+ COMMON_CHECKS.hasDescription,
1596
+ COMMON_CHECKS.hasRelations,
1597
+ COMMON_CHECKS.hasType
1598
+ ]
1305
1599
  }]
1306
1600
  ]);
1307
1601
  var FALLBACK_PROFILE = {
@@ -1466,7 +1760,8 @@ var GOVERNED_COLLECTIONS = /* @__PURE__ */ new Set([
1466
1760
  "principles",
1467
1761
  "standards",
1468
1762
  "strategy",
1469
- "features"
1763
+ "features",
1764
+ "architecture"
1470
1765
  ]);
1471
1766
  var AUTO_LINK_CONFIDENCE_THRESHOLD = 35;
1472
1767
  var MAX_AUTO_LINKS = 5;
@@ -1497,18 +1792,20 @@ var batchCaptureSchema = z2.object({
1497
1792
  var captureClassifierSchema = z2.object({
1498
1793
  enabled: z2.boolean(),
1499
1794
  autoRouted: z2.boolean(),
1795
+ agrees: z2.boolean(),
1796
+ abstained: z2.boolean(),
1500
1797
  topConfidence: z2.number(),
1501
- // Backward-compatible alias for topConfidence.
1502
1798
  confidence: z2.number(),
1503
1799
  reasons: z2.array(z2.string()),
1504
1800
  candidates: z2.array(
1505
1801
  z2.object({
1506
- collection: z2.enum(STARTER_COLLECTIONS),
1802
+ collection: z2.enum(CLASSIFIABLE_COLLECTIONS),
1507
1803
  signalScore: z2.number(),
1508
- // Backward-compatible alias for signalScore.
1509
1804
  confidence: z2.number()
1510
1805
  })
1511
- )
1806
+ ),
1807
+ agentProvidedCollection: z2.string().optional(),
1808
+ overrideCommand: z2.string().optional()
1512
1809
  });
1513
1810
  function trackClassifierTelemetry(params) {
1514
1811
  const telemetry = {
@@ -1550,12 +1847,12 @@ function buildClassifierUnknownResult() {
1550
1847
  "Could not infer collection from input.",
1551
1848
  "Provide collection explicitly, or rewrite with clearer intent.",
1552
1849
  [{ tool: "collections", description: "List available collections", parameters: { action: "list" } }],
1553
- { classifier: { enabled: true, autoRouted: false, topConfidence: 0, confidence: 0, candidates: [] } }
1850
+ { classifier: { enabled: true, autoRouted: false, agrees: false, abstained: true, topConfidence: 0, confidence: 0, reasons: [], candidates: [] } }
1554
1851
  )
1555
1852
  };
1556
1853
  }
1557
1854
  function buildProvisionedCollectionSuggestions(candidates) {
1558
- return candidates.length ? candidates.map((c) => `- \`${c.collection}\` (${c.signalScore}% signal score)`).join("\n") : "- No provisioned starter collection candidates were inferred confidently.";
1855
+ return candidates.length ? candidates.map((c) => `- \`${c.collection}\` (${c.signalScore}% signal score)`).join("\n") : "- No provisioned collection candidates were inferred confidently.";
1559
1856
  }
1560
1857
  function buildUnsupportedProvisioningResult(classified, provisionedCandidates) {
1561
1858
  const suggestions = buildProvisionedCollectionSuggestions(provisionedCandidates);
@@ -1579,6 +1876,8 @@ Correction path: rerun with explicit \`collection\`.`;
1579
1876
  classifier: {
1580
1877
  enabled: true,
1581
1878
  autoRouted: false,
1879
+ agrees: false,
1880
+ abstained: false,
1582
1881
  topConfidence: classified.topConfidence,
1583
1882
  confidence: classified.confidence,
1584
1883
  reasons: classified.reasons,
@@ -1612,17 +1911,17 @@ Correction path: if this was close, rerun with your chosen \`collection\`.`;
1612
1911
  )
1613
1912
  };
1614
1913
  }
1615
- async function getProvisionedStarterCollectionCandidates(classified, supportedStarterCollections) {
1914
+ async function getProvisionedCollectionCandidates(classified, supportedCollections) {
1616
1915
  const allCollections = await mcpQuery("chain.listCollections");
1617
- const provisionedStarterCollections = new Set(
1916
+ const provisionedCollections = new Set(
1618
1917
  (allCollections ?? []).map((collection) => collection.slug).filter(
1619
- (slug) => supportedStarterCollections.has(slug)
1918
+ (slug) => supportedCollections.has(slug)
1620
1919
  )
1621
1920
  );
1622
1921
  const provisionedCandidates = classified.candidates.filter(
1623
- (candidate) => provisionedStarterCollections.has(candidate.collection)
1922
+ (candidate) => provisionedCollections.has(candidate.collection)
1624
1923
  );
1625
- return { provisionedStarterCollections, provisionedCandidates };
1924
+ return { provisionedCollections, provisionedCandidates };
1626
1925
  }
1627
1926
  async function resolveCaptureCollection(params) {
1628
1927
  const {
@@ -1630,17 +1929,60 @@ async function resolveCaptureCollection(params) {
1630
1929
  name,
1631
1930
  description,
1632
1931
  classifierFlagOn,
1633
- supportedStarterCollections,
1932
+ supportedCollections,
1634
1933
  workspaceId,
1635
1934
  explicitCollectionProvided
1636
1935
  } = params;
1637
1936
  if (collection) {
1638
- return { resolvedCollection: collection };
1937
+ const classified2 = classifyCollection(name, description);
1938
+ if (classified2) {
1939
+ const agrees = classified2.collection === collection;
1940
+ const classifierMeta2 = {
1941
+ enabled: true,
1942
+ autoRouted: false,
1943
+ agrees,
1944
+ abstained: false,
1945
+ topConfidence: classified2.topConfidence,
1946
+ confidence: classified2.confidence,
1947
+ reasons: classified2.reasons,
1948
+ candidates: classified2.candidates.slice(0, 3),
1949
+ agentProvidedCollection: collection,
1950
+ ...!agrees && {
1951
+ overrideCommand: `capture collection="${classified2.collection}"`
1952
+ }
1953
+ };
1954
+ if (!agrees) {
1955
+ trackClassifierTelemetry({
1956
+ workspaceId,
1957
+ predictedCollection: classified2.collection,
1958
+ confidence: classified2.confidence,
1959
+ autoRouted: false,
1960
+ reasonCategory: "low-confidence",
1961
+ explicitCollectionProvided: true,
1962
+ outcome: "fallback"
1963
+ });
1964
+ }
1965
+ return { resolvedCollection: collection, classifierMeta: classifierMeta2 };
1966
+ }
1967
+ return {
1968
+ resolvedCollection: collection,
1969
+ classifierMeta: {
1970
+ enabled: true,
1971
+ autoRouted: false,
1972
+ agrees: false,
1973
+ abstained: true,
1974
+ topConfidence: 0,
1975
+ confidence: 0,
1976
+ reasons: [],
1977
+ candidates: [],
1978
+ agentProvidedCollection: collection
1979
+ }
1980
+ };
1639
1981
  }
1640
1982
  if (!classifierFlagOn) {
1641
1983
  return { earlyResult: buildCollectionRequiredResult() };
1642
1984
  }
1643
- const classified = classifyStarterCollection(name, description);
1985
+ const classified = classifyCollection(name, description);
1644
1986
  if (!classified) {
1645
1987
  trackClassifierTelemetry({
1646
1988
  workspaceId,
@@ -1653,8 +1995,8 @@ async function resolveCaptureCollection(params) {
1653
1995
  });
1654
1996
  return { earlyResult: buildClassifierUnknownResult() };
1655
1997
  }
1656
- const { provisionedStarterCollections, provisionedCandidates } = await getProvisionedStarterCollectionCandidates(classified, supportedStarterCollections);
1657
- if (!provisionedStarterCollections.has(classified.collection)) {
1998
+ const { provisionedCollections, provisionedCandidates } = await getProvisionedCollectionCandidates(classified, supportedCollections);
1999
+ if (!provisionedCollections.has(classified.collection)) {
1658
2000
  trackClassifierTelemetry({
1659
2001
  workspaceId,
1660
2002
  predictedCollection: classified.collection,
@@ -1673,6 +2015,8 @@ async function resolveCaptureCollection(params) {
1673
2015
  const classifierMeta = {
1674
2016
  enabled: true,
1675
2017
  autoRouted: autoRoute,
2018
+ agrees: true,
2019
+ abstained: false,
1676
2020
  topConfidence: classified.topConfidence,
1677
2021
  confidence: classified.confidence,
1678
2022
  reasons: classified.reasons,
@@ -1711,7 +2055,7 @@ var captureSuccessOutputSchema = z2.object({
1711
2055
  entryId: z2.string(),
1712
2056
  collection: z2.string(),
1713
2057
  name: z2.string(),
1714
- status: z2.enum(["draft", "committed"]),
2058
+ status: z2.enum(["draft", "committed", "proposed"]),
1715
2059
  qualityScore: z2.number(),
1716
2060
  qualityVerdict: z2.record(z2.unknown()).optional(),
1717
2061
  classifier: captureClassifierSchema.optional(),
@@ -1740,14 +2084,14 @@ var batchCaptureOutputSchema = z2.object({
1740
2084
  })).optional()
1741
2085
  });
1742
2086
  function registerSmartCaptureTools(server) {
1743
- const supportedStarterCollections = new Set(
1744
- STARTER_COLLECTIONS.filter((slug) => PROFILES.has(slug))
2087
+ const supportedCollections = new Set(
2088
+ CLASSIFIABLE_COLLECTIONS.filter((slug) => PROFILES.has(slug))
1745
2089
  );
1746
2090
  const captureTool = server.registerTool(
1747
2091
  "capture",
1748
2092
  {
1749
2093
  title: "Capture",
1750
- 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, bets, insights, assumptions, principles, 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.",
2094
+ 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, bets, insights, assumptions, principles, 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, architecture) 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.",
1751
2095
  inputSchema: captureSchema.shape,
1752
2096
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
1753
2097
  },
@@ -1765,7 +2109,7 @@ function registerSmartCaptureTools(server) {
1765
2109
  name,
1766
2110
  description,
1767
2111
  classifierFlagOn,
1768
- supportedStarterCollections,
2112
+ supportedCollections,
1769
2113
  workspaceId: wsCtx.workspaceId,
1770
2114
  explicitCollectionProvided
1771
2115
  });
@@ -1849,10 +2193,10 @@ Or use \`collections action=list\` to see available collections.`
1849
2193
  }
1850
2194
  data[profile.descriptionField || "description"] = description;
1851
2195
  const status = "draft";
2196
+ const agentId = getAgentSessionId();
1852
2197
  let finalEntryId;
1853
2198
  let internalId;
1854
2199
  try {
1855
- const agentId = getAgentSessionId();
1856
2200
  const result = await mcpMutation("chain.createEntry", {
1857
2201
  collectionSlug: resolvedCollection,
1858
2202
  entryId: entryId ?? void 0,
@@ -1865,7 +2209,6 @@ Or use \`collections action=list\` to see available collections.`
1865
2209
  });
1866
2210
  internalId = result.docId;
1867
2211
  finalEntryId = result.entryId;
1868
- await recordSessionActivity({ entryCreated: internalId });
1869
2212
  } catch (error) {
1870
2213
  const msg = error instanceof Error ? error.message : String(error);
1871
2214
  if (msg.includes("Duplicate") || msg.includes("already exists")) {
@@ -1921,7 +2264,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
1921
2264
  await mcpMutation("chain.createEntryRelation", {
1922
2265
  fromEntryId: finalEntryId,
1923
2266
  toEntryId: c.entryId,
1924
- type: relationType
2267
+ type: relationType,
2268
+ sessionId: agentId ?? void 0
1925
2269
  });
1926
2270
  linksCreated.push({
1927
2271
  targetEntryId: c.entryId,
@@ -1955,7 +2299,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
1955
2299
  await mcpMutation("chain.createEntryRelation", {
1956
2300
  fromEntryId: finalEntryId,
1957
2301
  toEntryId: link.to,
1958
- type: link.type
2302
+ type: link.type,
2303
+ sessionId: agentId ?? void 0
1959
2304
  });
1960
2305
  userLinkResults.push({ label: `\u2713 ${link.type} \u2192 ${link.to}`, ok: true });
1961
2306
  linksCreated.push({
@@ -2036,9 +2381,15 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2036
2381
  let commitError = null;
2037
2382
  if (shouldAutoCommit && finalEntryId) {
2038
2383
  try {
2039
- await mcpMutation("chain.commitEntry", { entryId: finalEntryId });
2040
- finalStatus = "committed";
2041
- await recordSessionActivity({ entryModified: internalId });
2384
+ const commitResult = await mcpMutation("chain.commitEntry", {
2385
+ entryId: finalEntryId,
2386
+ author: agentId ? `agent:${agentId}` : void 0,
2387
+ sessionId: agentId ?? void 0
2388
+ });
2389
+ finalStatus = commitResult?.status === "proposal_created" ? "proposed" : "committed";
2390
+ if (finalStatus === "committed") {
2391
+ await recordSessionActivity({ entryModified: internalId });
2392
+ }
2042
2393
  } catch (e) {
2043
2394
  commitError = e instanceof Error ? e.message : "unknown error";
2044
2395
  }
@@ -2048,14 +2399,29 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2048
2399
  `**${name}** added to \`${resolvedCollection}\` as \`${finalStatus}\``,
2049
2400
  `**Workspace:** ${wsCtx.workspaceSlug} (${wsCtx.workspaceId})`
2050
2401
  ];
2051
- if (classifierMeta?.autoRouted) {
2402
+ if (classifierMeta) {
2052
2403
  lines.push("");
2053
- lines.push("## Collection routing");
2054
- lines.push(`Auto-routed to \`${resolvedCollection}\` (${classifierMeta.topConfidence}% top confidence).`);
2055
- if (classifierMeta.reasons.length > 0) {
2056
- lines.push(`Reason: ${classifierMeta.reasons.join("; ")}.`);
2404
+ lines.push("## Classification");
2405
+ if (classifierMeta.abstained) {
2406
+ lines.push(`No classifier coverage for \`${resolvedCollection}\` \u2014 entry accepted as-is.`);
2407
+ } else if (classifierMeta.autoRouted) {
2408
+ lines.push(`Auto-routed to \`${resolvedCollection}\` (${classifierMeta.topConfidence}% confidence).`);
2409
+ if (classifierMeta.reasons.length > 0) {
2410
+ lines.push(`Reason: ${classifierMeta.reasons.join("; ")}.`);
2411
+ }
2412
+ lines.push(`Override: rerun with explicit \`collection\` if wrong.`);
2413
+ } else if (classifierMeta.agrees) {
2414
+ lines.push(`Classifier confirms \`${resolvedCollection}\` (${classifierMeta.topConfidence}% confidence).`);
2415
+ } else {
2416
+ const suggested = classifierMeta.candidates[0]?.collection ?? "unknown";
2417
+ lines.push(`Classifier suggests \`${suggested}\` (${classifierMeta.topConfidence}% confidence) \u2014 review classification.`);
2418
+ if (classifierMeta.reasons.length > 0) {
2419
+ lines.push(`Reason: ${classifierMeta.reasons.join("; ")}.`);
2420
+ }
2421
+ if (classifierMeta.overrideCommand) {
2422
+ lines.push(`Override: \`${classifierMeta.overrideCommand}\``);
2423
+ }
2057
2424
  }
2058
- lines.push("Correction path: rerun capture with explicit `collection` if this routing is wrong.");
2059
2425
  }
2060
2426
  const appUrl = process.env.PRODUCTBRAIN_APP_URL ?? "https://productbrain.io";
2061
2427
  const studioUrl = resolvedCollection === "bets" ? `${appUrl.replace(/\/$/, "")}/w/${wsCtx.workspaceSlug}/studio/${internalId}` : void 0;
@@ -2088,6 +2454,10 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2088
2454
  if (GOVERNED_COLLECTIONS.has(resolvedCollection)) {
2089
2455
  lines.push(`_Note: \`${resolvedCollection}\` is a governed collection \u2014 ensure this entry has been reviewed._`);
2090
2456
  }
2457
+ } else if (finalStatus === "proposed") {
2458
+ lines.push("");
2459
+ lines.push(`## Proposal created: ${finalEntryId}`);
2460
+ lines.push(`**${name}** requires consent before it can be committed, so a proposal was created instead of publishing directly.`);
2091
2461
  } else if (commitError) {
2092
2462
  lines.push("");
2093
2463
  lines.push("## Commit failed");
@@ -2142,6 +2512,11 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2142
2512
  if (failedChecks.length > 0) {
2143
2513
  lines.push(`2. **Improve quality:** \`update-entry entryId="${eid}"\` \u2014 fill missing fields`);
2144
2514
  }
2515
+ } else if (finalStatus === "proposed") {
2516
+ lines.push(`1. **Connect it:** \`graph action=suggest entryId="${eid}"\` \u2014 discover additional links to support the proposal`);
2517
+ if (failedChecks.length > 0) {
2518
+ lines.push(`2. **Improve quality:** \`update-entry entryId="${eid}"\` \u2014 strengthen the entry before approval`);
2519
+ }
2145
2520
  } else {
2146
2521
  if (userLinkResults.length === 0) {
2147
2522
  lines.push(`1. **Connect it:** \`graph action=suggest entryId="${eid}"\` \u2014 discover what this should link to`);
@@ -2166,13 +2541,15 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2166
2541
  const next = [];
2167
2542
  if (finalStatus === "committed") {
2168
2543
  next.push({ tool: "graph", description: "Discover connections", parameters: { action: "suggest", entryId: finalEntryId } });
2544
+ } else if (finalStatus === "proposed") {
2545
+ next.push({ tool: "graph", description: "Discover connections", parameters: { action: "suggest", entryId: finalEntryId } });
2169
2546
  } else {
2170
2547
  if (userLinkResults.length === 0) {
2171
2548
  next.push({ tool: "graph", description: "Discover links", parameters: { action: "suggest", entryId: finalEntryId } });
2172
2549
  }
2173
2550
  next.push({ tool: "commit-entry", description: "Commit to Chain", parameters: { entryId: finalEntryId } });
2174
2551
  }
2175
- const summary = finalStatus === "committed" ? `Captured and committed ${finalEntryId} (${name}) to ${resolvedCollection}. Quality ${quality.score}/10.` : `Captured ${finalEntryId} (${name}) as draft in ${resolvedCollection}. Quality ${quality.score}/10.`;
2552
+ const summary = finalStatus === "committed" ? `Captured and committed ${finalEntryId} (${name}) to ${resolvedCollection}. Quality ${quality.score}/10.` : finalStatus === "proposed" ? `Captured ${finalEntryId} (${name}) in ${resolvedCollection} and created a proposal for commit. Quality ${quality.score}/10.` : `Captured ${finalEntryId} (${name}) as draft in ${resolvedCollection}. Quality ${quality.score}/10.`;
2176
2553
  const toolResult = {
2177
2554
  content: [{ type: "text", text: lines.join("\n") }],
2178
2555
  structuredContent: success(
@@ -2301,7 +2678,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2301
2678
  await mcpMutation("chain.createEntryRelation", {
2302
2679
  fromEntryId: finalEntryId,
2303
2680
  toEntryId: c.entryId,
2304
- type: relationType
2681
+ type: relationType,
2682
+ sessionId: agentId ?? void 0
2305
2683
  });
2306
2684
  autoLinkCount++;
2307
2685
  } catch {
@@ -2311,7 +2689,6 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2311
2689
  }
2312
2690
  }
2313
2691
  results.push({ name: entry.name, collection: entry.collection, entryId: finalEntryId, ok: true, autoLinks: autoLinkCount });
2314
- await recordSessionActivity({ entryCreated: internalId });
2315
2692
  } catch (error) {
2316
2693
  const msg = error instanceof Error ? error.message : String(error);
2317
2694
  results.push({ name: entry.name, collection: entry.collection, entryId: "", ok: false, autoLinks: 0, error: msg });
@@ -2613,15 +2990,20 @@ export {
2613
2990
  requireActiveSession,
2614
2991
  requireWriteAccess,
2615
2992
  recoverSessionState,
2993
+ deriveEpistemicStatus,
2994
+ formatEpistemicLine,
2995
+ toEpistemicInput,
2616
2996
  extractPreview,
2617
2997
  translateStaleToolNames,
2618
2998
  initToolSurface,
2619
2999
  trackWriteTool,
2620
3000
  initFeatureFlags,
2621
3001
  CLASSIFIER_AUTO_ROUTE_THRESHOLD,
3002
+ CLASSIFIABLE_COLLECTIONS,
2622
3003
  STARTER_COLLECTIONS,
2623
- classifyStarterCollection,
3004
+ classifyCollection,
2624
3005
  isClassificationAmbiguous,
3006
+ classifyStarterCollection,
2625
3007
  success,
2626
3008
  failure,
2627
3009
  parseOrFail,
@@ -2631,6 +3013,7 @@ export {
2631
3013
  notFoundResult,
2632
3014
  validationResult,
2633
3015
  withEnvelope,
3016
+ inferStrategyCategory,
2634
3017
  formatQualityReport,
2635
3018
  checkEntryQuality,
2636
3019
  captureSchema,
@@ -2643,4 +3026,4 @@ export {
2643
3026
  formatRubricCoaching,
2644
3027
  formatRubricVerdictSection
2645
3028
  };
2646
- //# sourceMappingURL=chunk-FYFF4QKF.js.map
3029
+ //# sourceMappingURL=chunk-ED3KCWZE.js.map