@productbrain/mcp 0.0.1-beta.48 → 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;
@@ -1498,15 +1793,14 @@ var captureClassifierSchema = z2.object({
1498
1793
  enabled: z2.boolean(),
1499
1794
  autoRouted: z2.boolean(),
1500
1795
  agrees: z2.boolean(),
1796
+ abstained: z2.boolean(),
1501
1797
  topConfidence: z2.number(),
1502
- // Backward-compatible alias for topConfidence.
1503
1798
  confidence: z2.number(),
1504
1799
  reasons: z2.array(z2.string()),
1505
1800
  candidates: z2.array(
1506
1801
  z2.object({
1507
- collection: z2.enum(STARTER_COLLECTIONS),
1802
+ collection: z2.enum(CLASSIFIABLE_COLLECTIONS),
1508
1803
  signalScore: z2.number(),
1509
- // Backward-compatible alias for signalScore.
1510
1804
  confidence: z2.number()
1511
1805
  })
1512
1806
  ),
@@ -1553,12 +1847,12 @@ function buildClassifierUnknownResult() {
1553
1847
  "Could not infer collection from input.",
1554
1848
  "Provide collection explicitly, or rewrite with clearer intent.",
1555
1849
  [{ tool: "collections", description: "List available collections", parameters: { action: "list" } }],
1556
- { classifier: { enabled: true, autoRouted: false, agrees: true, topConfidence: 0, confidence: 0, reasons: [], candidates: [] } }
1850
+ { classifier: { enabled: true, autoRouted: false, agrees: false, abstained: true, topConfidence: 0, confidence: 0, reasons: [], candidates: [] } }
1557
1851
  )
1558
1852
  };
1559
1853
  }
1560
1854
  function buildProvisionedCollectionSuggestions(candidates) {
1561
- 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.";
1562
1856
  }
1563
1857
  function buildUnsupportedProvisioningResult(classified, provisionedCandidates) {
1564
1858
  const suggestions = buildProvisionedCollectionSuggestions(provisionedCandidates);
@@ -1583,6 +1877,7 @@ Correction path: rerun with explicit \`collection\`.`;
1583
1877
  enabled: true,
1584
1878
  autoRouted: false,
1585
1879
  agrees: false,
1880
+ abstained: false,
1586
1881
  topConfidence: classified.topConfidence,
1587
1882
  confidence: classified.confidence,
1588
1883
  reasons: classified.reasons,
@@ -1616,17 +1911,17 @@ Correction path: if this was close, rerun with your chosen \`collection\`.`;
1616
1911
  )
1617
1912
  };
1618
1913
  }
1619
- async function getProvisionedStarterCollectionCandidates(classified, supportedStarterCollections) {
1914
+ async function getProvisionedCollectionCandidates(classified, supportedCollections) {
1620
1915
  const allCollections = await mcpQuery("chain.listCollections");
1621
- const provisionedStarterCollections = new Set(
1916
+ const provisionedCollections = new Set(
1622
1917
  (allCollections ?? []).map((collection) => collection.slug).filter(
1623
- (slug) => supportedStarterCollections.has(slug)
1918
+ (slug) => supportedCollections.has(slug)
1624
1919
  )
1625
1920
  );
1626
1921
  const provisionedCandidates = classified.candidates.filter(
1627
- (candidate) => provisionedStarterCollections.has(candidate.collection)
1922
+ (candidate) => provisionedCollections.has(candidate.collection)
1628
1923
  );
1629
- return { provisionedStarterCollections, provisionedCandidates };
1924
+ return { provisionedCollections, provisionedCandidates };
1630
1925
  }
1631
1926
  async function resolveCaptureCollection(params) {
1632
1927
  const {
@@ -1634,18 +1929,19 @@ async function resolveCaptureCollection(params) {
1634
1929
  name,
1635
1930
  description,
1636
1931
  classifierFlagOn,
1637
- supportedStarterCollections,
1932
+ supportedCollections,
1638
1933
  workspaceId,
1639
1934
  explicitCollectionProvided
1640
1935
  } = params;
1641
1936
  if (collection) {
1642
- const classified2 = classifyStarterCollection(name, description);
1937
+ const classified2 = classifyCollection(name, description);
1643
1938
  if (classified2) {
1644
1939
  const agrees = classified2.collection === collection;
1645
1940
  const classifierMeta2 = {
1646
1941
  enabled: true,
1647
1942
  autoRouted: false,
1648
1943
  agrees,
1944
+ abstained: false,
1649
1945
  topConfidence: classified2.topConfidence,
1650
1946
  confidence: classified2.confidence,
1651
1947
  reasons: classified2.reasons,
@@ -1673,7 +1969,8 @@ async function resolveCaptureCollection(params) {
1673
1969
  classifierMeta: {
1674
1970
  enabled: true,
1675
1971
  autoRouted: false,
1676
- agrees: true,
1972
+ agrees: false,
1973
+ abstained: true,
1677
1974
  topConfidence: 0,
1678
1975
  confidence: 0,
1679
1976
  reasons: [],
@@ -1685,7 +1982,7 @@ async function resolveCaptureCollection(params) {
1685
1982
  if (!classifierFlagOn) {
1686
1983
  return { earlyResult: buildCollectionRequiredResult() };
1687
1984
  }
1688
- const classified = classifyStarterCollection(name, description);
1985
+ const classified = classifyCollection(name, description);
1689
1986
  if (!classified) {
1690
1987
  trackClassifierTelemetry({
1691
1988
  workspaceId,
@@ -1698,8 +1995,8 @@ async function resolveCaptureCollection(params) {
1698
1995
  });
1699
1996
  return { earlyResult: buildClassifierUnknownResult() };
1700
1997
  }
1701
- const { provisionedStarterCollections, provisionedCandidates } = await getProvisionedStarterCollectionCandidates(classified, supportedStarterCollections);
1702
- if (!provisionedStarterCollections.has(classified.collection)) {
1998
+ const { provisionedCollections, provisionedCandidates } = await getProvisionedCollectionCandidates(classified, supportedCollections);
1999
+ if (!provisionedCollections.has(classified.collection)) {
1703
2000
  trackClassifierTelemetry({
1704
2001
  workspaceId,
1705
2002
  predictedCollection: classified.collection,
@@ -1719,6 +2016,7 @@ async function resolveCaptureCollection(params) {
1719
2016
  enabled: true,
1720
2017
  autoRouted: autoRoute,
1721
2018
  agrees: true,
2019
+ abstained: false,
1722
2020
  topConfidence: classified.topConfidence,
1723
2021
  confidence: classified.confidence,
1724
2022
  reasons: classified.reasons,
@@ -1757,7 +2055,7 @@ var captureSuccessOutputSchema = z2.object({
1757
2055
  entryId: z2.string(),
1758
2056
  collection: z2.string(),
1759
2057
  name: z2.string(),
1760
- status: z2.enum(["draft", "committed"]),
2058
+ status: z2.enum(["draft", "committed", "proposed"]),
1761
2059
  qualityScore: z2.number(),
1762
2060
  qualityVerdict: z2.record(z2.unknown()).optional(),
1763
2061
  classifier: captureClassifierSchema.optional(),
@@ -1786,14 +2084,14 @@ var batchCaptureOutputSchema = z2.object({
1786
2084
  })).optional()
1787
2085
  });
1788
2086
  function registerSmartCaptureTools(server) {
1789
- const supportedStarterCollections = new Set(
1790
- STARTER_COLLECTIONS.filter((slug) => PROFILES.has(slug))
2087
+ const supportedCollections = new Set(
2088
+ CLASSIFIABLE_COLLECTIONS.filter((slug) => PROFILES.has(slug))
1791
2089
  );
1792
2090
  const captureTool = server.registerTool(
1793
2091
  "capture",
1794
2092
  {
1795
2093
  title: "Capture",
1796
- 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.",
1797
2095
  inputSchema: captureSchema.shape,
1798
2096
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
1799
2097
  },
@@ -1811,7 +2109,7 @@ function registerSmartCaptureTools(server) {
1811
2109
  name,
1812
2110
  description,
1813
2111
  classifierFlagOn,
1814
- supportedStarterCollections,
2112
+ supportedCollections,
1815
2113
  workspaceId: wsCtx.workspaceId,
1816
2114
  explicitCollectionProvided
1817
2115
  });
@@ -1895,10 +2193,10 @@ Or use \`collections action=list\` to see available collections.`
1895
2193
  }
1896
2194
  data[profile.descriptionField || "description"] = description;
1897
2195
  const status = "draft";
2196
+ const agentId = getAgentSessionId();
1898
2197
  let finalEntryId;
1899
2198
  let internalId;
1900
2199
  try {
1901
- const agentId = getAgentSessionId();
1902
2200
  const result = await mcpMutation("chain.createEntry", {
1903
2201
  collectionSlug: resolvedCollection,
1904
2202
  entryId: entryId ?? void 0,
@@ -1911,7 +2209,6 @@ Or use \`collections action=list\` to see available collections.`
1911
2209
  });
1912
2210
  internalId = result.docId;
1913
2211
  finalEntryId = result.entryId;
1914
- await recordSessionActivity({ entryCreated: internalId });
1915
2212
  } catch (error) {
1916
2213
  const msg = error instanceof Error ? error.message : String(error);
1917
2214
  if (msg.includes("Duplicate") || msg.includes("already exists")) {
@@ -1967,7 +2264,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
1967
2264
  await mcpMutation("chain.createEntryRelation", {
1968
2265
  fromEntryId: finalEntryId,
1969
2266
  toEntryId: c.entryId,
1970
- type: relationType
2267
+ type: relationType,
2268
+ sessionId: agentId ?? void 0
1971
2269
  });
1972
2270
  linksCreated.push({
1973
2271
  targetEntryId: c.entryId,
@@ -2001,7 +2299,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2001
2299
  await mcpMutation("chain.createEntryRelation", {
2002
2300
  fromEntryId: finalEntryId,
2003
2301
  toEntryId: link.to,
2004
- type: link.type
2302
+ type: link.type,
2303
+ sessionId: agentId ?? void 0
2005
2304
  });
2006
2305
  userLinkResults.push({ label: `\u2713 ${link.type} \u2192 ${link.to}`, ok: true });
2007
2306
  linksCreated.push({
@@ -2082,9 +2381,15 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2082
2381
  let commitError = null;
2083
2382
  if (shouldAutoCommit && finalEntryId) {
2084
2383
  try {
2085
- await mcpMutation("chain.commitEntry", { entryId: finalEntryId });
2086
- finalStatus = "committed";
2087
- 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
+ }
2088
2393
  } catch (e) {
2089
2394
  commitError = e instanceof Error ? e.message : "unknown error";
2090
2395
  }
@@ -2094,10 +2399,12 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2094
2399
  `**${name}** added to \`${resolvedCollection}\` as \`${finalStatus}\``,
2095
2400
  `**Workspace:** ${wsCtx.workspaceSlug} (${wsCtx.workspaceId})`
2096
2401
  ];
2097
- if (classifierMeta && classifierMeta.topConfidence > 0) {
2402
+ if (classifierMeta) {
2098
2403
  lines.push("");
2099
2404
  lines.push("## Classification");
2100
- if (classifierMeta.autoRouted) {
2405
+ if (classifierMeta.abstained) {
2406
+ lines.push(`No classifier coverage for \`${resolvedCollection}\` \u2014 entry accepted as-is.`);
2407
+ } else if (classifierMeta.autoRouted) {
2101
2408
  lines.push(`Auto-routed to \`${resolvedCollection}\` (${classifierMeta.topConfidence}% confidence).`);
2102
2409
  if (classifierMeta.reasons.length > 0) {
2103
2410
  lines.push(`Reason: ${classifierMeta.reasons.join("; ")}.`);
@@ -2107,7 +2414,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2107
2414
  lines.push(`Classifier confirms \`${resolvedCollection}\` (${classifierMeta.topConfidence}% confidence).`);
2108
2415
  } else {
2109
2416
  const suggested = classifierMeta.candidates[0]?.collection ?? "unknown";
2110
- lines.push(`Agent chose \`${resolvedCollection}\`, classifier suggests \`${suggested}\` (${classifierMeta.topConfidence}% confidence).`);
2417
+ lines.push(`Classifier suggests \`${suggested}\` (${classifierMeta.topConfidence}% confidence) \u2014 review classification.`);
2111
2418
  if (classifierMeta.reasons.length > 0) {
2112
2419
  lines.push(`Reason: ${classifierMeta.reasons.join("; ")}.`);
2113
2420
  }
@@ -2147,6 +2454,10 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2147
2454
  if (GOVERNED_COLLECTIONS.has(resolvedCollection)) {
2148
2455
  lines.push(`_Note: \`${resolvedCollection}\` is a governed collection \u2014 ensure this entry has been reviewed._`);
2149
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.`);
2150
2461
  } else if (commitError) {
2151
2462
  lines.push("");
2152
2463
  lines.push("## Commit failed");
@@ -2201,6 +2512,11 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2201
2512
  if (failedChecks.length > 0) {
2202
2513
  lines.push(`2. **Improve quality:** \`update-entry entryId="${eid}"\` \u2014 fill missing fields`);
2203
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
+ }
2204
2520
  } else {
2205
2521
  if (userLinkResults.length === 0) {
2206
2522
  lines.push(`1. **Connect it:** \`graph action=suggest entryId="${eid}"\` \u2014 discover what this should link to`);
@@ -2225,13 +2541,15 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2225
2541
  const next = [];
2226
2542
  if (finalStatus === "committed") {
2227
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 } });
2228
2546
  } else {
2229
2547
  if (userLinkResults.length === 0) {
2230
2548
  next.push({ tool: "graph", description: "Discover links", parameters: { action: "suggest", entryId: finalEntryId } });
2231
2549
  }
2232
2550
  next.push({ tool: "commit-entry", description: "Commit to Chain", parameters: { entryId: finalEntryId } });
2233
2551
  }
2234
- 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.`;
2235
2553
  const toolResult = {
2236
2554
  content: [{ type: "text", text: lines.join("\n") }],
2237
2555
  structuredContent: success(
@@ -2360,7 +2678,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2360
2678
  await mcpMutation("chain.createEntryRelation", {
2361
2679
  fromEntryId: finalEntryId,
2362
2680
  toEntryId: c.entryId,
2363
- type: relationType
2681
+ type: relationType,
2682
+ sessionId: agentId ?? void 0
2364
2683
  });
2365
2684
  autoLinkCount++;
2366
2685
  } catch {
@@ -2370,7 +2689,6 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2370
2689
  }
2371
2690
  }
2372
2691
  results.push({ name: entry.name, collection: entry.collection, entryId: finalEntryId, ok: true, autoLinks: autoLinkCount });
2373
- await recordSessionActivity({ entryCreated: internalId });
2374
2692
  } catch (error) {
2375
2693
  const msg = error instanceof Error ? error.message : String(error);
2376
2694
  results.push({ name: entry.name, collection: entry.collection, entryId: "", ok: false, autoLinks: 0, error: msg });
@@ -2672,15 +2990,20 @@ export {
2672
2990
  requireActiveSession,
2673
2991
  requireWriteAccess,
2674
2992
  recoverSessionState,
2993
+ deriveEpistemicStatus,
2994
+ formatEpistemicLine,
2995
+ toEpistemicInput,
2675
2996
  extractPreview,
2676
2997
  translateStaleToolNames,
2677
2998
  initToolSurface,
2678
2999
  trackWriteTool,
2679
3000
  initFeatureFlags,
2680
3001
  CLASSIFIER_AUTO_ROUTE_THRESHOLD,
3002
+ CLASSIFIABLE_COLLECTIONS,
2681
3003
  STARTER_COLLECTIONS,
2682
- classifyStarterCollection,
3004
+ classifyCollection,
2683
3005
  isClassificationAmbiguous,
3006
+ classifyStarterCollection,
2684
3007
  success,
2685
3008
  failure,
2686
3009
  parseOrFail,
@@ -2690,6 +3013,7 @@ export {
2690
3013
  notFoundResult,
2691
3014
  validationResult,
2692
3015
  withEnvelope,
3016
+ inferStrategyCategory,
2693
3017
  formatQualityReport,
2694
3018
  checkEntryQuality,
2695
3019
  captureSchema,
@@ -2702,4 +3026,4 @@ export {
2702
3026
  formatRubricCoaching,
2703
3027
  formatRubricVerdictSection
2704
3028
  };
2705
- //# sourceMappingURL=chunk-4ZJEIAA6.js.map
3029
+ //# sourceMappingURL=chunk-ED3KCWZE.js.map