@productbrain/mcp 0.0.1-beta.7530 → 0.0.1-beta.7547

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.
@@ -3624,13 +3624,11 @@ async function handleVerifyChain(server, { collection, mode }) {
3624
3624
  };
3625
3625
  }
3626
3626
  async function handleVerifyEntry({ entryId }) {
3627
+ requireWriteAccess();
3627
3628
  const agentSessionId = getAgentSessionId();
3628
3629
  const result2 = await kernelMutation(
3629
3630
  "chain.verifyEntry",
3630
- {
3631
- entryId,
3632
- ...agentSessionId ? { sessionId: agentSessionId } : {}
3633
- }
3631
+ { entryId, ...agentSessionId ? { sessionId: agentSessionId } : {} }
3634
3632
  );
3635
3633
  const basis = result2.attestation?.basis;
3636
3634
  const strength = result2.attestation?.strength;
@@ -7631,7 +7629,51 @@ function formatShapeAdvisorySummaryLine(sas) {
7631
7629
  return `Shape advisories (${sas.windowDays}d): ${sas.pendingCount} pending / ${sas.compoundTotal} compound${pendingRatePart} \u2014 precisionAgent ${agentPart}, precisionHuman ${humanPart}.${truncatedNote}${candidatePart}`;
7632
7630
  }
7633
7631
 
7632
+ // src/lib/ruleListRender.ts
7633
+ var RULE_LIST_CONTROL_CHARS_RE = /[\x00-\x08\x0b-\x1f\x7f]/g;
7634
+ function sanitizeRuleListField(value) {
7635
+ return value.replace(RULE_LIST_CONTROL_CHARS_RE, "").replace(/\s+/g, " ").trim();
7636
+ }
7637
+ function formatRuleListRow(row) {
7638
+ const entryId = sanitizeRuleListField(row.entryId);
7639
+ const owner = row.ownerRoleId ? sanitizeRuleListField(row.ownerRoleId) : "\u2014";
7640
+ const team = row.teamId ? ` (team ${sanitizeRuleListField(row.teamId)})` : "";
7641
+ const servedAt = row.servedAt ? ` \u2014 served ${new Date(row.servedAt).toISOString()}` : "";
7642
+ return `${entryId} \u2014 owner ${owner}${team}${servedAt}`;
7643
+ }
7644
+ function formatRuleListBucketLines(label, bucket, truncated) {
7645
+ if (!bucket || bucket.rows.length === 0) return [];
7646
+ const lines = [`${label}:`, ...bucket.rows.map((r) => ` - ${formatRuleListRow(r)}`)];
7647
+ if (bucket.overCap > 0) lines.push(` (${truncated ? "at least " : ""}+${bucket.overCap} more, capped)`);
7648
+ return lines;
7649
+ }
7650
+ function formatRuleListsLines(ruleLists) {
7651
+ if (!ruleLists) return [];
7652
+ const lines = [
7653
+ ...formatRuleListBucketLines("Everyone", ruleLists.everyone, ruleLists.truncated),
7654
+ ...formatRuleListBucketLines("Team", ruleLists.team, ruleLists.truncated),
7655
+ ...formatRuleListBucketLines("Role", ruleLists.role, ruleLists.truncated)
7656
+ ];
7657
+ if (lines.length === 0 && ruleLists.unassigned === 0 && !ruleLists.truncated) return [];
7658
+ if (ruleLists.truncated) {
7659
+ lines.push("(Some Current rules may be missing from these lists \u2014 the underlying read was truncated.)");
7660
+ }
7661
+ if (ruleLists.unassigned > 0) {
7662
+ const atLeast = ruleLists.truncated ? "at least " : "";
7663
+ lines.push(`(${atLeast}${ruleLists.unassigned} rule(s) opted into a list but could not be resolved \u2014 excluded, not silently dropped.)`);
7664
+ }
7665
+ return lines;
7666
+ }
7667
+
7634
7668
  // src/tools/orient-rituals.ts
7669
+ var RULE_LISTS_EVERYONE_ONLY_LABEL = "Rules (everyone list only; run `orient mode=full` for your team and role lists):";
7670
+ var RULE_LISTS_UNAVAILABLE_LINE = "Rules: unavailable \u2014 rule list fetch failed, retry `orient mode=full`";
7671
+ function buildEveryoneOnlyRuleListsLines(ruleLists) {
7672
+ if (!ruleLists) return [];
7673
+ const everyoneOnly = { everyone: ruleLists.everyone, team: null, role: { rows: [], overCap: 0 }, unassigned: ruleLists.unassigned, truncated: ruleLists.truncated };
7674
+ const bucketLines = formatRuleListsLines(everyoneOnly);
7675
+ return [RULE_LISTS_EVERYONE_ONLY_LABEL, bucketLines.length > 0 ? bucketLines.join("\n") : "(none currently served)"];
7676
+ }
7635
7677
  var SPINE_UNAVAILABLE_LINE = "Spine check: unavailable \u2014 orient fetch failed, retry `orient`";
7636
7678
  function buildRitualsSurfaceLines(view, opts) {
7637
7679
  const lines = [""];
@@ -7743,20 +7785,29 @@ async function fetchAndRenderOpenerDirection(kernelQuery2, errors, session) {
7743
7785
  return [...buildDirectionLines(view), ...buildRitualsSurfaceLines(view, { fetchFailed: fetchFailed || view?.fetchFailed === true })];
7744
7786
  }
7745
7787
  async function buildAlreadyOrientedResult(kernelQuery2, agentSessionId, taskGroundingWarningLines2) {
7746
- let ritualsFetchFailed = false;
7747
- let rituals = null;
7748
- try {
7749
- rituals = await kernelQuery2("chain.getRitualsSurface", {
7788
+ const [ritualsSettled, bindingGovernanceSettled] = await Promise.allSettled([
7789
+ // PR #532 late review fix (P2, "add a time bucket to the direct rituals query"): matches
7790
+ // conflictQueue.ts's minute-bucket convention — forces a fresh reactive result once a snooze
7791
+ // expires on wall-clock time alone, with no corresponding DB write to bust the cache.
7792
+ kernelQuery2("chain.getRitualsSurface", {
7750
7793
  now: Math.floor(Date.now() / 6e4) * 6e4
7751
- });
7752
- } catch {
7753
- ritualsFetchFailed = true;
7754
- }
7794
+ }),
7795
+ kernelQuery2("chain.getBindingGovernanceView", {})
7796
+ ]);
7797
+ const ritualsFetchFailed = ritualsSettled.status === "rejected";
7798
+ const rituals = ritualsSettled.status === "fulfilled" ? ritualsSettled.value : null;
7799
+ const ruleListsFetchFailed = bindingGovernanceSettled.status === "rejected";
7800
+ const ruleLists = bindingGovernanceSettled.status === "fulfilled" ? bindingGovernanceSettled.value?.ruleLists : void 0;
7755
7801
  const ritualsLines = buildRitualsSurfaceLines(rituals, { fetchFailed: ritualsFetchFailed });
7802
+ const ruleListsBlock = ruleListsFetchFailed ? `${RULE_LISTS_UNAVAILABLE_LINE}
7803
+
7804
+ ` : ruleLists !== void 0 ? `${buildEveryoneOnlyRuleListsLines(ruleLists).join("\n")}
7805
+
7806
+ ` : "";
7756
7807
  return {
7757
7808
  content: [{
7758
7809
  type: "text",
7759
- text: "Already oriented. Session active, writes unlocked.\n" + ritualsLines.join("\n") + "\n\n" + taskGroundingWarningLines2.join("\n") + "Use `orient mode='full'` for workspace context or `orient task='...'` before substantive work."
7810
+ text: "Already oriented. Session active, writes unlocked.\n" + ritualsLines.join("\n") + "\n\n" + ruleListsBlock + taskGroundingWarningLines2.join("\n") + "Use `orient mode='full'` for workspace context or `orient task='...'` before substantive work."
7760
7811
  }],
7761
7812
  structuredContent: success(
7762
7813
  "Already oriented. Session active, writes unlocked.",
@@ -7775,7 +7826,21 @@ async function buildAlreadyOrientedResult(kernelQuery2, agentSessionId, taskGrou
7775
7826
  // atCapLine renders in TEXT (buildRitualsSurfaceLines) but was never mirrored into
7776
7827
  // structuredContent — a structured-content consumer silently missed the dropped-at-cap
7777
7828
  // warning even when the same response's text carried it.
7778
- ...rituals?.atCapLine !== void 0 ? { atCapLine: rituals.atCapLine } : {}
7829
+ ...rituals?.atCapLine !== void 0 ? { atCapLine: rituals.atCapLine } : {},
7830
+ // Pre-review fix 1 (TEN-3912): team/role dropped from THIS path's payload — they are
7831
+ // always trivial here (see the doc comment above), never this session's real rules —
7832
+ // and `ruleListsScope` says explicitly why, rather than shipping a shape a consumer could
7833
+ // misread as "this session has no team/role rules".
7834
+ ...ruleLists ? {
7835
+ ruleLists: { everyone: ruleLists.everyone, unassigned: ruleLists.unassigned, truncated: ruleLists.truncated },
7836
+ ruleListsScope: "everyone-only"
7837
+ } : {},
7838
+ // Round 1 review (PRRT_kwDORS4ils6mOeU8, TEN-3912): explicit `true` only on the rejection
7839
+ // above, mirroring the existing `fetchFailed`/`groundingStub`-style boolean flags on this
7840
+ // same payload (present-and-true means "this happened", omitted means "it didn't") — never
7841
+ // a bare `false`, never inferred from `ruleLists` being absent (a genuine no-row read also
7842
+ // leaves it undefined).
7843
+ ...ruleListsFetchFailed ? { ruleListsFetchFailed: true } : {}
7779
7844
  }
7780
7845
  )
7781
7846
  };
@@ -7849,42 +7914,6 @@ function replaceVocabTokens(body, workspaceCtx, collectionCtxMap) {
7849
7914
  });
7850
7915
  }
7851
7916
 
7852
- // src/lib/ruleListRender.ts
7853
- var RULE_LIST_CONTROL_CHARS_RE = /[\x00-\x08\x0b-\x1f\x7f]/g;
7854
- function sanitizeRuleListField(value) {
7855
- return value.replace(RULE_LIST_CONTROL_CHARS_RE, "").replace(/\s+/g, " ").trim();
7856
- }
7857
- function formatRuleListRow(row) {
7858
- const entryId = sanitizeRuleListField(row.entryId);
7859
- const owner = row.ownerRoleId ? sanitizeRuleListField(row.ownerRoleId) : "\u2014";
7860
- const team = row.teamId ? ` (team ${sanitizeRuleListField(row.teamId)})` : "";
7861
- const servedAt = row.servedAt ? ` \u2014 served ${new Date(row.servedAt).toISOString()}` : "";
7862
- return `${entryId} \u2014 owner ${owner}${team}${servedAt}`;
7863
- }
7864
- function formatRuleListBucketLines(label, bucket, truncated) {
7865
- if (!bucket || bucket.rows.length === 0) return [];
7866
- const lines = [`${label}:`, ...bucket.rows.map((r) => ` - ${formatRuleListRow(r)}`)];
7867
- if (bucket.overCap > 0) lines.push(` (${truncated ? "at least " : ""}+${bucket.overCap} more, capped)`);
7868
- return lines;
7869
- }
7870
- function formatRuleListsLines(ruleLists) {
7871
- if (!ruleLists) return [];
7872
- const lines = [
7873
- ...formatRuleListBucketLines("Everyone", ruleLists.everyone, ruleLists.truncated),
7874
- ...formatRuleListBucketLines("Team", ruleLists.team, ruleLists.truncated),
7875
- ...formatRuleListBucketLines("Role", ruleLists.role, ruleLists.truncated)
7876
- ];
7877
- if (lines.length === 0 && ruleLists.unassigned === 0 && !ruleLists.truncated) return [];
7878
- if (ruleLists.truncated) {
7879
- lines.push("(Some Current rules may be missing from these lists \u2014 the underlying read was truncated.)");
7880
- }
7881
- if (ruleLists.unassigned > 0) {
7882
- const atLeast = ruleLists.truncated ? "at least " : "";
7883
- lines.push(`(${atLeast}${ruleLists.unassigned} rule(s) opted into a list but could not be resolved \u2014 excluded, not silently dropped.)`);
7884
- }
7885
- return lines;
7886
- }
7887
-
7888
7917
  // src/tools/start_pb.ts
7889
7918
  import { z as z18 } from "zod/v3";
7890
7919
 
@@ -8512,6 +8541,8 @@ async function _handleOrient({ mode = "full", tier, task, startupSignals, invoca
8512
8541
  "Full onboarding is optional: call `orient action=start` if you want guided workspace setup. If you already ran setup, continue your task without restarting it."
8513
8542
  ];
8514
8543
  scanLines.push(...buildRitualsSurfaceLines(orientView, { fetchFailed: orientFetchFailed || orientView?.fetchFailed === true }));
8544
+ const blankRuleLines = formatRuleListsLines(orientView?.ruleLists);
8545
+ if (blankRuleLines.length > 0) scanLines.push("", "Rules:", ...blankRuleLines);
8515
8546
  let oriented = false;
8516
8547
  let orientationStatus2 = "no_session";
8517
8548
  if (agentSessionId) {
@@ -8524,11 +8555,12 @@ async function _handleOrient({ mode = "full", tier, task, startupSignals, invoca
8524
8555
  orientationStatus2 = "failed";
8525
8556
  }
8526
8557
  }
8558
+ const blankRuleListsExtra = orientView?.ruleLists ? { ruleLists: orientView.ruleLists } : {};
8527
8559
  const blankResult = {
8528
8560
  content: [{ type: "text", text: scanLines.join("\n") }],
8529
8561
  structuredContent: success(
8530
8562
  "No accepted user-authored entries found. Follow the spine guidance for your task; full onboarding is optional.",
8531
- { oriented, orientationStatus: orientationStatus2, redirectHint: "Optional full onboarding: `orient action=start`. Continue an existing task using the spine guidance." },
8563
+ { oriented, orientationStatus: orientationStatus2, redirectHint: "Optional full onboarding: `orient action=start`. Continue an existing task using the spine guidance.", ...blankRuleListsExtra },
8532
8564
  task ? [] : [{ tool: "orient", description: "Optional full workspace onboarding", parameters: { action: "start" } }]
8533
8565
  )
8534
8566
  };
@@ -8968,8 +9000,6 @@ async function _handleOrient({ mode = "full", tier, task, startupSignals, invoca
8968
9000
  businessRules: (gov.businessRules ?? []).map(mapGovernanceEntry)
8969
9001
  }, task));
8970
9002
  }
8971
- const ruleLines2 = formatRuleListsLines(orientView?.ruleLists);
8972
- if (ruleLines2.length > 0) lines.push("", "Rules:", ...ruleLines2);
8973
9003
  let allEntries = [];
8974
9004
  try {
8975
9005
  allEntries = await kernelQuery("chain.listEntries", {
@@ -9040,6 +9070,8 @@ async function _handleOrient({ mode = "full", tier, task, startupSignals, invoca
9040
9070
  lines.push("");
9041
9071
  }
9042
9072
  }
9073
+ const ruleLinesFull = formatRuleListsLines(orientView?.ruleLists);
9074
+ if (ruleLinesFull.length > 0) lines.push("", "Rules:", ...ruleLinesFull);
9043
9075
  const classificationGaps = readiness?.gaps?.filter((g) => g.id?.startsWith?.(PURPOSE_GAP_PREFIX)) ?? [];
9044
9076
  if (classificationGaps.length > 0) {
9045
9077
  lines.push("## Classification gaps");
@@ -14098,11 +14130,16 @@ All entries: \`draft\` | \`active\` | \`deprecated\` | \`archived\`
14098
14130
  `;
14099
14131
 
14100
14132
  // src/resources/orientationMarkdown.ts
14101
- function buildOrientationMarkdown(collections, standards, businessRules, principles, directionLines) {
14133
+ function buildOrientationMarkdown(collections, standards, businessRules, principles, directionLines, ruleLines) {
14102
14134
  const sections = ["# Product Brain \u2014 Orientation"];
14103
14135
  if (directionLines && directionLines.length > 0) {
14104
14136
  sections.push(directionLines.join("\n"));
14105
14137
  }
14138
+ if (ruleLines && ruleLines.length > 0) {
14139
+ sections.push(
14140
+ "## Rules (delivered lists)\nEveryone/team/role rules currently served to this workspace and role (DEC-1774) \u2014 distinct from the collection-listing section below.\n\n" + ruleLines.join("\n")
14141
+ );
14142
+ }
14106
14143
  sections.push(
14107
14144
  "## About Product Brain (PB)\nPB is a knowledge management system used by many product teams. This section describes how PB works \u2014 not your product.\n\n### How PB Organizes Its Tools\n- **Tools** = actions with side-effects or dynamic computation (capture, accept, search).\n- **Resources** = stable, read-only data (orientation, terminology, collection schemas).\n- **Prompts** = multi-step choreography (workflows, guided capture, deep dives).\n- Every tool, resource, and prompt works for ANY workspace \u2014 no bespoke logic.\n- Resource templates (URI params) serve workspace-specific data through generic patterns.\n- Compound tools with `action` enums keep the tool count minimal.\n\n" + DATA_HANDLING_SECTION
14108
14145
  );
@@ -14117,7 +14154,7 @@ function buildOrientationMarkdown(collections, standards, businessRules, princip
14117
14154
  wsRules.push({ id: r.entryId ?? "", name: r.name, severity: r.data?.severity ?? void 0, source: "business-rules" });
14118
14155
  }
14119
14156
  if (wsRules.length > 0) {
14120
- const ruleLines = wsRules.map((r) => {
14157
+ const ruleLines2 = wsRules.map((r) => {
14121
14158
  const sev = r.severity ? ` [${r.severity}]` : "";
14122
14159
  return `- **${r.id}**: ${r.name}${sev}`;
14123
14160
  }).join("\n");
@@ -14125,7 +14162,7 @@ function buildOrientationMarkdown(collections, standards, businessRules, princip
14125
14162
  `## Your Workspace Principles & Rules (${wsRules.length} active)
14126
14163
  These are principles, standards, and rules your team has accepted onto the Chain. Respect them during implementation.
14127
14164
 
14128
- ` + ruleLines + '\n\nUse `entries action=get entryId="<ID>"` to drill into any rule before making changes in that area.'
14165
+ ` + ruleLines2 + '\n\nUse `entries action=get entryId="<ID>"` to drill into any rule before making changes in that area.'
14129
14166
  );
14130
14167
  } else {
14131
14168
  sections.push(
@@ -14193,7 +14230,7 @@ Use \`entries action=update\` for post-creation adjustments (status changes, fie
14193
14230
  );
14194
14231
  return sections.join("\n\n---\n\n");
14195
14232
  }
14196
- async function buildOrientationResourceText(kernelQuery2) {
14233
+ async function buildOrientationResourceText(kernelQuery2, agentSessionId) {
14197
14234
  const [collectionsResult, standardsResult, rulesResult, principlesResult, orientViewResult] = await Promise.allSettled([
14198
14235
  kernelQuery2("chain.listCollections"),
14199
14236
  kernelQuery2("chain.listEntries", { collectionSlug: "standards" }),
@@ -14201,7 +14238,8 @@ async function buildOrientationResourceText(kernelQuery2) {
14201
14238
  kernelQuery2("chain.listEntries", { collectionSlug: "principles" }),
14202
14239
  kernelQuery2("chain.getOrientView", {
14203
14240
  tier: "summary",
14204
- invocationPath: "unknown"
14241
+ invocationPath: "unknown",
14242
+ ...agentSessionId ? { sessionId: agentSessionId } : {}
14205
14243
  })
14206
14244
  ]);
14207
14245
  const collections = collectionsResult.status === "fulfilled" ? collectionsResult.value : null;
@@ -14209,6 +14247,7 @@ async function buildOrientationResourceText(kernelQuery2) {
14209
14247
  const businessRules = rulesResult.status === "fulfilled" ? rulesResult.value : null;
14210
14248
  const principles = principlesResult.status === "fulfilled" ? principlesResult.value : null;
14211
14249
  let directionLines;
14250
+ let ruleLines = [];
14212
14251
  if (orientViewResult.status === "rejected") {
14213
14252
  const reason = orientViewResult.reason instanceof Error ? orientViewResult.reason.message : String(orientViewResult.reason);
14214
14253
  const normalized = sanitizeSingleLineField(reason);
@@ -14217,8 +14256,9 @@ async function buildOrientationResourceText(kernelQuery2) {
14217
14256
  } else {
14218
14257
  const orientView = orientViewResult.value;
14219
14258
  directionLines = buildDirectionLines(orientView);
14259
+ ruleLines = agentSessionId ? formatRuleListsLines(orientView.ruleLists) : buildEveryoneOnlyRuleListsLines(orientView.ruleLists);
14220
14260
  }
14221
- return buildOrientationMarkdown(collections, standards, businessRules, principles, directionLines);
14261
+ return buildOrientationMarkdown(collections, standards, businessRules, principles, directionLines, ruleLines);
14222
14262
  }
14223
14263
 
14224
14264
  // src/resources/index.ts
@@ -14257,7 +14297,8 @@ function registerResources(server) {
14257
14297
  "chain-orientation",
14258
14298
  "productbrain://orientation",
14259
14299
  async (uri) => ({
14260
- contents: [{ uri: uri.href, text: await buildOrientationResourceText(kernelQuery), mimeType: "text/markdown" }]
14300
+ contents: [{ uri: uri.href, text: await buildOrientationResourceText(kernelQuery, getAgentSessionId()), mimeType: "text/markdown" }]
14301
+ // TEN-3912 fix 2
14261
14302
  })
14262
14303
  );
14263
14304
  server.resource(
@@ -14870,4 +14911,4 @@ export {
14870
14911
  createProductBrainServer,
14871
14912
  initFeatureFlags
14872
14913
  };
14873
- //# sourceMappingURL=chunk-57YGCYCX.js.map
14914
+ //# sourceMappingURL=chunk-LKAOXJNJ.js.map