@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" || groundingEntry?.kind === "version",
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("Only true when user explicitly asks to publish. Default false = draft. Never auto-publish without user confirmation."),
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': only true when the user explicitly asks to publish. Default false = draft."
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 (draft by default \u2014 never autoPublish without explicit user confirmation).\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.',
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-7TLBTMIV.js.map
16756
+ //# sourceMappingURL=chunk-PZDI2QMN.js.map