@productbrain/mcp 0.0.1-beta.5946 → 0.0.1-beta.5955

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.
@@ -1072,7 +1072,8 @@ var batchCaptureRealOutputSchema = z3.object({
1072
1072
  // stays a draft) — the same path that now also emits coherencyRefusal. The strict schema must
1073
1073
  // accept it or a refused response fails to validate on `status` (codex review, completing the
1074
1074
  // coherencyRefusal contract fix).
1075
- status: z3.enum(["draft", "committed", "proposed", "draft_on_failure"]),
1075
+ status: z3.enum(["draft", "committed", "proposed", "draft_on_failure", "draft_spine_floor"]),
1076
+ spineFloor: z3.object({ relationType: z3.string(), parentKind: z3.string(), exempt: z3.boolean() }).optional(),
1076
1077
  classifiedBy: z3.enum(["llm", "heuristic", "explicit"]).optional(),
1077
1078
  confidence: z3.number().optional(),
1078
1079
  confidenceTier: z3.enum(["high", "medium", "low"]).optional(),
@@ -1096,6 +1097,7 @@ var batchCaptureRealOutputSchema = z3.object({
1096
1097
  committed: z3.number(),
1097
1098
  proposed: z3.number(),
1098
1099
  drafts: z3.number(),
1100
+ spineFloored: z3.number().optional(),
1099
1101
  classified: z3.number().optional(),
1100
1102
  autoCommitApplied: z3.boolean(),
1101
1103
  skippedLowConfidence: z3.array(z3.object({
@@ -1764,6 +1766,8 @@ function inferRelationType(_sourceCollection, _targetCollection, profile) {
1764
1766
  var AUTO_LINK_CONFIDENCE_THRESHOLD = 35;
1765
1767
  var MAX_AUTO_LINKS = 5;
1766
1768
  var MAX_SUGGESTIONS = 5;
1769
+ var SPINE_FLOOR_SENTENCE = "kept as a draft; it can be accepted once its spine above is accepted \u2014 the promote door checks the whole spine and names the first missing rung.";
1770
+ var SPINE_FLOOR_EXEMPT_SENTENCE = "kept as a draft, though this workspace is not yet enforcing the spine above it \u2014 it can be accepted as-is.";
1767
1771
  var BR_STD_ENTRY_ID_REGEX = /^(BR|STD)-\d+$/;
1768
1772
  var entryIdSchema = z5.string().regex(BR_STD_ENTRY_ID_REGEX).optional().describe("Only for business-rules and standards. Must match BR-NNN or STD-NNN. Omit for all other collections \u2014 IDs are auto-generated.");
1769
1773
  var captureSchema = z5.object({
@@ -2071,12 +2075,14 @@ var captureSuccessOutputSchema = z5.object({
2071
2075
  entryId: z5.string(),
2072
2076
  collection: z5.string(),
2073
2077
  name: z5.string(),
2074
- // "draft_on_failure" is emitted when an auto-commit is REFUSED/failed (stays a draft) — the
2075
- // same path that also emits coherencyRefusal; the strict schema must accept it or fail.
2076
- status: z5.enum(["draft", "committed", "proposed", "draft_on_failure"]),
2078
+ // Single captures report the persisted draft; batch captures distinguish the draft reason.
2079
+ status: z5.enum(["draft", "committed", "proposed", "draft_on_failure", "draft_spine_floor"]),
2077
2080
  // WP-480 S1: `qualityScore` (required, client N/10) deleted — a named, accepted
2078
2081
  // breaking output-contract change. The server verdict stays as `qualityVerdict`.
2079
2082
  qualityVerdict: z5.record(z5.unknown()).optional(),
2083
+ qualityHints: z5.array(z5.record(z5.unknown())).optional(),
2084
+ relationSuggestions: z5.array(z5.record(z5.unknown())).optional(),
2085
+ groundingReport: captureSinglePreviewOutputSchema.shape.groundingReport.optional(),
2080
2086
  classifier: captureClassifierSchema.optional(),
2081
2087
  studioUrl: z5.string().optional(),
2082
2088
  warnings: z5.array(z5.string()).optional(),
@@ -2094,6 +2100,8 @@ var captureSuccessOutputSchema = z5.object({
2094
2100
  slug: z5.string(),
2095
2101
  status: z5.literal("proposal-pending")
2096
2102
  }).optional(),
2103
+ // TASK-160: the server's birth-floor verdict must survive the strict output contract.
2104
+ spineFloor: z5.object({ relationType: z5.string(), parentKind: z5.string(), exempt: z5.boolean() }).optional(),
2097
2105
  // WP-465 surface parity: structured coherency refusal when an auto-commit is gate-refused.
2098
2106
  // Loose record — the CoherencyRefusal contract is authored in
2099
2107
  // convex/lib/gates/coherencyControls.ts; re-declaring it here would be a second SSOT.
@@ -2183,7 +2191,7 @@ function registerSmartCaptureTools(server) {
2183
2191
  "capture",
2184
2192
  {
2185
2193
  title: "Capture",
2186
- description: "The tool for creating knowledge entries \u2014 single or batch. Two actions:\n\n- **capture**: Create a single entry. 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 \u2014 the classifier auto-routes when omitted.\n- **batch**: Create multiple entries in one call via `items[]` (absorbs batch-capture). Each item may carry inline `relations` created right after that entry is captured.\n\n**action=capture details:**\nSupported collections with smart profiles: tensions, business-rules, glossary, decisions, features, audiences, strategy, standards, chains, insights, principles.\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). In Open mode, entries commit by default when `autoCommit` is omitted. Pass `autoCommit: true` to promote the entry from draft to SSOT immediately after linking when you want to be explicit. Governed collections (glossary, business-rules, principles, standards, strategy, features, architecture) will warn but still commit \u2014 use only when you're certain.\n\nEntries are created as `draft` first, then may publish immediately depending on governance and `autoCommit`. Use `entries action=update` for post-creation adjustments.",
2194
+ description: "The tool for creating knowledge entries \u2014 single or batch. Two actions:\n\n- **capture**: Create a single entry. 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 \u2014 the classifier auto-routes when omitted.\n- **batch**: Create multiple entries in one call via `items[]` (absorbs batch-capture). Each item may carry inline `relations` created right after that entry is captured.\n\n**action=capture details:**\nSupported collections with smart profiles: tensions, business-rules, glossary, decisions, features, audiences, strategy, standards, chains, insights, principles.\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). In Open mode, entries commit by default when `autoCommit` is omitted. Pass `autoCommit: true` to promote the entry from draft to SSOT immediately after linking when you want to be explicit. Governed collections (glossary, business-rules, principles, standards, strategy, features, architecture) will warn but still commit \u2014 use only when you're certain. An objective, key result or work package is always born draft first \u2014 `autoCommit: true` has no effect on that first birth even with a complete spine; commit it explicitly afterward.\n\nEntries are created as `draft` first, then may publish immediately depending on governance and `autoCommit`. Use `entries action=update` for post-creation adjustments.",
2187
2195
  inputSchema: captureCompoundSchema,
2188
2196
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2189
2197
  },
@@ -2429,6 +2437,7 @@ ${groundingReport.governance.map((g) => `- **${g.entryId}** ${g.name} [${g.colle
2429
2437
  let authorityDomainFromCreate;
2430
2438
  let coachingFromCreate;
2431
2439
  let wasAutoCommittedServerSide = false;
2440
+ let spineFloor;
2432
2441
  try {
2433
2442
  const result2 = await kernelMutation("chain.createEntry", {
2434
2443
  collectionSlug: resolvedCollection,
@@ -2536,6 +2545,7 @@ No DB writes \u2014 call without \`preview:true\` to capture for real.${result2.
2536
2545
  if (created.status === "active") {
2537
2546
  wasAutoCommittedServerSide = true;
2538
2547
  }
2548
+ spineFloor = created.spineFloor;
2539
2549
  } catch (error) {
2540
2550
  const msg = error instanceof Error ? error.message : String(error);
2541
2551
  if (isGatewayTimeout(error)) return buildCaptureOutcomeUnknownResult(error, name, msg);
@@ -2776,7 +2786,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2776
2786
  }
2777
2787
  }
2778
2788
  const freshGovernanceMode = await refreshWorkspaceGovernanceMode();
2779
- const shouldAutoCommit = shouldAutoCommitCapture(autoCommit, freshGovernanceMode);
2789
+ const shouldAutoCommit = !spineFloor && shouldAutoCommitCapture(autoCommit, freshGovernanceMode);
2780
2790
  let finalStatus = "draft";
2781
2791
  let commitError = null;
2782
2792
  let commitRefusal = null;
@@ -2907,6 +2917,11 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2907
2917
  } else {
2908
2918
  lines.push(`Error: ${commitError}. Entry saved as draft \u2014 use \`entries action=commit entryId="${finalEntryId}"\` to promote when ready.`);
2909
2919
  }
2920
+ } else if (spineFloor) {
2921
+ lines.push("");
2922
+ lines.push("## Saved as draft");
2923
+ lines.push(`**${name}** ${spineFloor.exempt ? SPINE_FLOOR_EXEMPT_SENTENCE : SPINE_FLOOR_SENTENCE}`);
2924
+ lines.push(`Next: \`entries action=commit entryId="${finalEntryId}"\`.`);
2910
2925
  }
2911
2926
  if (shouldAutoCommit && conflictCandidates.length > 0) {
2912
2927
  lines.push("");
@@ -3130,6 +3145,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
3130
3145
  relationSuggestions: relationSuggestionsFromCreate,
3131
3146
  // TEN-2365: capture-time authority-domain proposal (PENDING ratification), when filed.
3132
3147
  ...authorityDomainFromCreate && { authorityDomain: authorityDomainFromCreate },
3148
+ // WP-745 TASK-160 (PR #901 review): the machine-readable projection — was reaching the human text only.
3149
+ ...spineFloor && { spineFloor },
3133
3150
  // WP-318 S2: Pre-write grounding report — always included (empty arrays when no suggestions)
3134
3151
  groundingReport
3135
3152
  },
@@ -3149,7 +3166,8 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
3149
3166
  const createdBy = agentId ? `agent:${agentId}` : "capture";
3150
3167
  const wsCtx = await getWorkspaceContext();
3151
3168
  const freshGovernanceModeForBatch = await refreshWorkspaceGovernanceMode();
3152
- const autoCommitApplied = shouldAutoCommitCapture(autoCommit, freshGovernanceModeForBatch);
3169
+ const shouldAutoCommitBatch = shouldAutoCommitCapture(autoCommit, freshGovernanceModeForBatch);
3170
+ let autoCommitApplied = false;
3153
3171
  const needsClassification = entries.map((e, i) => ({ ...e, index: i })).filter((e) => !e.collection);
3154
3172
  let classificationMap = /* @__PURE__ */ new Map();
3155
3173
  if (needsClassification.length > 0) {
@@ -3403,11 +3421,13 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
3403
3421
  ];
3404
3422
  if (!preview) resolveGapsForEntry(entry.name, result2.entryId);
3405
3423
  const batchWasAutoCommittedServerSide = result2.status === "active";
3406
- let finalStatus = batchWasAutoCommittedServerSide ? "committed" : "draft";
3424
+ const batchSpineFloor = result2.spineFloor;
3425
+ let finalStatus = batchWasAutoCommittedServerSide ? "committed" : batchSpineFloor ? "draft_spine_floor" : "draft";
3407
3426
  let commitError;
3408
3427
  let commitRefusal;
3409
3428
  let contradictionAdvisory;
3410
3429
  if (batchWasAutoCommittedServerSide) {
3430
+ if (!preview) autoCommitApplied = true;
3411
3431
  await recordSessionActivity({ entryModified: internalId });
3412
3432
  trackChainEntryCommitted(wsCtx.workspaceId, {
3413
3433
  entry_id: finalEntryId,
@@ -3502,9 +3522,10 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
3502
3522
  if (relationsProposedCount > 0) {
3503
3523
  batchEntryWarnings.push(`${relationsProposedCount} inline relation(s) converted to an agent proposal (misuse pattern) \u2014 review in Cortex UI.`);
3504
3524
  }
3505
- if (!preview && autoCommitApplied && !batchWasAutoCommittedServerSide) {
3525
+ if (!preview && shouldAutoCommitBatch && !batchWasAutoCommittedServerSide && !batchSpineFloor) {
3506
3526
  try {
3507
3527
  const semanticConflicts = await discoverSemanticConflicts(entry.name, entry.description, resolvedSlug);
3528
+ autoCommitApplied = true;
3508
3529
  const commitResult = await kernelMutation("chain.commitEntry", {
3509
3530
  entryId: finalEntryId,
3510
3531
  author: agentId ? `agent:${agentId}` : void 0,
@@ -3559,6 +3580,8 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
3559
3580
  ...commitError ? { commitError } : {},
3560
3581
  // WP-465 surface parity: thread full refusal payload for coherency-refused batch entries.
3561
3582
  ...commitRefusal ? { commitRefusal } : {},
3583
+ ...batchSpineFloor ? { spineFloor: batchSpineFloor } : {},
3584
+ // WP-745 TASK-160: read by the render below.
3562
3585
  // WP-485 Slice 2b (R2, FEAT-1370): the server's contradiction advisory (DEC-1321 —
3563
3586
  // advisory only, never blocking) — visible, never a silent success.
3564
3587
  ...contradictionAdvisory ? { contradictionAdvisory } : {},
@@ -3598,6 +3621,7 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
3598
3621
  const proposed = created.filter((r) => r.status === "proposed");
3599
3622
  const drafts = created.filter((r) => r.status === "draft");
3600
3623
  const commitFailed = created.filter((r) => r.status === "draft_on_failure");
3624
+ const spineFloored = created.filter((r) => r.status === "draft_spine_floor");
3601
3625
  const classifiedCount = created.filter((r) => r.classifiedBy && r.classifiedBy !== "explicit").length;
3602
3626
  await server.sendLoggingMessage({
3603
3627
  level: "info",
@@ -3625,8 +3649,9 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
3625
3649
  }
3626
3650
  if (created.length > 0) {
3627
3651
  const commitFailedSegment = commitFailed.length > 0 ? `, ${commitFailed.length} commit-failed` : "";
3652
+ const spineFloorSegment = spineFloored.length > 0 ? `, ${spineFloored.length} spine-floored` : "";
3628
3653
  lines.push(
3629
- `**Statuses:** ${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${commitFailedSegment}.`
3654
+ `**Statuses:** ${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${spineFloorSegment}${commitFailedSegment}.`
3630
3655
  );
3631
3656
  lines.push("");
3632
3657
  }
@@ -3670,11 +3695,13 @@ _Use \`entries action=move\` to correct any misclassified entries._`);
3670
3695
  lines.push("");
3671
3696
  lines.push("_Re-capture these with an explicit `collection` or use the suggested collection._");
3672
3697
  }
3673
- if (commitFailed.length > 0) {
3698
+ if (commitFailed.length > 0 || spineFloored.length > 0) {
3674
3699
  lines.push("");
3675
3700
  lines.push("## Saved as draft");
3676
- for (const r of commitFailed) {
3677
- if (r.commitRefusal) {
3701
+ for (const r of [...spineFloored, ...commitFailed]) {
3702
+ if (r.spineFloor) {
3703
+ lines.push(`- **${r.entryId}**: ${r.name} [${r.collection}] \u2014 ${r.spineFloor.exempt ? SPINE_FLOOR_EXEMPT_SENTENCE : SPINE_FLOOR_SENTENCE} Next: \`entries action=commit entryId="${r.entryId}"\`.`);
3704
+ } else if (r.commitRefusal) {
3678
3705
  lines.push(`- **${r.entryId}**: ${r.name} [${r.collection}]`);
3679
3706
  const refusalText = coherencyRefusedResult("batch-capture", r.entryId ?? "", r.commitRefusal, { headingLevel: 3 }).content[0].text;
3680
3707
  lines.push(...refusalText.split("\n"));
@@ -3743,8 +3770,9 @@ _Use \`entries action=move\` to correct any misclassified entries._`);
3743
3770
  const skippedNote = skippedLowConfidence.length > 0 ? `, ${skippedLowConfidence.length} skipped (low confidence)` : "";
3744
3771
  const classifiedNote = classifiedCount > 0 ? `, ${classifiedCount} auto-classified` : "";
3745
3772
  const commitFailedNote = commitFailed.length > 0 ? `, ${commitFailed.length} commit-failed` : "";
3746
- const summary = failed.length > 0 || unknownOutcomeEntries.length > 0 || skippedLowConfidence.length > 0 || commitFailed.length > 0 ? `Batch captured ${created.length}/${entries.length} entries (${failed.length} failed${unknownOutcomeSummaryNote(unknownOutcomeEntries)}${skippedNote}, ${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${commitFailedNote}${classifiedNote}).` : `Batch captured ${created.length} entries successfully (${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${classifiedNote}).`;
3747
- const firstDraft = drafts[0];
3773
+ const spineFloorNote = spineFloored.length > 0 ? `, ${spineFloored.length} spine-floored` : "";
3774
+ const summary = failed.length > 0 || unknownOutcomeEntries.length > 0 || skippedLowConfidence.length > 0 || commitFailed.length > 0 || spineFloored.length > 0 ? `Batch captured ${created.length}/${entries.length} entries (${failed.length} failed${unknownOutcomeSummaryNote(unknownOutcomeEntries)}${skippedNote}, ${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${spineFloorNote}${commitFailedNote}${classifiedNote}).` : `Batch captured ${created.length} entries successfully (${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${classifiedNote}).`;
3775
+ const firstDraft = drafts[0] ?? spineFloored[0] ?? commitFailed[0];
3748
3776
  const next = [];
3749
3777
  if (created.length > 0) {
3750
3778
  next.push({ tool: "relations", description: "Discover connections", parameters: { action: "suggest", entryId: created[0].entryId } });
@@ -3780,6 +3808,8 @@ _Use \`entries action=move\` to correct any misclassified entries._`);
3780
3808
  ...r.commitError ? { commitError: r.commitError } : {},
3781
3809
  // WP-465 surface parity: thread full refusal payload into batch structured output.
3782
3810
  ...r.commitRefusal ? { coherencyRefusal: r.commitRefusal } : {},
3811
+ ...r.spineFloor ? { spineFloor: r.spineFloor } : {},
3812
+ // WP-745 TASK-160: the projection a machine consumer receives; was human-render only (Copilot review, PR #901).
3783
3813
  ...r.authorityDomain ? { domain: r.authorityDomain.slug } : {},
3784
3814
  // TEN-957 (WP-484 S2): inline relations created for this entry.
3785
3815
  ...r.relationsCreated ? { relationsCreated: r.relationsCreated } : {},
@@ -3796,6 +3826,7 @@ _Use \`entries action=move\` to correct any misclassified entries._`);
3796
3826
  proposed: proposed.length,
3797
3827
  drafts: drafts.length,
3798
3828
  ...commitFailed.length > 0 ? { commitFailed: commitFailed.length } : {},
3829
+ ...spineFloored.length > 0 ? { spineFloored: spineFloored.length } : {},
3799
3830
  ...classifiedCount > 0 ? { classified: classifiedCount } : {},
3800
3831
  autoCommitApplied,
3801
3832
  ...skippedLowConfidence.length > 0 && {
@@ -17062,4 +17093,4 @@ export {
17062
17093
  createProductBrainServer,
17063
17094
  initFeatureFlags
17064
17095
  };
17065
- //# sourceMappingURL=chunk-7T4HSTNG.js.map
17096
+ //# sourceMappingURL=chunk-W3MF7EC3.js.map