@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.
- package/dist/{chunk-2ZWGQ6PK.js → chunk-MOPOQUJP.js} +35 -1
- package/dist/chunk-MOPOQUJP.js.map +1 -0
- package/dist/{chunk-73AECI6K.js → chunk-UUUJXF2W.js} +94 -43
- package/dist/chunk-UUUJXF2W.js.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/http.js +20 -4
- package/dist/http.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{setup-2JWIZ3QO.js → setup-YYADLH22.js} +2 -2
- package/dist/views/src/graph-constellation/index.html +1 -1
- package/package.json +1 -1
- package/dist/chunk-2ZWGQ6PK.js.map +0 -1
- package/dist/chunk-73AECI6K.js.map +0 -1
- /package/dist/{setup-2JWIZ3QO.js.map → setup-YYADLH22.js.map} +0 -0
|
@@ -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-
|
|
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-
|
|
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(
|
|
647
|
+
function initToolSurface() {
|
|
646
648
|
}
|
|
647
649
|
function trackWriteTool(_tool) {
|
|
648
650
|
}
|
|
649
651
|
|
|
650
652
|
// src/gap-store.ts
|
|
651
|
-
var
|
|
653
|
+
var SESSIONLESS_KEY = "__sessionless__";
|
|
654
|
+
var sessionGaps = /* @__PURE__ */ new Map();
|
|
652
655
|
var DEDUP_WINDOW_MS = 6e4;
|
|
653
|
-
function
|
|
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
|
|
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
|
-
|
|
662
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
10725
|
-
|
|
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
|
-
|
|
10733
|
-
if (
|
|
10734
|
-
|
|
10735
|
-
|
|
10736
|
-
const
|
|
10737
|
-
|
|
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
|
-
|
|
10746
|
-
|
|
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
|
|
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(
|
|
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-
|
|
15452
|
+
//# sourceMappingURL=chunk-UUUJXF2W.js.map
|