@productbrain/mcp 0.0.1-beta.129 → 0.0.1-beta.134

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.
@@ -177,6 +177,38 @@ function trackKnowledgeGap(workspaceId, props) {
177
177
  } catch {
178
178
  }
179
179
  }
180
+ function trackCaptureQualityHints(workspaceId, props) {
181
+ if (!client) return;
182
+ try {
183
+ client.capture({
184
+ distinctId,
185
+ event: "mcp_capture_quality_hints",
186
+ properties: {
187
+ ...props,
188
+ workspace_id: workspaceId,
189
+ source_system: "mcp-server",
190
+ $groups: { workspace: workspaceId }
191
+ }
192
+ });
193
+ } catch {
194
+ }
195
+ }
196
+ function trackCaptureRelationSuggestions(workspaceId, props) {
197
+ if (!client) return;
198
+ try {
199
+ client.capture({
200
+ distinctId,
201
+ event: "mcp_capture_relation_suggestions",
202
+ properties: {
203
+ ...props,
204
+ workspace_id: workspaceId,
205
+ source_system: "mcp-server",
206
+ $groups: { workspace: workspaceId }
207
+ }
208
+ });
209
+ } catch {
210
+ }
211
+ }
180
212
  function getPostHogClient() {
181
213
  return client;
182
214
  }
@@ -269,10 +301,12 @@ export {
269
301
  trackCaptureClassifierFallback,
270
302
  trackChainEntryCommitted,
271
303
  trackKnowledgeGap,
304
+ trackCaptureQualityHints,
305
+ trackCaptureRelationSuggestions,
272
306
  getPostHogClient,
273
307
  shutdownAnalytics,
274
308
  MCP_NPX_PACKAGE,
275
309
  resolveClient,
276
310
  writeClientConfig
277
311
  };
278
- //# sourceMappingURL=chunk-2ZWGQ6PK.js.map
312
+ //# sourceMappingURL=chunk-MOPOQUJP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/analytics.ts","../src/cli/config-writer.ts"],"sourcesContent":["/**\n * PostHog analytics for SynergyOS maintainers — tracks MCP usage (sessions, tool calls).\n * Not user-facing. Key is injected at build time via SYNERGYOS_POSTHOG_KEY.\n * Override with POSTHOG_MCP_KEY for self-hosted deployments.\n */\n\nimport { userInfo } from \"node:os\";\nimport { PostHog } from \"posthog-node\";\n\nlet client: PostHog | null = null;\nlet distinctId = \"anonymous\";\n\nconst POSTHOG_HOST = \"https://eu.i.posthog.com\";\n\n/** Injected at build time: SYNERGYOS_POSTHOG_KEY env when running `npm run build`/publish. */\ndeclare const __SYNERGYOS_POSTHOG_KEY__: string;\n\n/** Only write to stderr when MCP_DEBUG=1 for quieter default DX. */\nfunction log(msg: string): void {\n if (process.env.MCP_DEBUG === \"1\") {\n process.stderr.write(msg);\n }\n}\n\nfunction getBuildTimeKey(): string {\n try {\n return __SYNERGYOS_POSTHOG_KEY__;\n } catch {\n // Not replaced by bundler (e.g. running via tsx in tests) — treat as absent.\n return \"\";\n }\n}\n\nexport function initAnalytics(): void {\n const apiKey = process.env.POSTHOG_MCP_KEY || getBuildTimeKey();\n if (!apiKey) {\n log(\"[MCP-ANALYTICS] No PostHog key — tracking disabled (set SYNERGYOS_POSTHOG_KEY at build time for publish)\\n\");\n return;\n }\n\n client = new PostHog(apiKey, {\n host: POSTHOG_HOST,\n flushAt: 1,\n flushInterval: 5000,\n featureFlagsPollingInterval: 30_000,\n });\n distinctId = process.env.MCP_USER_ID || fallbackDistinctId();\n\n log(`[MCP-ANALYTICS] Initialized — host=${POSTHOG_HOST} distinctId=${distinctId}\\n`);\n}\n\nfunction fallbackDistinctId(): string {\n try {\n return userInfo().username;\n } catch {\n return `os-${process.pid}`;\n }\n}\n\nexport function trackSessionStarted(\n workspaceId: string,\n serverVersion: string,\n): void {\n if (!client) return;\n client.capture({\n distinctId,\n event: \"mcp_session_started\",\n properties: {\n workspace_id: workspaceId,\n server_version: serverVersion,\n source: \"mcp-server\",\n $groups: { workspace: workspaceId },\n },\n });\n}\n\nexport function trackToolCall(\n fn: string,\n status: \"ok\" | \"error\",\n durationMs: number,\n workspaceId: string,\n errorMsg?: string,\n): void {\n const properties: Record<string, unknown> = {\n tool: fn,\n status,\n duration_ms: durationMs,\n workspace_id: workspaceId,\n source: \"mcp-server\",\n $groups: { workspace: workspaceId },\n };\n if (errorMsg) properties.error = errorMsg;\n\n if (!client) return;\n client.capture({\n distinctId,\n event: \"mcp_tool_called\",\n properties,\n });\n}\n\nexport function trackSetupStarted(): void {\n if (!client) return;\n client.capture({\n distinctId,\n event: \"mcp_setup_started\",\n properties: {\n source: \"mcp-server\",\n platform: process.platform,\n },\n });\n}\n\nexport function trackSetupCompleted(\n chosenClient: string,\n outcome: \"config_written\" | \"config_existed\" | \"snippet_shown\" | \"write_error\",\n): void {\n if (!client) return;\n client.capture({\n distinctId,\n event: \"mcp_setup_completed\",\n properties: {\n client: chosenClient,\n outcome,\n source: \"mcp-server\",\n platform: process.platform,\n },\n });\n}\n\nexport function trackQualityVerdict(\n workspaceId: string,\n props: {\n entry_id: string;\n entry_type: string;\n tier: string;\n context: string;\n passed: boolean;\n source: string;\n criteria_total: number;\n criteria_failed: number;\n llm_scheduled: boolean;\n },\n): void {\n if (!client) return;\n client.capture({\n distinctId,\n event: \"quality_verdict_generated\",\n properties: {\n ...props,\n workspace_id: workspaceId,\n source_system: \"mcp-server\",\n $groups: { workspace: workspaceId },\n },\n });\n}\n\nexport function trackQualityCheck(\n workspaceId: string,\n props: {\n entry_id: string;\n entry_type: string;\n tier: string;\n passed: boolean;\n source: string;\n llm_status?: string;\n llm_duration_ms?: number;\n llm_error?: string;\n has_roger_martin: boolean;\n },\n): void {\n if (!client) return;\n client.capture({\n distinctId,\n event: \"quality_verdict_checked\",\n properties: {\n ...props,\n workspace_id: workspaceId,\n source_system: \"mcp-server\",\n $groups: { workspace: workspaceId },\n },\n });\n}\n\nexport type ClassifierReasonCategory =\n | \"auto-routed\"\n | \"low-confidence\"\n | \"ambiguous\"\n | \"non-provisioned\";\n\ntype CaptureClassifierTelemetryProps = {\n predicted_collection: string;\n confidence: number;\n auto_routed: boolean;\n reason_category: ClassifierReasonCategory;\n explicit_collection_provided: boolean;\n};\n\nfunction trackCaptureClassifierEvent(\n event: \"mcp_capture_classifier_evaluated\" | \"mcp_capture_classifier_auto_routed\" | \"mcp_capture_classifier_fallback\",\n workspaceId: string,\n props: CaptureClassifierTelemetryProps,\n): void {\n if (!client) return;\n try {\n client.capture({\n distinctId,\n event,\n properties: {\n ...props,\n workspace_id: workspaceId,\n source_system: \"mcp-server\",\n $groups: { workspace: workspaceId },\n },\n });\n } catch {\n // Analytics are advisory and must never break capture flow.\n }\n}\n\nexport function trackCaptureClassifierEvaluated(\n workspaceId: string,\n props: CaptureClassifierTelemetryProps,\n): void {\n trackCaptureClassifierEvent(\"mcp_capture_classifier_evaluated\", workspaceId, props);\n}\n\nexport function trackCaptureClassifierAutoRouted(\n workspaceId: string,\n props: CaptureClassifierTelemetryProps,\n): void {\n trackCaptureClassifierEvent(\"mcp_capture_classifier_auto_routed\", workspaceId, props);\n}\n\nexport function trackCaptureClassifierFallback(\n workspaceId: string,\n props: CaptureClassifierTelemetryProps,\n): void {\n trackCaptureClassifierEvent(\"mcp_capture_classifier_fallback\", workspaceId, props);\n}\n\n/** GLO-26 / TEN-156: every SSOT commit for PostHog funnels (split auto vs manual). */\nexport function trackChainEntryCommitted(\n workspaceId: string,\n props: {\n entry_id: string;\n collection?: string;\n commit_method: \"auto\" | \"manual\";\n surface:\n | \"mcp_commit_tool\"\n | \"mcp_capture\"\n | \"mcp_wrapup\";\n },\n): void {\n if (!client) return;\n try {\n client.capture({\n distinctId,\n event: \"chain_entry_committed\",\n properties: {\n workspace_id: workspaceId,\n source_system: \"mcp-server\",\n $groups: { workspace: workspaceId },\n ...props,\n },\n });\n } catch {\n // Analytics must never break the tool path.\n }\n}\n\nexport function trackKnowledgeGap(\n workspaceId: string,\n props: {\n query: string;\n tool: string;\n action: string;\n gap_type: \"search_zero\" | \"context_task_empty\" | \"context_entry_isolated\" | \"context_graph_empty\";\n collection_scope?: string;\n },\n): void {\n if (!client) return;\n try {\n client.capture({\n distinctId,\n event: \"knowledge_gap_detected\",\n properties: {\n ...props,\n query: props.query.slice(0, 200),\n workspace_id: workspaceId,\n source_system: \"mcp-server\",\n $groups: { workspace: workspaceId },\n },\n });\n } catch {\n // Analytics must never break the tool response path.\n }\n}\n\n// ── BET-272 S6 / STD-155: Capture intelligence observability ────────────────\n\n/** Fires when formative quality hints are returned at capture time. */\nexport function trackCaptureQualityHints(\n workspaceId: string,\n props: {\n collection: string;\n hint_count: number;\n hint_fields: string[];\n },\n): void {\n if (!client) return;\n try {\n client.capture({\n distinctId,\n event: \"mcp_capture_quality_hints\",\n properties: {\n ...props,\n workspace_id: workspaceId,\n source_system: \"mcp-server\",\n $groups: { workspace: workspaceId },\n },\n });\n } catch {\n // Analytics must never break capture flow.\n }\n}\n\n/** Fires when relation suggestions are returned at capture time. */\nexport function trackCaptureRelationSuggestions(\n workspaceId: string,\n props: {\n collection: string;\n suggestion_count: number;\n relation_types: string[];\n avg_confidence: number;\n },\n): void {\n if (!client) return;\n try {\n client.capture({\n distinctId,\n event: \"mcp_capture_relation_suggestions\",\n properties: {\n ...props,\n workspace_id: workspaceId,\n source_system: \"mcp-server\",\n $groups: { workspace: workspaceId },\n },\n });\n } catch {\n // Analytics must never break capture flow.\n }\n}\n\nexport function getPostHogClient(): PostHog | null {\n return client;\n}\n\nexport async function shutdownAnalytics(): Promise<void> {\n await client?.shutdown();\n}\n","/**\n * Multi-client MCP config detection and writer.\n *\n * Supports:\n * - Cursor: .cursor/mcp.json in cwd (project-level)\n * - Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)\n * %APPDATA%/Claude/claude_desktop_config.json (Windows)\n *\n * The writer reads existing config, merges the new server entry (never\n * overwrites existing entries), and writes back. Falls back to printing\n * a snippet for unsupported OS or unknown formats.\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { homedir, platform } from \"node:os\";\n\nexport interface McpClientInfo {\n name: string;\n configPath: string;\n}\n\nconst SERVER_ENTRY_KEY = \"Product Brain\";\nconst LEGACY_ENTRY_KEY = \"productbrain\";\n\n/**\n * Canonical npx package specifier. Update here when exiting beta.\n * Frontend mirror: src/lib/constants/mcp.ts\n * Business rule: BR-84 (Chain)\n */\nexport const MCP_NPX_PACKAGE = \"@productbrain/mcp@beta\";\n\nfunction buildServerEntry(apiKey: string) {\n return {\n command: \"npx\",\n args: [\"-y\", MCP_NPX_PACKAGE],\n env: { PRODUCTBRAIN_API_KEY: apiKey },\n };\n}\n\n// ── Detection ───────────────────────────────────────────────────────────\n\nfunction getCursorConfigPath(): string {\n return join(process.cwd(), \".cursor\", \"mcp.json\");\n}\n\nfunction getClaudeDesktopConfigPath(): string | null {\n const os = platform();\n if (os === \"darwin\") {\n return join(\n homedir(),\n \"Library\",\n \"Application Support\",\n \"Claude\",\n \"claude_desktop_config.json\",\n );\n }\n if (os === \"win32\") {\n const appData = process.env.APPDATA ?? join(homedir(), \"AppData\", \"Roaming\");\n return join(appData, \"Claude\", \"claude_desktop_config.json\");\n }\n // Linux: no official Claude Desktop location yet\n return null;\n}\n\nexport function resolveClient(name: \"Cursor\" | \"Claude Desktop\"): McpClientInfo | null {\n if (name === \"Cursor\") {\n return { name, configPath: getCursorConfigPath() };\n }\n const configPath = getClaudeDesktopConfigPath();\n return configPath ? { name, configPath } : null;\n}\n\n// ── Writing ─────────────────────────────────────────────────────────────\n\nfunction readJsonSafe(path: string): Record<string, any> {\n if (!existsSync(path)) return {};\n try {\n return JSON.parse(readFileSync(path, \"utf-8\"));\n } catch {\n return {};\n }\n}\n\n/**\n * Write or merge the Product Brain server entry into a client config file.\n * Migrates legacy \"productbrain\" key to \"Product Brain\" when present.\n * Returns true if the config was written, false if already present.\n */\nexport async function writeClientConfig(\n client: McpClientInfo,\n apiKey: string,\n): Promise<boolean> {\n const config = readJsonSafe(client.configPath);\n\n const serversKey = \"mcpServers\";\n if (!config[serversKey]) config[serversKey] = {};\n\n // Migrate legacy \"productbrain\" key or update existing Product Brain with new API key\n if (config[serversKey][LEGACY_ENTRY_KEY]) {\n const legacy = config[serversKey][LEGACY_ENTRY_KEY];\n config[serversKey][SERVER_ENTRY_KEY] = {\n ...buildServerEntry(apiKey),\n env: { ...legacy.env, PRODUCTBRAIN_API_KEY: legacy.env?.PRODUCTBRAIN_API_KEY ?? apiKey },\n };\n delete config[serversKey][LEGACY_ENTRY_KEY];\n } else {\n const existing = config[serversKey][SERVER_ENTRY_KEY];\n config[serversKey][SERVER_ENTRY_KEY] = existing\n ? { ...existing, env: { ...existing.env, PRODUCTBRAIN_API_KEY: apiKey } }\n : buildServerEntry(apiKey);\n }\n\n const dir = dirname(client.configPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(client.configPath, JSON.stringify(config, null, 2) + \"\\n\", \"utf-8\");\n return true;\n}\n"],"mappings":"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","names":["client"]}
@@ -3,12 +3,14 @@ import {
3
3
  trackCaptureClassifierAutoRouted,
4
4
  trackCaptureClassifierEvaluated,
5
5
  trackCaptureClassifierFallback,
6
+ trackCaptureQualityHints,
7
+ trackCaptureRelationSuggestions,
6
8
  trackChainEntryCommitted,
7
9
  trackKnowledgeGap,
8
10
  trackQualityCheck,
9
11
  trackQualityVerdict,
10
12
  trackToolCall
11
- } from "./chunk-2ZWGQ6PK.js";
13
+ } from "./chunk-MOPOQUJP.js";
12
14
 
13
15
  // src/auth.ts
14
16
  import { AsyncLocalStorage } from "async_hooks";
@@ -642,24 +644,38 @@ ${found.map((f) => `- ${f}`).join("\n")}`;
642
644
  }
643
645
 
644
646
  // src/tool-surface.ts
645
- function initToolSurface(_server) {
647
+ function initToolSurface() {
646
648
  }
647
649
  function trackWriteTool(_tool) {
648
650
  }
649
651
 
650
652
  // src/gap-store.ts
651
- var SESSION_GAPS = [];
653
+ var SESSIONLESS_KEY = "__sessionless__";
654
+ var sessionGaps = /* @__PURE__ */ new Map();
652
655
  var DEDUP_WINDOW_MS = 6e4;
653
- function isDuplicate(gap) {
656
+ function currentSessionKey() {
657
+ return getAgentSessionId() ?? SESSIONLESS_KEY;
658
+ }
659
+ function getGapsForSession(sessionKey) {
660
+ let gaps = sessionGaps.get(sessionKey);
661
+ if (!gaps) {
662
+ gaps = [];
663
+ sessionGaps.set(sessionKey, gaps);
664
+ }
665
+ return gaps;
666
+ }
667
+ function isDuplicate(gaps, gap) {
654
668
  const cutoff = gap.timestamp - DEDUP_WINDOW_MS;
655
- return SESSION_GAPS.some(
669
+ return gaps.some(
656
670
  (existing) => existing.query === gap.query && existing.tool === gap.tool && existing.action === gap.action && existing.timestamp >= cutoff
657
671
  );
658
672
  }
659
673
  function recordGap(gap) {
660
674
  const timestamped = { ...gap, timestamp: Date.now() };
661
- if (isDuplicate(timestamped)) return false;
662
- SESSION_GAPS.push(timestamped);
675
+ const sessionKey = currentSessionKey();
676
+ const gaps = getGapsForSession(sessionKey);
677
+ if (isDuplicate(gaps, timestamped)) return false;
678
+ gaps.push(timestamped);
663
679
  persistGap(gap);
664
680
  return true;
665
681
  }
@@ -676,11 +692,12 @@ function persistGap(gap) {
676
692
  });
677
693
  }
678
694
  function getSessionGaps() {
679
- return SESSION_GAPS;
695
+ return getGapsForSession(currentSessionKey());
680
696
  }
681
697
  function getTopGaps(limit = 5) {
698
+ const gaps = getGapsForSession(currentSessionKey());
682
699
  const counts = /* @__PURE__ */ new Map();
683
- for (const gap of SESSION_GAPS) {
700
+ for (const gap of gaps) {
684
701
  const key = gap.query.toLowerCase().trim();
685
702
  const existing = counts.get(key);
686
703
  if (existing) {
@@ -692,7 +709,8 @@ function getTopGaps(limit = 5) {
692
709
  return [...counts.entries()].map(([query, { count, gapType }]) => ({ query, count, gapType })).sort((a, b) => b.count - a.count).slice(0, limit);
693
710
  }
694
711
  function clearSessionGaps() {
695
- SESSION_GAPS.length = 0;
712
+ const sessionKey = currentSessionKey();
713
+ sessionGaps.delete(sessionKey);
696
714
  }
697
715
  function resolveGapsForEntry(entryName, entryId) {
698
716
  mcpMutation("gaps.resolve", { entryName, entryId }).catch(() => {
@@ -2489,6 +2507,8 @@ Or use \`collections action=list\` to see available collections.`
2489
2507
  let finalEntryId;
2490
2508
  let internalId;
2491
2509
  let normalizationMeta;
2510
+ let formativeHints = [];
2511
+ let relationSuggestionsFromCreate = [];
2492
2512
  try {
2493
2513
  const result = await mcpMutation("chain.createEntry", {
2494
2514
  collectionSlug: resolvedCollection,
@@ -2506,6 +2526,8 @@ Or use \`collections action=list\` to see available collections.`
2506
2526
  finalEntryId = result.entryId;
2507
2527
  entryWarnings.push(...result.warnings ?? []);
2508
2528
  normalizationMeta = result.normalization;
2529
+ relationSuggestionsFromCreate = result.relationSuggestions ?? [];
2530
+ formativeHints = result.qualityHints ?? [];
2509
2531
  resolveGapsForEntry(name, result.entryId);
2510
2532
  } catch (error) {
2511
2533
  const msg = error instanceof Error ? error.message : String(error);
@@ -2824,6 +2846,39 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2824
2846
  lines.push("");
2825
2847
  lines.push(`_To improve: \`update-entry entryId="${finalEntryId}"\` to fill missing fields._`);
2826
2848
  }
2849
+ if (formativeHints.length > 0) {
2850
+ lines.push("");
2851
+ lines.push("## Quality Hints (advisory)");
2852
+ for (const hint of formativeHints) {
2853
+ lines.push(`- **${hint.field}:** ${hint.hint}`);
2854
+ }
2855
+ lines.push(`_These hints are advisory \u2014 your entry was captured. Use \`update-entry entryId="${finalEntryId}"\` to address them._`);
2856
+ }
2857
+ if (relationSuggestionsFromCreate.length > 0) {
2858
+ lines.push("");
2859
+ lines.push("## Suggested Relations (advisory)");
2860
+ for (const rs of relationSuggestionsFromCreate) {
2861
+ lines.push(`- **${rs.relationType}** \u2192 ${rs.targetEntryId} "${rs.targetName}" (confidence: ${rs.confidence})`);
2862
+ }
2863
+ lines.push(`_Create with: \`relations action=create fromId="${finalEntryId}" toId="TARGET-ID" type="TYPE"\`_`);
2864
+ }
2865
+ if (formativeHints.length > 0) {
2866
+ trackCaptureQualityHints(wsCtx.workspaceId, {
2867
+ collection: resolvedCollection,
2868
+ hint_count: formativeHints.length,
2869
+ hint_fields: formativeHints.map((h) => h.field)
2870
+ });
2871
+ }
2872
+ if (relationSuggestionsFromCreate.length > 0) {
2873
+ trackCaptureRelationSuggestions(wsCtx.workspaceId, {
2874
+ collection: resolvedCollection,
2875
+ suggestion_count: relationSuggestionsFromCreate.length,
2876
+ relation_types: relationSuggestionsFromCreate.map((rs) => rs.relationType),
2877
+ avg_confidence: Math.round(
2878
+ relationSuggestionsFromCreate.reduce((sum, rs) => sum + rs.confidence, 0) / relationSuggestionsFromCreate.length
2879
+ )
2880
+ });
2881
+ }
2827
2882
  const isBetOrGoal = isBetCapture || resolvedCK === "bet" || resolvedCK === "goal";
2828
2883
  const hasStrategyLink = linksCreated.some((l) => l.targetCollection === "strategy");
2829
2884
  if (isBetOrGoal && !hasStrategyLink) {
@@ -2936,7 +2991,11 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2936
2991
  rejected: normalizationMeta.rejected
2937
2992
  }
2938
2993
  },
2939
- expectedFields
2994
+ expectedFields,
2995
+ // BET-272 S3: Advisory quality hints — always included (empty array when all criteria pass)
2996
+ qualityHints: formativeHints,
2997
+ // BET-272 S5: Advisory relation suggestions from graph (BR-144, STD-147)
2998
+ relationSuggestions: relationSuggestionsFromCreate
2940
2999
  },
2941
3000
  next
2942
3001
  ),
@@ -10696,7 +10755,8 @@ function scoreEntry(entry, keywords) {
10696
10755
  function formatGovernanceEntry(entry) {
10697
10756
  const id = entry.entryId ?? entry.name;
10698
10757
  const desc = entry.description ? ` \u2014 ${entry.description.slice(0, 120)}${entry.description.length > 120 ? "..." : ""}` : "";
10699
- return `- **[${id}]** ${entry.name}${desc}`;
10758
+ const tierLabel = entry.tier === 1 ? " `[direct]`" : entry.tier === 2 ? " `[domain]`" : "";
10759
+ return `- **[${id}]** ${entry.name}${tierLabel}${desc}`;
10700
10760
  }
10701
10761
  function buildOperatingProtocol(governanceOrStandards, task) {
10702
10762
  const governance = Array.isArray(governanceOrStandards) ? { standards: governanceOrStandards } : governanceOrStandards ?? {};
@@ -10721,45 +10781,34 @@ function buildOperatingProtocol(governanceOrStandards, task) {
10721
10781
  { header: "**Active standards:**", entries: standards }
10722
10782
  ];
10723
10783
  if (task) {
10724
- const keywords = extractKeywords(task);
10725
- let anyRelevant = false;
10784
+ const hasTiers = types.some((t) => t.entries.some((e) => e.tier !== void 0));
10785
+ const keywords = hasTiers ? [] : extractKeywords(task);
10726
10786
  let shownCount = 0;
10727
- let totalRelevant = 0;
10728
10787
  let totalGovernance = 0;
10729
10788
  for (const type of types) {
10730
10789
  if (type.entries.length === 0) continue;
10731
10790
  totalGovernance += type.entries.length;
10732
- const scored = type.entries.map((e) => ({ entry: e, score: scoreEntry(e, keywords) })).filter((s) => s.score > 0).sort((a, b) => b.score - a.score);
10733
- if (scored.length > 0) {
10734
- anyRelevant = true;
10735
- totalRelevant += scored.length;
10736
- const capped = scored.slice(0, MAX_ENTRIES_WITH_TASK);
10737
- shownCount += capped.length;
10738
- lines.push(type.header);
10739
- for (const s of capped) {
10740
- lines.push(formatGovernanceEntry(s.entry));
10741
- }
10742
- lines.push("");
10791
+ let sorted;
10792
+ if (hasTiers) {
10793
+ sorted = [...type.entries].sort((a, b) => (a.tier ?? 99) - (b.tier ?? 99));
10794
+ } else {
10795
+ const scored = type.entries.map((e) => ({ entry: e, score: scoreEntry(e, keywords) })).filter((s) => s.score > 0).sort((a, b) => b.score - a.score);
10796
+ sorted = scored.length > 0 ? scored.map((s) => s.entry) : type.entries.slice(0, MAX_ENTRIES_NO_TASK);
10743
10797
  }
10744
- }
10745
- if (!anyRelevant) {
10746
- for (const type of types) {
10747
- if (type.entries.length === 0) continue;
10798
+ const capped = sorted.slice(0, MAX_ENTRIES_WITH_TASK);
10799
+ if (capped.length > 0) {
10800
+ shownCount += capped.length;
10748
10801
  lines.push(type.header);
10749
- for (const e of type.entries.slice(0, MAX_ENTRIES_NO_TASK)) {
10802
+ for (const e of capped) {
10750
10803
  lines.push(formatGovernanceEntry(e));
10751
10804
  }
10752
10805
  lines.push("");
10753
10806
  }
10754
- lines.push(
10755
- `_No governance matched task. Showing top ${MAX_ENTRIES_NO_TASK} per type. Run \`orient\` without a task to see all._`
10756
- );
10757
- } else {
10758
- const overflow = totalRelevant > shownCount ? ` ${totalRelevant - shownCount} more matched \u2014 use \`entries action=search query="..."\` before proposing.` : "";
10759
- lines.push(
10760
- `_${shownCount} of ${totalGovernance} governance entries shown, filtered by task.${overflow}_`
10761
- );
10762
10807
  }
10808
+ const filterNote = hasTiers ? "ranked by relevance" : "filtered by task keywords";
10809
+ lines.push(
10810
+ `_${shownCount} of ${totalGovernance} governance entries shown, ${filterNote}._`
10811
+ );
10763
10812
  } else {
10764
10813
  for (const type of types) {
10765
10814
  if (type.entries.length === 0) continue;
@@ -13475,7 +13524,8 @@ function registerOrientTool(server) {
13475
13524
  const mapGovernanceEntry = (e) => ({
13476
13525
  entryId: e.entryId,
13477
13526
  name: e.name,
13478
- description: typeof e.preview === "string" ? e.preview : void 0
13527
+ description: typeof e.preview === "string" ? e.preview : void 0,
13528
+ tier: typeof e.tier === "number" ? e.tier : void 0
13479
13529
  });
13480
13530
  lines.push(...buildOperatingProtocol({
13481
13531
  principles: (orientEntries.principles ?? []).map(mapGovernanceEntry),
@@ -13758,7 +13808,8 @@ function registerOrientTool(server) {
13758
13808
  const mapGovernanceEntry = (e) => ({
13759
13809
  entryId: e.entryId,
13760
13810
  name: e.name,
13761
- description: typeof e.preview === "string" ? e.preview : void 0
13811
+ description: typeof e.preview === "string" ? e.preview : void 0,
13812
+ tier: typeof e.tier === "number" ? e.tier : void 0
13762
13813
  });
13763
13814
  lines.push(...buildOperatingProtocol({
13764
13815
  principles: (orientEntries.principles ?? []).map(mapGovernanceEntry),
@@ -15353,7 +15404,7 @@ function createProductBrainServer() {
15353
15404
  { name: "Product Brain", version: SERVER_VERSION },
15354
15405
  { capabilities: { logging: {} }, instructions: INSTRUCTIONS }
15355
15406
  );
15356
- initToolSurface(server);
15407
+ initToolSurface();
15357
15408
  const enabledModules = new Set(
15358
15409
  (process.env.PB_MODULES ?? "core,gitchain,arch").split(",").map((m) => m.trim().toLowerCase())
15359
15410
  );
@@ -15398,4 +15449,4 @@ export {
15398
15449
  SERVER_VERSION,
15399
15450
  createProductBrainServer
15400
15451
  };
15401
- //# sourceMappingURL=chunk-73AECI6K.js.map
15452
+ //# sourceMappingURL=chunk-UUUJXF2W.js.map