@productbrain/mcp 0.0.1-beta.160 → 0.0.1-beta.162

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.
@@ -1838,7 +1838,15 @@ var captureSchema = z2.object({
1838
1838
  sourceRef: z2.string().optional().describe("URI or path of the source document backing this entry (e.g. 'meeting-2026-03-28.md', 'import://batch-5'). Stored as top-level entry field, not in data."),
1839
1839
  sourceExcerpt: z2.string().optional().describe("Verbatim excerpt from the source that backs this entry's claims. Stored as top-level entry field, not in data."),
1840
1840
  // WP-316 S3: Preview gate — dry-run mode. Returns what would happen, no DB writes.
1841
- preview: z2.boolean().optional().describe("If true, validates the capture without writing. Returns what would happen. Default false.")
1841
+ preview: z2.boolean().optional().describe("If true, validates the capture without writing. Returns what would happen. Default false."),
1842
+ // WP-318 S2: Pre-write grounding — run link suggestion before creating entry.
1843
+ suggestOnly: z2.boolean().optional().describe(
1844
+ "If true, runs pre-write grounding (suggestLinksForCapture) and returns a groundingReport WITHOUT creating any entry. Use to preview graph participation before committing. Default false."
1845
+ ),
1846
+ // WP-318 S2: Format for groundingReport in response.
1847
+ format: z2.enum(["agent", "human"]).optional().describe(
1848
+ "Response format for grounding data. 'agent' (default): full JSON groundingReport. 'human': compressed summary string appended to the capture summary."
1849
+ )
1842
1850
  });
1843
1851
  var batchCaptureSchema = z2.object({
1844
1852
  entries: z2.array(z2.object({
@@ -2266,7 +2274,7 @@ function registerSmartCaptureTools(server) {
2266
2274
  inputSchema: captureSchema.shape,
2267
2275
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2268
2276
  },
2269
- withEnvelope(async ({ collection, name, description, context, entryId, canonicalKey, data: userData, links, autoCommit, sourceRef, sourceExcerpt, preview }) => {
2277
+ withEnvelope(async ({ collection, name, description, context, entryId, canonicalKey, data: userData, links, autoCommit, sourceRef, sourceExcerpt, preview, suggestOnly, format }) => {
2270
2278
  requireWriteAccess();
2271
2279
  const timingStart = Date.now();
2272
2280
  const wsCtx = await getWorkspaceContext();
@@ -2341,6 +2349,71 @@ Or use \`collections action=list\` to see available collections.`
2341
2349
  )
2342
2350
  };
2343
2351
  }
2352
+ const skipAutoDiscoveryEarly = links && links.length > 0;
2353
+ const groundingStart = Date.now();
2354
+ const groundingPromise = !skipAutoDiscoveryEarly && (name || description) ? mcpQuery("chain.suggestLinksForCapture", {
2355
+ entries: [{ name, description: description ?? "", collectionHint: resolvedCollection }],
2356
+ limit: 5,
2357
+ threshold: 2
2358
+ }).catch(() => null) : Promise.resolve(null);
2359
+ const groundingRaw = await groundingPromise;
2360
+ const groundingElapsedMs = Date.now() - groundingStart;
2361
+ const groundingEntry = groundingRaw?.[0] ?? null;
2362
+ const groundingRelated = groundingEntry?.related ?? [];
2363
+ const groundingDuplicates = groundingEntry?.duplicates ?? [];
2364
+ const groundingGovernance = (groundingEntry?.governance ?? []).map((g) => ({
2365
+ entryId: g.entryId,
2366
+ name: g.name,
2367
+ collectionSlug: g.collectionSlug
2368
+ }));
2369
+ const groundingReport = {
2370
+ related: groundingRelated.map((r) => ({
2371
+ entryId: r.entryId,
2372
+ name: r.name,
2373
+ collectionSlug: r.collectionSlug,
2374
+ overlapRatio: r.overlapRatio,
2375
+ recommendedRelationType: r.recommendedRelationType,
2376
+ reasoning: r.reasoning
2377
+ })),
2378
+ duplicates: groundingDuplicates.map((d) => ({
2379
+ entryId: d.entryId,
2380
+ name: d.name,
2381
+ collectionSlug: d.collectionSlug,
2382
+ matchType: d.matchType,
2383
+ overlapRatio: d.overlapRatio
2384
+ })),
2385
+ governance: groundingGovernance
2386
+ };
2387
+ if (suggestOnly) {
2388
+ const hasSuggestions = groundingReport.related.length > 0 || groundingReport.duplicates.length > 0;
2389
+ const summaryText = hasSuggestions ? `Grounding preview for "${name}": ${groundingReport.related.length} related, ${groundingReport.duplicates.length} possible duplicates. No entry created.` : `Grounding preview for "${name}": no similar entries found. Safe to capture.`;
2390
+ return {
2391
+ content: [{
2392
+ type: "text",
2393
+ text: `# Grounding Preview \u2014 No Entry Created
2394
+
2395
+ ${summaryText}
2396
+
2397
+ ` + (groundingReport.related.length > 0 ? `## Related entries
2398
+ ${groundingReport.related.map((r) => `- **${r.entryId}** ${r.name} [${r.collectionSlug}] \u2014 ${r.reasoning}`).join("\n")}
2399
+
2400
+ ` : "") + (groundingReport.duplicates.length > 0 ? `## Possible duplicates
2401
+ ${groundingReport.duplicates.map((d) => `- **${d.entryId}** ${d.name} [${d.collectionSlug}] (${d.matchType}, overlap: ${d.overlapRatio})`).join("\n")}
2402
+
2403
+ ` : "") + (groundingReport.governance.length > 0 ? `## Governance entries to review
2404
+ ${groundingReport.governance.map((g) => `- **${g.entryId}** ${g.name} [${g.collectionSlug}]`).join("\n")}
2405
+
2406
+ ` : "") + `_Call \`capture\` without \`suggestOnly\` to proceed with entry creation._`
2407
+ }],
2408
+ structuredContent: {
2409
+ ...success(
2410
+ summaryText,
2411
+ { groundingReport, outcome: "suggest_only" },
2412
+ [{ tool: "capture", description: "Capture for real", parameters: { collection: resolvedCollection, name, description } }]
2413
+ )
2414
+ }
2415
+ };
2416
+ }
2344
2417
  const data = buildDataFromFields(col.fields ?? [], profile.descriptionField, description);
2345
2418
  for (const def of profile.defaults) {
2346
2419
  if (def.value !== "infer" && def.value !== "today") {
@@ -2429,6 +2502,14 @@ Or use \`collections action=list\` to see available collections.`
2429
2502
  ...preview ? { preview: true } : {}
2430
2503
  });
2431
2504
  if (result?.preview) {
2505
+ const hasGrounding = groundingReport.related.length > 0 || groundingReport.duplicates.length > 0 || groundingReport.governance.length > 0;
2506
+ const groundingSummary = hasGrounding ? `
2507
+
2508
+ **Grounding:** ${[
2509
+ groundingReport.duplicates.length > 0 && `${groundingReport.duplicates.length} possible duplicate${groundingReport.duplicates.length > 1 ? "s" : ""}`,
2510
+ groundingReport.related.length > 0 && `${groundingReport.related.length} related entr${groundingReport.related.length > 1 ? "ies" : "y"}`,
2511
+ groundingReport.governance.length > 0 && `${groundingReport.governance[0]?.entryId} may govern this`
2512
+ ].filter(Boolean).join(", ")}.` : "";
2432
2513
  const previewEnvelope = success(
2433
2514
  `Preview: would capture "${name}" \u2014 no DB writes`,
2434
2515
  {
@@ -2436,7 +2517,8 @@ Or use \`collections action=list\` to see available collections.`
2436
2517
  name,
2437
2518
  collection: resolvedCollection,
2438
2519
  outcome: "preview",
2439
- warnings: result.warnings ?? []
2520
+ warnings: result.warnings ?? [],
2521
+ groundingReport
2440
2522
  },
2441
2523
  [{ tool: "capture", description: "Capture for real", parameters: { collection: resolvedCollection, name, description } }]
2442
2524
  );
@@ -2448,7 +2530,7 @@ Or use \`collections action=list\` to see available collections.`
2448
2530
 
2449
2531
  No DB writes \u2014 call without \`preview:true\` to capture for real.${result.warnings?.length ? `
2450
2532
 
2451
- **Warnings:** ${result.warnings.join("; ")}` : ""}` }],
2533
+ **Warnings:** ${result.warnings.join("; ")}` : ""}${groundingSummary}` }],
2452
2534
  structuredContent: previewEnvelope
2453
2535
  };
2454
2536
  }
@@ -2485,6 +2567,17 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2485
2567
  throw error;
2486
2568
  }
2487
2569
  const tAfterCreate = Date.now();
2570
+ void mcpMutation("chain.recordGroundingOutcome", {
2571
+ surface: "mcp",
2572
+ promptShown: groundingReport.governance.length > 0,
2573
+ highestRatio: Math.max(
2574
+ ...groundingReport.duplicates.map((d) => d.overlapRatio),
2575
+ ...groundingReport.related.map((r) => r.overlapRatio),
2576
+ 0
2577
+ ),
2578
+ hadGovernance: groundingReport.governance.length > 0,
2579
+ grounding_ms: groundingElapsedMs
2580
+ }).catch(() => void 0);
2488
2581
  const linksCreated = [];
2489
2582
  const linksSuggested = [];
2490
2583
  const userLinkResults = [];
@@ -2770,6 +2863,19 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2770
2863
  lines.push(`${i + 1}. **${s.entryId ?? "(no ID)"}**: ${s.name} [${s.collection}]${preview2}`);
2771
2864
  }
2772
2865
  }
2866
+ if (format === "human") {
2867
+ const hasDuplicates = groundingReport.duplicates.length > 0;
2868
+ const hasRelated = groundingReport.related.length > 0;
2869
+ const hasGov = groundingReport.governance.length > 0;
2870
+ if (hasDuplicates || hasRelated || hasGov) {
2871
+ lines.push("");
2872
+ const parts = [];
2873
+ if (hasDuplicates) parts.push(`${groundingReport.duplicates.length} possible duplicate${groundingReport.duplicates.length > 1 ? "s" : ""}`);
2874
+ if (hasRelated) parts.push(`${groundingReport.related.length} related entr${groundingReport.related.length > 1 ? "ies" : "y"}`);
2875
+ if (hasGov) parts.push(`${groundingReport.governance[0]?.entryId} may govern this`);
2876
+ lines.push(`_Grounding: ${parts.join(", ")}. Review and link if relevant._`);
2877
+ }
2878
+ }
2773
2879
  lines.push("");
2774
2880
  lines.push(formatQualityReport(quality));
2775
2881
  const failedChecks = quality.checks.filter((c) => !c.passed);
@@ -2946,7 +3052,9 @@ Use \`entries action=get\` to inspect the existing entry, or \`update-entry\` to
2946
3052
  // BET-272 S3: Advisory quality hints — always included (empty array when all criteria pass)
2947
3053
  qualityHints: formativeHints,
2948
3054
  // BET-272 S5: Advisory relation suggestions from graph (BR-144, STD-147)
2949
- relationSuggestions: relationSuggestionsFromCreate
3055
+ relationSuggestions: relationSuggestionsFromCreate,
3056
+ // WP-318 S2: Pre-write grounding report — always included (empty arrays when no suggestions)
3057
+ groundingReport
2950
3058
  },
2951
3059
  next
2952
3060
  ),
@@ -11114,7 +11222,16 @@ async function tryMarkOriented(agentSessionId, coherenceSnapshot) {
11114
11222
  setSessionOriented(true);
11115
11223
  return { oriented: true, orientationStatus: "complete" };
11116
11224
  } catch {
11117
- return { oriented: false, orientationStatus: "failed" };
11225
+ if (!coherenceSnapshot) {
11226
+ return { oriented: false, orientationStatus: "failed" };
11227
+ }
11228
+ try {
11229
+ await mcpCall("agent.markOriented", { sessionId: agentSessionId });
11230
+ setSessionOriented(true);
11231
+ return { oriented: true, orientationStatus: "complete" };
11232
+ } catch {
11233
+ return { oriented: false, orientationStatus: "failed" };
11234
+ }
11118
11235
  }
11119
11236
  }
11120
11237
  var startSchema = z17.object({
@@ -13361,6 +13478,25 @@ function formatScanReport(result) {
13361
13478
  // src/tools/orient.ts
13362
13479
  import { z as z22 } from "zod";
13363
13480
  var PURPOSE_GAP_PREFIX = "purpose-gap-";
13481
+ async function markOrientedWithSnapshotFallback(agentSessionId, coherenceSnapshot) {
13482
+ try {
13483
+ await mcpCall("agent.markOriented", {
13484
+ sessionId: agentSessionId,
13485
+ ...coherenceSnapshot ? { coherenceSnapshot } : {}
13486
+ });
13487
+ setSessionOriented(true);
13488
+ return true;
13489
+ } catch {
13490
+ if (!coherenceSnapshot) return false;
13491
+ try {
13492
+ await mcpCall("agent.markOriented", { sessionId: agentSessionId });
13493
+ setSessionOriented(true);
13494
+ return true;
13495
+ } catch {
13496
+ return false;
13497
+ }
13498
+ }
13499
+ }
13364
13500
  function extractSessionEntryIds(priorSessions) {
13365
13501
  const allSeen = /* @__PURE__ */ new Set();
13366
13502
  const all = [];
@@ -13648,16 +13784,12 @@ function registerOrientTool(server) {
13648
13784
  }
13649
13785
  let orientationStatus2 = "no_session";
13650
13786
  if (agentSessionId && hasTaskGrounding) {
13651
- try {
13652
- await mcpCall("agent.markOriented", {
13653
- sessionId: agentSessionId,
13654
- ...coherenceSnapshot ? { coherenceSnapshot } : {}
13655
- });
13656
- setSessionOriented(true);
13787
+ const orientedOk = await markOrientedWithSnapshotFallback(agentSessionId, coherenceSnapshot);
13788
+ if (orientedOk) {
13657
13789
  orientationStatus2 = "complete";
13658
13790
  lines.push("---");
13659
13791
  lines.push(`Orientation complete. Session ${agentSessionId}. Write tools available.`);
13660
- } catch {
13792
+ } else {
13661
13793
  orientationStatus2 = "failed";
13662
13794
  lines.push("---");
13663
13795
  lines.push("_Warning: Could not mark session as oriented. Write tools may be restricted._");
@@ -14019,16 +14151,12 @@ function registerOrientTool(server) {
14019
14151
  }
14020
14152
  let orientationStatus = "no_session";
14021
14153
  if (agentSessionId && hasTaskGrounding) {
14022
- try {
14023
- await mcpCall("agent.markOriented", {
14024
- sessionId: agentSessionId,
14025
- ...fullCoherenceSnapshot ? { coherenceSnapshot: fullCoherenceSnapshot } : {}
14026
- });
14027
- setSessionOriented(true);
14154
+ const orientedOk = await markOrientedWithSnapshotFallback(agentSessionId, fullCoherenceSnapshot);
14155
+ if (orientedOk) {
14028
14156
  orientationStatus = "complete";
14029
14157
  lines.push("---");
14030
14158
  lines.push(`Orientation complete. Session ${agentSessionId}. Write tools available.`);
14031
- } catch {
14159
+ } else {
14032
14160
  orientationStatus = "failed";
14033
14161
  lines.push("---");
14034
14162
  lines.push("_Warning: Could not mark session as oriented. Write tools may be restricted._");
@@ -15629,7 +15757,6 @@ export {
15629
15757
  hashKey,
15630
15758
  runWithAuth,
15631
15759
  getAgentSessionId,
15632
- startAgentSession,
15633
15760
  orphanAgentSession,
15634
15761
  bootstrap,
15635
15762
  bootstrapHttp,
@@ -15639,4 +15766,4 @@ export {
15639
15766
  SERVER_VERSION,
15640
15767
  createProductBrainServer
15641
15768
  };
15642
- //# sourceMappingURL=chunk-LKUFRQUO.js.map
15769
+ //# sourceMappingURL=chunk-EPAHRXLQ.js.map