@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
|
-
//
|
|
2075
|
-
|
|
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
|
|
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
|
-
|
|
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 &&
|
|
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.
|
|
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
|
|
3747
|
-
const
|
|
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-
|
|
17096
|
+
//# sourceMappingURL=chunk-W3MF7EC3.js.map
|