@productbrain/mcp 0.0.1-beta.6442 → 0.0.1-beta.6461
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.
|
@@ -847,6 +847,169 @@ async function resolveCollection(params) {
|
|
|
847
847
|
return result2 ?? null;
|
|
848
848
|
}
|
|
849
849
|
|
|
850
|
+
// src/tools/coherency-refusal.ts
|
|
851
|
+
function asDescription(value) {
|
|
852
|
+
if (typeof value !== "string") return void 0;
|
|
853
|
+
const trimmed = value.trim();
|
|
854
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
855
|
+
}
|
|
856
|
+
var GENERIC_RECOVERY_LINE = "Respond and retry: link the entry to the strategic spine, pass `coherencyAcknowledgement`, or supply a typed `steeringOverrideReason`.";
|
|
857
|
+
function coherencyRefusedResult(toolName, entryId, refusal, options) {
|
|
858
|
+
const headingLevel = options?.headingLevel ?? 1;
|
|
859
|
+
const heading = "#".repeat(headingLevel);
|
|
860
|
+
const lines = [
|
|
861
|
+
`${heading} Write refused by the coherency gate`,
|
|
862
|
+
"",
|
|
863
|
+
refusal.reason ?? `The coherency gate refused this write on ${entryId}.`
|
|
864
|
+
];
|
|
865
|
+
if (refusal.subject?.name) {
|
|
866
|
+
lines.push(`On: ${refusal.subject.name}`);
|
|
867
|
+
}
|
|
868
|
+
if (refusal.mode) {
|
|
869
|
+
lines.push("", `Mode: ${refusal.mode}`);
|
|
870
|
+
}
|
|
871
|
+
const routes = refusal.routes;
|
|
872
|
+
const routeKeys = routes ? Object.keys(routes) : [];
|
|
873
|
+
if (routeKeys.length > 0) {
|
|
874
|
+
lines.push("", `${"#".repeat(headingLevel + 1)} Recovery routes`);
|
|
875
|
+
const beforeRoutes = lines.length;
|
|
876
|
+
if (routes && typeof routes.link === "object" && routes.link !== null) {
|
|
877
|
+
const link = routes.link;
|
|
878
|
+
lines.push(`- **link:** ${asDescription(link.description) ?? "Link the entry to the strategic spine."}`);
|
|
879
|
+
}
|
|
880
|
+
if (routes && typeof routes.acknowledge === "object" && routes.acknowledge !== null) {
|
|
881
|
+
const ack = routes.acknowledge;
|
|
882
|
+
const responses = ack.responses?.join(" | ") ?? "linked | accepted-fix | diverged";
|
|
883
|
+
lines.push(
|
|
884
|
+
`- **acknowledge** (\`${ack.param ?? "coherencyAcknowledgement"}\`): ${responses}${ack.minReasonChars ? ` (\u2265${ack.minReasonChars} chars for diverged)` : ""}. ${asDescription(ack.description) ?? ""}`.trim()
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
if (routes && typeof routes.override === "object" && routes.override !== null) {
|
|
888
|
+
const ov = routes.override;
|
|
889
|
+
lines.push(
|
|
890
|
+
`- **override** (\`${ov.param ?? "steeringOverrideReason"}\`): \u2265${ov.minChars ?? 12} chars. ${asDescription(ov.description) ?? ""}`.trim()
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
if (lines.length === beforeRoutes) {
|
|
894
|
+
lines.push(GENERIC_RECOVERY_LINE);
|
|
895
|
+
}
|
|
896
|
+
} else {
|
|
897
|
+
lines.push("", GENERIC_RECOVERY_LINE);
|
|
898
|
+
}
|
|
899
|
+
return failureResult(
|
|
900
|
+
lines.join("\n"),
|
|
901
|
+
"COHERENCY_REFUSED",
|
|
902
|
+
`Coherency gate refused ${toolName} on ${entryId}.`,
|
|
903
|
+
"Use one of the refusal's routes (link / coherencyAcknowledgement / steeringOverrideReason), then retry.",
|
|
904
|
+
void 0,
|
|
905
|
+
{ entryId, refusal }
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// src/lib/collectionCache.ts
|
|
910
|
+
var collectionCache = new ScopedCache(COLLECTION_CACHE_TTL_MS);
|
|
911
|
+
async function getCollections() {
|
|
912
|
+
const scope = cacheScope();
|
|
913
|
+
const hit = collectionCache.get(scope);
|
|
914
|
+
if (hit) return hit.collections;
|
|
915
|
+
const result2 = await kernelQuery("chain.listCollections") ?? [];
|
|
916
|
+
const bySlug = new Map(result2.map((c) => [c.slug, c]));
|
|
917
|
+
collectionCache.set(scope, { collections: result2, bySlug });
|
|
918
|
+
return result2;
|
|
919
|
+
}
|
|
920
|
+
async function getCollectionBySlug(slug) {
|
|
921
|
+
await getCollections();
|
|
922
|
+
return collectionCache.get(cacheScope())?.bySlug.get(slug);
|
|
923
|
+
}
|
|
924
|
+
function invalidateCollectionCacheForScope() {
|
|
925
|
+
collectionCache.delete(cacheScope());
|
|
926
|
+
}
|
|
927
|
+
async function isGoverned(slug) {
|
|
928
|
+
const col = await getCollectionBySlug(slug);
|
|
929
|
+
return !!col?.governed;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/lib/versionWrite.ts
|
|
933
|
+
function buildVersionPatch(params) {
|
|
934
|
+
const { capture, targetProseKey } = params;
|
|
935
|
+
const data = { ...capture.fields ?? {} };
|
|
936
|
+
if (capture.prose.trim()) data[targetProseKey] = capture.prose;
|
|
937
|
+
return capture.name ? { name: capture.name, data } : { data };
|
|
938
|
+
}
|
|
939
|
+
function readVersionWriteOutcome(result2) {
|
|
940
|
+
if (result2 && typeof result2 === "object" && result2.outcome === "refused") {
|
|
941
|
+
const rawRefusal = result2.refusal;
|
|
942
|
+
const refusal = rawRefusal && typeof rawRefusal === "object" ? rawRefusal : { reason: "The workspace refused this update." };
|
|
943
|
+
return { ok: false, refusal };
|
|
944
|
+
}
|
|
945
|
+
if (result2 && typeof result2 === "object" && typeof result2.id === "string") {
|
|
946
|
+
return { ok: true, id: result2.id };
|
|
947
|
+
}
|
|
948
|
+
return { ok: false, refusal: { reason: "The workspace refused this update." } };
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// src/tools/captureVersion.ts
|
|
952
|
+
function buildVersionPreviewResult(groundingEntry, name) {
|
|
953
|
+
return successResult(
|
|
954
|
+
`# Would save as a new version of ${groundingEntry.entryId}
|
|
955
|
+
|
|
956
|
+
No DB writes \u2014 call without \`preview:true\` to version for real.`,
|
|
957
|
+
`Would save as a new version of ${groundingEntry.entryId}.`,
|
|
958
|
+
{ id: groundingEntry.entryId, kind: "version", preview: true, name }
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
async function resolveTargetProseKey(collectionSlug) {
|
|
962
|
+
const col = await getCollectionBySlug(collectionSlug).catch(() => void 0);
|
|
963
|
+
return col?.descriptionFieldKey || "description";
|
|
964
|
+
}
|
|
965
|
+
async function runVersionCapture(params) {
|
|
966
|
+
const { groundingEntry, name, description, data, sourceRef, sourceExcerpt, links, groundingElapsedMs } = params;
|
|
967
|
+
const versionAgentId = getAgentSessionId();
|
|
968
|
+
const targetProseKey = await resolveTargetProseKey(groundingEntry.collectionSlug);
|
|
969
|
+
const patch = buildVersionPatch({ capture: { prose: description ?? "", fields: data ?? void 0 }, targetProseKey });
|
|
970
|
+
try {
|
|
971
|
+
const rawResult = await kernelMutation("chain.updateEntry", {
|
|
972
|
+
entryId: groundingEntry.entryId,
|
|
973
|
+
...patch,
|
|
974
|
+
autoPublish: true,
|
|
975
|
+
changeNote: "Captured as a new version via MCP",
|
|
976
|
+
changedBy: versionAgentId ? `agent:${versionAgentId}` : "capture",
|
|
977
|
+
...sourceRef ? { sourceRef } : {},
|
|
978
|
+
...sourceExcerpt ? { sourceExcerpt } : {}
|
|
979
|
+
});
|
|
980
|
+
void kernelMutation("chain.recordGroundingOutcome", {
|
|
981
|
+
surface: "mcp",
|
|
982
|
+
promptShown: false,
|
|
983
|
+
highestRatio: groundingEntry.score,
|
|
984
|
+
hadGovernance: false,
|
|
985
|
+
grounding_ms: groundingElapsedMs
|
|
986
|
+
}).catch(() => void 0);
|
|
987
|
+
const outcome = readVersionWriteOutcome(rawResult);
|
|
988
|
+
if (!outcome.ok) {
|
|
989
|
+
return coherencyRefusedResult("capture", groundingEntry.entryId, outcome.refusal);
|
|
990
|
+
}
|
|
991
|
+
if (links && links.length > 0) {
|
|
992
|
+
await kernelMutation("chain.createEntryRelations", {
|
|
993
|
+
relations: links.slice(0, 25).map((l) => ({ fromEntryId: groundingEntry.entryId, toEntryId: l.to, type: l.type, proposedBy: "user" })),
|
|
994
|
+
sessionId: versionAgentId ?? void 0
|
|
995
|
+
}).catch(() => void 0);
|
|
996
|
+
}
|
|
997
|
+
return successResult(
|
|
998
|
+
`# Saved as a new version of ${groundingEntry.entryId}
|
|
999
|
+
|
|
1000
|
+
${name}`,
|
|
1001
|
+
`Saved as a new version of ${groundingEntry.entryId}.`,
|
|
1002
|
+
{ id: groundingEntry.entryId, kind: "version" }
|
|
1003
|
+
);
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
const classified = classifyError(error);
|
|
1006
|
+
return failureResult(classified.message, classified.code, classified.message, classified.recovery, classified.availableActions, classified.diagnostics);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
async function captureVersionOrPreview(preview, params) {
|
|
1010
|
+
return preview ? buildVersionPreviewResult(params.groundingEntry, params.name) : runVersionCapture(params);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
850
1013
|
// src/lib/fieldTypes.ts
|
|
851
1014
|
var FIELD_TYPE_DEFAULTS = {
|
|
852
1015
|
"string": "",
|
|
@@ -909,29 +1072,6 @@ function inferSourceDate(...values) {
|
|
|
909
1072
|
return `${inferredYear}-${month}-${day}`;
|
|
910
1073
|
}
|
|
911
1074
|
|
|
912
|
-
// src/lib/collectionCache.ts
|
|
913
|
-
var collectionCache = new ScopedCache(COLLECTION_CACHE_TTL_MS);
|
|
914
|
-
async function getCollections() {
|
|
915
|
-
const scope = cacheScope();
|
|
916
|
-
const hit = collectionCache.get(scope);
|
|
917
|
-
if (hit) return hit.collections;
|
|
918
|
-
const result2 = await kernelQuery("chain.listCollections") ?? [];
|
|
919
|
-
const bySlug = new Map(result2.map((c) => [c.slug, c]));
|
|
920
|
-
collectionCache.set(scope, { collections: result2, bySlug });
|
|
921
|
-
return result2;
|
|
922
|
-
}
|
|
923
|
-
async function getCollectionBySlug(slug) {
|
|
924
|
-
await getCollections();
|
|
925
|
-
return collectionCache.get(cacheScope())?.bySlug.get(slug);
|
|
926
|
-
}
|
|
927
|
-
function invalidateCollectionCacheForScope() {
|
|
928
|
-
collectionCache.delete(cacheScope());
|
|
929
|
-
}
|
|
930
|
-
async function isGoverned(slug) {
|
|
931
|
-
const col = await getCollectionBySlug(slug);
|
|
932
|
-
return !!col?.governed;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
1075
|
// src/lib/textMatch.ts
|
|
936
1076
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
937
1077
|
"the",
|
|
@@ -2335,6 +2475,7 @@ ${groundingReport.duplicates.map((d) => `- **${d.entryId}** ${d.name} [${d.colle
|
|
|
2335
2475
|
}
|
|
2336
2476
|
};
|
|
2337
2477
|
}
|
|
2478
|
+
if (groundingEntry?.kind === "version") return captureVersionOrPreview(preview ?? false, { groundingEntry, name, description, data: userData, sourceRef, sourceExcerpt, links, groundingElapsedMs });
|
|
2338
2479
|
const data = buildDataFromFields(col.fields ?? [], profile.descriptionField, description);
|
|
2339
2480
|
for (const def of profile.defaults) {
|
|
2340
2481
|
if (def.value !== "infer" && def.value !== "today") {
|
|
@@ -2538,7 +2679,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
|
|
|
2538
2679
|
const tAfterCreate = Date.now();
|
|
2539
2680
|
void kernelMutation("chain.recordGroundingOutcome", {
|
|
2540
2681
|
surface: "mcp",
|
|
2541
|
-
promptShown: groundingEntry?.kind === "ask"
|
|
2682
|
+
promptShown: groundingEntry?.kind === "ask",
|
|
2542
2683
|
highestRatio: Math.max(
|
|
2543
2684
|
...groundingReport.duplicates.map((d) => d.overlapRatio),
|
|
2544
2685
|
...groundingReport.related.map((r) => r.overlapRatio),
|
|
@@ -3719,65 +3860,6 @@ async function runConflictPreflight(name, description, collectionHint) {
|
|
|
3719
3860
|
}
|
|
3720
3861
|
}
|
|
3721
3862
|
|
|
3722
|
-
// src/tools/coherency-refusal.ts
|
|
3723
|
-
function asDescription(value) {
|
|
3724
|
-
if (typeof value !== "string") return void 0;
|
|
3725
|
-
const trimmed = value.trim();
|
|
3726
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
3727
|
-
}
|
|
3728
|
-
var GENERIC_RECOVERY_LINE = "Respond and retry: link the entry to the strategic spine, pass `coherencyAcknowledgement`, or supply a typed `steeringOverrideReason`.";
|
|
3729
|
-
function coherencyRefusedResult(toolName, entryId, refusal, options) {
|
|
3730
|
-
const headingLevel = options?.headingLevel ?? 1;
|
|
3731
|
-
const heading = "#".repeat(headingLevel);
|
|
3732
|
-
const lines = [
|
|
3733
|
-
`${heading} Write refused by the coherency gate`,
|
|
3734
|
-
"",
|
|
3735
|
-
refusal.reason ?? `The coherency gate refused this write on ${entryId}.`
|
|
3736
|
-
];
|
|
3737
|
-
if (refusal.subject?.name) {
|
|
3738
|
-
lines.push(`On: ${refusal.subject.name}`);
|
|
3739
|
-
}
|
|
3740
|
-
if (refusal.mode) {
|
|
3741
|
-
lines.push("", `Mode: ${refusal.mode}`);
|
|
3742
|
-
}
|
|
3743
|
-
const routes = refusal.routes;
|
|
3744
|
-
const routeKeys = routes ? Object.keys(routes) : [];
|
|
3745
|
-
if (routeKeys.length > 0) {
|
|
3746
|
-
lines.push("", `${"#".repeat(headingLevel + 1)} Recovery routes`);
|
|
3747
|
-
const beforeRoutes = lines.length;
|
|
3748
|
-
if (routes && typeof routes.link === "object" && routes.link !== null) {
|
|
3749
|
-
const link = routes.link;
|
|
3750
|
-
lines.push(`- **link:** ${asDescription(link.description) ?? "Link the entry to the strategic spine."}`);
|
|
3751
|
-
}
|
|
3752
|
-
if (routes && typeof routes.acknowledge === "object" && routes.acknowledge !== null) {
|
|
3753
|
-
const ack = routes.acknowledge;
|
|
3754
|
-
const responses = ack.responses?.join(" | ") ?? "linked | accepted-fix | diverged";
|
|
3755
|
-
lines.push(
|
|
3756
|
-
`- **acknowledge** (\`${ack.param ?? "coherencyAcknowledgement"}\`): ${responses}${ack.minReasonChars ? ` (\u2265${ack.minReasonChars} chars for diverged)` : ""}. ${asDescription(ack.description) ?? ""}`.trim()
|
|
3757
|
-
);
|
|
3758
|
-
}
|
|
3759
|
-
if (routes && typeof routes.override === "object" && routes.override !== null) {
|
|
3760
|
-
const ov = routes.override;
|
|
3761
|
-
lines.push(
|
|
3762
|
-
`- **override** (\`${ov.param ?? "steeringOverrideReason"}\`): \u2265${ov.minChars ?? 12} chars. ${asDescription(ov.description) ?? ""}`.trim()
|
|
3763
|
-
);
|
|
3764
|
-
}
|
|
3765
|
-
if (lines.length === beforeRoutes) {
|
|
3766
|
-
lines.push(GENERIC_RECOVERY_LINE);
|
|
3767
|
-
}
|
|
3768
|
-
} else {
|
|
3769
|
-
lines.push("", GENERIC_RECOVERY_LINE);
|
|
3770
|
-
}
|
|
3771
|
-
return failureResult(
|
|
3772
|
-
lines.join("\n"),
|
|
3773
|
-
"COHERENCY_REFUSED",
|
|
3774
|
-
`Coherency gate refused ${toolName} on ${entryId}.`,
|
|
3775
|
-
"Use one of the refusal's routes (link / coherencyAcknowledgement / steeringOverrideReason), then retry.",
|
|
3776
|
-
void 0,
|
|
3777
|
-
{ entryId, refusal }
|
|
3778
|
-
);
|
|
3779
|
-
}
|
|
3780
|
-
|
|
3781
3863
|
// src/tools/knowledge/getHistory.ts
|
|
3782
3864
|
import { z as z7 } from "zod/v3";
|
|
3783
3865
|
var getHistorySchema = z7.object({
|
|
@@ -3821,7 +3903,8 @@ var updateEntrySchema = z8.object({
|
|
|
3821
3903
|
data: z8.record(z8.unknown()).optional().describe("Fields to update (merged with existing data)"),
|
|
3822
3904
|
order: z8.number().optional().describe("New sort order"),
|
|
3823
3905
|
canonicalKey: z8.string().optional().describe("Semantic type (e.g. 'decision', 'tension'). Only changeable on draft/uncommitted entries."),
|
|
3824
|
-
autoPublish: z8.boolean().optional().default(false).describe("
|
|
3906
|
+
autoPublish: z8.boolean().optional().default(false).describe("Accepted for backward compatibility but IGNORED \u2014 draft vs. published is decided by the entry's own accepted/active status, never by this flag. Removal tracked as a follow-up."),
|
|
3907
|
+
// DEC-1716
|
|
3825
3908
|
changeNote: z8.string().optional().describe("Strongly recommended: short human-readable rationale for WHY this change was made (e.g. 'Aligned description with F1-themed copy'). Surfaces in activity feed and pb get. If omitted, falls back to session purpose or auto-generated field summary."),
|
|
3826
3909
|
sourceRef: z8.string().optional().describe("URI or path of the source document backing this entry. Write-once: can only be set if currently empty."),
|
|
3827
3910
|
sourceExcerpt: z8.string().optional().describe("Verbatim excerpt from the source that backs this entry's claims. Write-once: can only be set if currently empty."),
|
|
@@ -4708,8 +4791,9 @@ var entriesSchema = z11.object({
|
|
|
4708
4791
|
canonicalKey: z11.string().max(200).optional().describe(
|
|
4709
4792
|
"For 'update': semantic type (e.g. 'decision', 'tension'); only changeable on draft/uncommitted entries. For 'move': optional atomic move+retype target. For 'preflight': optional semantic type to check requirements for."
|
|
4710
4793
|
),
|
|
4794
|
+
// DEC-1716: draft vs. published is decided by the entry's own accepted/active status, never this flag.
|
|
4711
4795
|
autoPublish: z11.boolean().optional().default(false).describe(
|
|
4712
|
-
"For 'update':
|
|
4796
|
+
"For 'update': accepted for backward compatibility but IGNORED \u2014 draft vs. published is decided by the entry's own accepted/active status, never by this flag."
|
|
4713
4797
|
),
|
|
4714
4798
|
changeNote: z11.string().max(2e3).optional().describe(
|
|
4715
4799
|
"For 'update': short human-readable rationale for WHY this change was made. Surfaces in activity feed."
|
|
@@ -4951,7 +5035,7 @@ function registerEntriesTools(server) {
|
|
|
4951
5035
|
"entries",
|
|
4952
5036
|
{
|
|
4953
5037
|
title: "Entries",
|
|
4954
|
-
description: 'Read and manage entries on the Chain. One tool for the entry lifecycle.\n\n- **preflight**: Inspect a type and selected context before composing. No writes, capacity reservation or authority grant.\n- **list**: Browse entries with optional filters (collection, status, tag, label). Use collections action=list first to discover slugs.\n- **get**: Fetch a single entry by ID \u2014 full record with data, labels, relations, history.\n- **batch**: Fetch multiple entries (max 20) in one call. Same shape as get per entry.\n- **search**: Full-text search across entries. Scope by collection or filter by status.\n- **update**: Change fields on an existing entry (
|
|
5038
|
+
description: 'Read and manage entries on the Chain. One tool for the entry lifecycle.\n\n- **preflight**: Inspect a type and selected context before composing. No writes, capacity reservation or authority grant.\n- **list**: Browse entries with optional filters (collection, status, tag, label). Use collections action=list first to discover slugs.\n- **get**: Fetch a single entry by ID \u2014 full record with data, labels, relations, history.\n- **batch**: Fetch multiple entries (max 20) in one call. Same shape as get per entry.\n- **search**: Full-text search across entries. Scope by collection or filter by status.\n- **update**: Change fields on an existing entry (publishes whenever the entry is not draft \u2014 includes archived, not just accepted/active; autoPublish is accepted but ignored).\n- **commit**: Accept a draft entry onto the Chain (SSOT). Runs the contradiction preflight first.\n- **history**: Audit trail for an entry.\n- **move**: Relocate an entry to a different collection; pass canonicalKey too for an atomic move+retype.\n- **verify**: Mark an entry as verified (lightweight, no codebase scan \u2014 see `quality action=verify-chain` for that).\n\nUse `entries action=get entryId="..."` to fetch one. Use `entries action=search query="..."` to discover.',
|
|
4955
5039
|
inputSchema: entriesSchema,
|
|
4956
5040
|
// Mixed read/write noun (§3 R3): can't be marked read-only once update/commit/move
|
|
4957
5041
|
// live here too — accepted for the 11-tool default (§9 R3).
|
|
@@ -16669,4 +16753,4 @@ export {
|
|
|
16669
16753
|
createProductBrainServer,
|
|
16670
16754
|
initFeatureFlags
|
|
16671
16755
|
};
|
|
16672
|
-
//# sourceMappingURL=chunk-
|
|
16756
|
+
//# sourceMappingURL=chunk-PZDI2QMN.js.map
|