@productbrain/mcp 0.0.1-beta.1409 → 0.0.1-beta.1419

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.
@@ -1,4 +1,5 @@
1
1
  import {
2
+ cacheScope,
2
3
  closeAgentSession,
3
4
  getAgentSessionId,
4
5
  getApiKeyScope,
@@ -36,7 +37,7 @@ import {
36
37
  trackSessionCaptureRate,
37
38
  trackWriteBackHintServed,
38
39
  trackZeroCaptureAuditFired
39
- } from "./chunk-RJ6V5N5E.js";
40
+ } from "./chunk-Q5IQ5I37.js";
40
41
 
41
42
  // src/server.ts
42
43
  import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -47,6 +48,52 @@ import { z as z3 } from "zod";
47
48
  // src/tools/smart-capture.ts
48
49
  import { z as z2 } from "zod";
49
50
 
51
+ // src/lib/scopedCache.ts
52
+ var ScopedCache = class {
53
+ constructor(ttlMs, maxScopes = 100) {
54
+ this.ttlMs = ttlMs;
55
+ this.maxScopes = maxScopes;
56
+ }
57
+ store = /* @__PURE__ */ new Map();
58
+ get(scope) {
59
+ const e = this.store.get(scope);
60
+ if (!e) return void 0;
61
+ if (Date.now() > e.expiresAt) {
62
+ this.store.delete(scope);
63
+ return void 0;
64
+ }
65
+ return e.value;
66
+ }
67
+ set(scope, value) {
68
+ this.store.set(scope, { value, expiresAt: Date.now() + this.ttlMs });
69
+ if (this.store.size > this.maxScopes) this.evict();
70
+ }
71
+ delete(scope) {
72
+ this.store.delete(scope);
73
+ }
74
+ clear() {
75
+ this.store.clear();
76
+ }
77
+ size() {
78
+ return this.store.size;
79
+ }
80
+ /** Drop expired entries first; if still over budget, drop the oldest-expiring. */
81
+ evict() {
82
+ const now = Date.now();
83
+ for (const [k, e] of this.store) {
84
+ if (now > e.expiresAt) this.store.delete(k);
85
+ }
86
+ if (this.store.size > this.maxScopes) {
87
+ const sorted = [...this.store.entries()].sort((a, b) => a[1].expiresAt - b[1].expiresAt);
88
+ const overBy = sorted.length - this.maxScopes;
89
+ for (let i = 0; i < overBy; i++) {
90
+ this.store.delete(sorted[i][0]);
91
+ }
92
+ }
93
+ }
94
+ };
95
+ var COLLECTION_CACHE_TTL_MS = 6e4;
96
+
50
97
  // src/tools/knowledge-helpers.ts
51
98
  var EPISTEMIC_COLLECTIONS = /* @__PURE__ */ new Set(["insights", "assumptions"]);
52
99
  function deriveEpistemicStatus(entry) {
@@ -548,18 +595,22 @@ function inferSourceDate(...values) {
548
595
  }
549
596
 
550
597
  // src/lib/collectionCache.ts
551
- var cachedCollections = null;
552
- var cachedBySlug = null;
598
+ var collectionCache = new ScopedCache(COLLECTION_CACHE_TTL_MS);
553
599
  async function getCollections() {
554
- if (cachedCollections !== null) return cachedCollections;
555
- const result = await kernelQuery("chain.listCollections");
556
- cachedCollections = result ?? [];
557
- cachedBySlug = new Map(cachedCollections.map((c) => [c.slug, c]));
558
- return cachedCollections;
600
+ const scope = cacheScope();
601
+ const hit = collectionCache.get(scope);
602
+ if (hit) return hit.collections;
603
+ const result = await kernelQuery("chain.listCollections") ?? [];
604
+ const bySlug = new Map(result.map((c) => [c.slug, c]));
605
+ collectionCache.set(scope, { collections: result, bySlug });
606
+ return result;
559
607
  }
560
608
  async function getCollectionBySlug(slug) {
561
609
  await getCollections();
562
- return cachedBySlug.get(slug);
610
+ return collectionCache.get(cacheScope())?.bySlug.get(slug);
611
+ }
612
+ function invalidateCollectionCacheForScope() {
613
+ collectionCache.delete(cacheScope());
563
614
  }
564
615
  async function isGoverned(slug) {
565
616
  const col = await getCollectionBySlug(slug);
@@ -703,28 +754,43 @@ async function buildRuntimeProfile(slug) {
703
754
  qualityChecks
704
755
  };
705
756
  }
706
- var profileCache = null;
757
+ var profileCache = new ScopedCache(COLLECTION_CACHE_TTL_MS);
707
758
  async function getProfile(slug) {
708
- if (!profileCache) profileCache = /* @__PURE__ */ new Map();
709
- const cached = profileCache.get(slug);
759
+ const scope = cacheScope();
760
+ let map = profileCache.get(scope);
761
+ if (!map) {
762
+ map = /* @__PURE__ */ new Map();
763
+ profileCache.set(scope, map);
764
+ }
765
+ const cached = map.get(slug);
710
766
  if (cached) return cached;
711
767
  const profile = await buildRuntimeProfile(slug);
712
- profileCache.set(slug, profile);
768
+ map.set(slug, profile);
713
769
  return profile;
714
770
  }
771
+ function invalidateProfileCacheForScope() {
772
+ profileCache.delete(cacheScope());
773
+ }
715
774
  function extractSearchTerms(name, description) {
716
775
  const text = `${name} ${description}`;
717
776
  return text.replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 3).slice(0, 8).join(" ");
718
777
  }
719
- var hubCollectionCache = null;
778
+ var hubCache = new ScopedCache(COLLECTION_CACHE_TTL_MS);
720
779
  async function populateHubCache() {
721
- if (hubCollectionCache !== null) return;
780
+ const scope = cacheScope();
781
+ const hit = hubCache.get(scope);
782
+ if (hit) return hit;
722
783
  const cols = await getCollections();
723
- hubCollectionCache = new Set(
784
+ const set = new Set(
724
785
  cols.filter((c) => c.governanceRole === "steering" || c.thinkingLayer === "strategy").map((c) => c.slug)
725
786
  );
787
+ hubCache.set(scope, set);
788
+ return set;
726
789
  }
727
- function computeLinkConfidence(candidate, sourceName, sourceDescription, sourceCollection, candidateCollection) {
790
+ function invalidateHubCacheForScope() {
791
+ hubCache.delete(cacheScope());
792
+ }
793
+ function computeLinkConfidence(candidate, sourceName, sourceDescription, sourceCollection, candidateCollection, hubSlugs) {
728
794
  const text = `${sourceName} ${sourceDescription}`.toLowerCase();
729
795
  const candidateName = candidate.name.toLowerCase();
730
796
  let score = 0;
@@ -740,7 +806,7 @@ function computeLinkConfidence(candidate, sourceName, sourceDescription, sourceC
740
806
  if (matchingWords.length > 0) {
741
807
  reasons.push(`word overlap (${matchingWords.slice(0, 3).join(", ")})`);
742
808
  }
743
- if (hubCollectionCache?.has(candidateCollection)) {
809
+ if (hubSlugs.has(candidateCollection)) {
744
810
  score += 15;
745
811
  reasons.push("hub collection");
746
812
  }
@@ -1605,7 +1671,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
1605
1671
  const skipAutoDiscovery = links && links.length > 0;
1606
1672
  const searchQuery = extractSearchTerms(name, description);
1607
1673
  if (searchQuery && !skipAutoDiscovery) {
1608
- await populateHubCache();
1674
+ const hubSlugs = await populateHubCache();
1609
1675
  const [searchResults, allCollections] = await Promise.all([
1610
1676
  kernelQuery("chain.searchEntries", { query: searchQuery }),
1611
1677
  getCollections()
@@ -1613,7 +1679,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
1613
1679
  const collMap = /* @__PURE__ */ new Map();
1614
1680
  for (const c of allCollections) collMap.set(c._id, c.slug);
1615
1681
  const candidates = (searchResults ?? []).filter((r) => r.entryId !== finalEntryId && r._id !== internalId).map((r) => {
1616
- const conf = computeLinkConfidence(r, name, description, resolvedCollection, collMap.get(r.collectionId) ?? "unknown");
1682
+ const conf = computeLinkConfidence(r, name, description, resolvedCollection, collMap.get(r.collectionId) ?? "unknown", hubSlugs);
1617
1683
  return {
1618
1684
  ...r,
1619
1685
  collSlug: collMap.get(r.collectionId) ?? "unknown",
@@ -2145,7 +2211,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2145
2211
  logger: "product-brain"
2146
2212
  });
2147
2213
  const allCollections = await getCollections();
2148
- await populateHubCache();
2214
+ const hubSlugs = await populateHubCache();
2149
2215
  const collCache = /* @__PURE__ */ new Map();
2150
2216
  for (const c of allCollections) collCache.set(c.slug, c);
2151
2217
  const collIdToSlug = /* @__PURE__ */ new Map();
@@ -2325,7 +2391,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2325
2391
  try {
2326
2392
  const searchResults = await kernelQuery("chain.searchEntries", { query: searchQuery });
2327
2393
  const candidates = (searchResults ?? []).filter((r) => r.entryId !== finalEntryId).map((r) => {
2328
- const conf = computeLinkConfidence(r, entry.name, entry.description, resolvedSlug, collIdToSlug.get(r.collectionId) ?? "unknown");
2394
+ const conf = computeLinkConfidence(r, entry.name, entry.description, resolvedSlug, collIdToSlug.get(r.collectionId) ?? "unknown", hubSlugs);
2329
2395
  return { ...r, collSlug: collIdToSlug.get(r.collectionId) ?? "unknown", confidence: conf.score };
2330
2396
  }).sort((a, b) => b.confidence - a.confidence);
2331
2397
  entryOverlapCount = candidates.filter((c) => c.entryId).length;
@@ -5755,6 +5821,9 @@ async function handleCreate2(slug, name, description, purpose, icon, navGroup, f
5755
5821
  ...meta.classificationPriority !== void 0 && { classificationPriority: meta.classificationPriority },
5756
5822
  createdBy: getAgentSessionId() ? `agent:${getAgentSessionId()}` : "mcp"
5757
5823
  });
5824
+ invalidateCollectionCacheForScope();
5825
+ invalidateProfileCacheForScope();
5826
+ invalidateHubCacheForScope();
5758
5827
  const fieldList = fields.map((f) => ` - \`${f.key}\` (${f.type}${f.required ? ", required" : ""}${f.searchable ? ", searchable" : ""})`).join("\n");
5759
5828
  return {
5760
5829
  content: [{
@@ -5819,6 +5888,9 @@ async function handleUpdate(slug, name, description, purpose, icon, navGroup, fi
5819
5888
  ...meta.qualityCriteria !== void 0 && { qualityCriteria: meta.qualityCriteria },
5820
5889
  ...meta.usageGuidance !== void 0 && { usageGuidance: meta.usageGuidance }
5821
5890
  });
5891
+ invalidateCollectionCacheForScope();
5892
+ invalidateProfileCacheForScope();
5893
+ invalidateHubCacheForScope();
5822
5894
  const changes = [name && "name", description && "description", purpose && "purpose", icon && "icon", navGroup && "navGroup", fields && "fields", meta.defaultCanonicalKey !== void 0 && "defaultCanonicalKey", meta.defaultWorkflowStatus !== void 0 && "defaultWorkflowStatus", meta.validWorkflowStatuses !== void 0 && "validWorkflowStatuses", meta.classificationCheck !== void 0 && "classificationCheck", meta.classificationPriority !== void 0 && "classificationPriority", meta.qualityCriteria !== void 0 && "qualityCriteria", meta.usageGuidance !== void 0 && "usageGuidance"].filter(Boolean).join(", ");
5823
5895
  return {
5824
5896
  content: [{
@@ -9551,8 +9623,11 @@ function registerVerifyTools(server) {
9551
9623
  });
9552
9624
  let allEntryIds;
9553
9625
  try {
9554
- const allEntries = await kernelQuery("chain.listEntries", {});
9555
- allEntryIds = new Set(allEntries.map((e) => e.entryId).filter(Boolean));
9626
+ const allEntries = await kernelQuery(
9627
+ "chain.listEntries",
9628
+ { fields: ["entryId"] }
9629
+ );
9630
+ allEntryIds = new Set((allEntries ?? []).map((e) => e.entryId).filter(Boolean));
9556
9631
  } catch {
9557
9632
  allEntryIds = new Set(scopedEntries.map((e) => e.entryId).filter(Boolean));
9558
9633
  }
@@ -9781,7 +9856,9 @@ function buildPlannedWork(allEntries) {
9781
9856
  }
9782
9857
  async function queryPlannedWork() {
9783
9858
  try {
9784
- const allEntries = await kernelQuery("chain.listEntries", {});
9859
+ const allEntries = await kernelQuery("chain.listEntries", {
9860
+ fields: ["_id", "entryId", "name", "status", "workflowStatus", "collectionSlug", "stratum", "canonicalKey"]
9861
+ });
9785
9862
  if (!allEntries) return { uncommittedDrafts: [], inProgressEntries: [], openTensions: [] };
9786
9863
  return buildPlannedWork(allEntries);
9787
9864
  } catch {
@@ -10460,11 +10537,12 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
10460
10537
  lines.push(...formatRecoveryBlock(recoveryBlock));
10461
10538
  }
10462
10539
  try {
10463
- const allEntries = await kernelQuery("chain.listEntries", {});
10464
- const committed = (allEntries ?? []).filter(
10465
- (e) => e.status === "active" && !e.seededByPlatform
10540
+ const allEntries = await kernelQuery(
10541
+ "chain.listEntries",
10542
+ { fields: ["seededByPlatform", "collectionId"] }
10466
10543
  );
10467
- const committedCollections = new Set(committed.map((e) => e.collectionId ?? e.collection));
10544
+ const committed = (allEntries ?? []).filter((e) => !e.seededByPlatform);
10545
+ const committedCollections = new Set(committed.map((e) => e.collectionId));
10468
10546
  if (committed.length >= 10 && committedCollections.size >= 3) {
10469
10547
  lines.push("");
10470
10548
  lines.push(`_${committed.length} entries across ${committedCollections.size} collections on the Chain._`);
@@ -12380,7 +12458,9 @@ async function _handleOrient({ mode = "full", tier, task, scope }) {
12380
12458
  }
12381
12459
  let allEntries = [];
12382
12460
  try {
12383
- allEntries = await kernelQuery("chain.listEntries", {}) ?? [];
12461
+ allEntries = await kernelQuery("chain.listEntries", {
12462
+ fields: ["_id", "entryId", "name", "status", "workflowStatus", "collectionSlug", "stratum", "canonicalKey"]
12463
+ }) ?? [];
12384
12464
  } catch {
12385
12465
  }
12386
12466
  const plannedWork = buildPlannedWork(allEntries);
@@ -12645,11 +12725,14 @@ function formatOrgHealthLines(orgHealth, maxFlags = 3) {
12645
12725
  }
12646
12726
  async function fetchOrganisationHealth() {
12647
12727
  try {
12648
- const allEntries = await kernelQuery("chain.listEntries", { status: "active" });
12728
+ const allEntries = await kernelQuery(
12729
+ "chain.listEntries",
12730
+ { fields: ["name", "description", "collectionSlug"] }
12731
+ );
12649
12732
  if (!allEntries || allEntries.length === 0) return null;
12650
12733
  const classifyInput = allEntries.map((e) => ({
12651
12734
  name: e.name ?? "",
12652
- description: typeof e.data?.description === "string" ? e.data.description : ""
12735
+ description: typeof e.description === "string" ? e.description : ""
12653
12736
  }));
12654
12737
  const classifications = await kernelQuery(
12655
12738
  "chain.batchClassifyHeuristic",
@@ -12680,8 +12763,11 @@ async function handleHealthCheck() {
12680
12763
  let totalEntries = 0;
12681
12764
  if (collections.length > 0) {
12682
12765
  try {
12683
- const entries = await kernelQuery("chain.listEntries", {});
12684
- totalEntries = entries.length;
12766
+ const entries = await kernelQuery(
12767
+ "chain.listEntries",
12768
+ { fields: ["entryId"] }
12769
+ );
12770
+ totalEntries = (entries ?? []).length;
12685
12771
  } catch (e) {
12686
12772
  errors.push(`Entry count failed: ${e instanceof Error ? e.message : String(e)}`);
12687
12773
  }
@@ -14398,4 +14484,4 @@ export {
14398
14484
  createProductBrainServer,
14399
14485
  initFeatureFlags
14400
14486
  };
14401
- //# sourceMappingURL=chunk-UI4E64DX.js.map
14487
+ //# sourceMappingURL=chunk-54VAG6CG.js.map