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

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.
@@ -1554,6 +1554,8 @@ var groundingGovernanceEntrySchema = z4.object({
1554
1554
  collectionSlug: z4.string()
1555
1555
  });
1556
1556
  var groundingReportSchema = z4.object({
1557
+ // TASK-156: null preserves uncertainty for unavailable checks or an older server response.
1558
+ scanTruncated: z4.boolean().nullable(),
1557
1559
  related: z4.array(groundingRelatedEntrySchema),
1558
1560
  duplicates: z4.array(groundingDuplicateEntrySchema),
1559
1561
  governance: z4.array(groundingGovernanceEntrySchema)
@@ -1680,6 +1682,9 @@ function reconcileBatchPreviewDuplicate(tracker, args) {
1680
1682
  return null;
1681
1683
  }
1682
1684
 
1685
+ // src/lib/readinessNotices.ts
1686
+ var READINESS_PROVISIONAL_LINE = "Readiness is provisional: its sample omitted accepted entries. Counts are lower bounds, apparent gaps may already be covered, and the overall stage is unverified.";
1687
+
1683
1688
  // src/tools/smart-capture.ts
1684
1689
  var GENERIC_PROFILE = {
1685
1690
  governedDraft: false,
@@ -2265,9 +2270,8 @@ async function handleCapture(server, { collection, name, description, context, e
2265
2270
  };
2266
2271
  }
2267
2272
  const profile = await getProfile(resolvedCollection);
2268
- const skipAutoDiscoveryEarly = links && links.length > 0;
2269
2273
  const groundingStart = Date.now();
2270
- const groundingPromise = !skipAutoDiscoveryEarly && (name || description) ? kernelQuery("chain.suggestLinksForCapture", {
2274
+ const groundingPromise = name || description ? kernelQuery("chain.suggestLinksForCapture", {
2271
2275
  entries: [{ name, description: description ?? "", collectionHint: resolvedCollection }],
2272
2276
  limit: 5,
2273
2277
  threshold: 2
@@ -2286,6 +2290,8 @@ async function handleCapture(server, { collection, name, description, context, e
2286
2290
  collectionSlug: g.collectionSlug
2287
2291
  }));
2288
2292
  const groundingReport = {
2293
+ // null preserves uncertainty for unavailable checks or an older server response.
2294
+ scanTruncated: typeof groundingEntry?.scanTruncated === "boolean" ? groundingEntry.scanTruncated : null,
2289
2295
  related: groundingRelated.map((r) => ({
2290
2296
  entryId: r.entryId,
2291
2297
  name: r.name,
@@ -2303,9 +2309,10 @@ async function handleCapture(server, { collection, name, description, context, e
2303
2309
  })),
2304
2310
  governance: groundingGovernance
2305
2311
  };
2312
+ const groundingCaveat = !groundingEntry ? "Grounding could not be completed; duplicate presence is unknown. Review existing entries before creating another." : groundingReport.scanTruncated !== false ? "Grounding covered a bounded sample; completeness is unverified and additional matching entries may exist. Review existing entries before creating another." : "";
2306
2313
  if (suggestOnly) {
2307
2314
  const hasSuggestions = groundingReport.related.length > 0 || groundingReport.duplicates.length > 0;
2308
- const summaryText = hasSuggestions ? `Grounding preview for "${name}": ${groundingReport.related.length} related, ${groundingReport.duplicates.length} possible duplicates. No entry created.` : `Grounding preview for "${name}": no similar entries found. Safe to capture.`;
2315
+ const summaryText = hasSuggestions ? `Grounding preview for "${name}": ${groundingReport.related.length} related, ${groundingReport.duplicates.length} possible duplicates. No entry created.` : groundingEntry ? `Grounding preview for "${name}": no similar entries found in this check. No entry created.` : `Grounding preview for "${name}": duplicate check unavailable. No entry created.`;
2309
2316
  return {
2310
2317
  content: [{
2311
2318
  type: "text",
@@ -2313,7 +2320,9 @@ async function handleCapture(server, { collection, name, description, context, e
2313
2320
 
2314
2321
  ${summaryText}
2315
2322
 
2316
- ` + (groundingReport.related.length > 0 ? `## Related entries
2323
+ ` + (groundingCaveat ? `${groundingCaveat}
2324
+
2325
+ ` : "") + (groundingReport.related.length > 0 ? `## Related entries
2317
2326
  ${groundingReport.related.map((r) => `- **${r.entryId}** ${r.name} [${r.collectionSlug}] \u2014 ${r.reasoning}`).join("\n")}
2318
2327
 
2319
2328
  ` : "") + (groundingReport.duplicates.length > 0 ? `## Possible duplicates
@@ -2569,7 +2578,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2569
2578
  const linksSuggested = [];
2570
2579
  const userLinkResults = [];
2571
2580
  const advisedLinks = [];
2572
- let conflictCandidates = [];
2581
+ const conflictCandidates = groundingReport.duplicates.filter((candidate) => candidate.matchType === "possible_duplicate" && candidate.entryId !== finalEntryId).slice(0, 3).map((candidate) => ({ entryId: candidate.entryId, name: candidate.name, collection: candidate.collectionSlug }));
2573
2582
  const pendingRelations = [];
2574
2583
  const skipAutoDiscovery = links && links.length > 0;
2575
2584
  const searchQuery = extractSearchTerms(name, description);
@@ -2646,7 +2655,6 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2646
2655
  const reason = c.confidence >= AUTO_LINK_CONFIDENCE_THRESHOLD ? "high relevance (already linked)" : `"${c.name.toLowerCase().split(/\s+/).filter((w) => `${name} ${description}`.toLowerCase().includes(w) && w.length > 3).slice(0, 2).join('", "')}" appears in content`;
2647
2656
  linksSuggested.push({ entryId: c.entryId, name: c.name, collection: c.collSlug, reason, preview: c.preview });
2648
2657
  }
2649
- conflictCandidates = candidates.filter((c) => c.entryId && c.entryId !== finalEntryId).slice(0, 3).map((c) => ({ entryId: c.entryId, name: c.name, collection: c.collSlug }));
2650
2658
  }
2651
2659
  if (links && links.length > 0 && finalEntryId) {
2652
2660
  for (const link of links) {
@@ -2678,8 +2686,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2678
2686
  entryWarnings.push(`Auto-linking partially failed: ${err instanceof Error ? err.message : "unknown error"}`);
2679
2687
  return null;
2680
2688
  }) : Promise.resolve(null);
2681
- const cardinalityPromise = resolvedCK ? kernelQuery("chain.checkCardinalityWarning", { canonicalKey: resolvedCK }).catch(() => null) : Promise.resolve(null);
2682
- const contradictionPromise = runContradictionCheck(name, description);
2689
+ const cardinalityPromise = resolvedCK ? kernelQuery("chain.checkCardinalityWarning", { canonicalKey: resolvedCK, excludeEntryId: internalId }).catch(() => null) : Promise.resolve(null);
2690
+ const contradictionPromise = runContradictionCheck(name, description, finalEntryId, internalId);
2683
2691
  const coachingPromise = coachingFromCreate !== void 0 ? Promise.resolve(coachingFromCreate) : kernelMutation("quality.evaluateHeuristicAndSchedule", {
2684
2692
  entryId: finalEntryId,
2685
2693
  context: "capture"
@@ -2769,13 +2777,6 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2769
2777
  }
2770
2778
  const freshGovernanceMode = await refreshWorkspaceGovernanceMode();
2771
2779
  const shouldAutoCommit = shouldAutoCommitCapture(autoCommit, freshGovernanceMode);
2772
- if (shouldAutoCommit && finalEntryId && conflictCandidates.length === 0) {
2773
- try {
2774
- const conflictResults = await kernelQuery("chain.searchEntries", { query: name });
2775
- conflictCandidates = (conflictResults ?? []).filter((r) => r.entryId && r.entryId !== finalEntryId && r._id !== internalId).slice(0, 3).map((r) => ({ entryId: r.entryId, name: r.name, collection: r.collectionSlug ?? "unknown" }));
2776
- } catch {
2777
- }
2778
- }
2779
2780
  let finalStatus = "draft";
2780
2781
  let commitError = null;
2781
2782
  let commitRefusal = null;
@@ -2925,6 +2926,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2925
2926
  lines.push(`${i + 1}. **${s.entryId ?? "(no ID)"}**: ${s.name} [${s.collection}]${preview2}`);
2926
2927
  }
2927
2928
  }
2929
+ if (groundingCaveat) lines.push("", groundingCaveat);
2928
2930
  if (format === "human") {
2929
2931
  const hasDuplicates = groundingReport.duplicates.length > 0;
2930
2932
  const hasRelated = groundingReport.related.length > 0;
@@ -2962,11 +2964,11 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2962
2964
  for (const rs of relationSuggestionsFromCreate) {
2963
2965
  lines.push(`- **${rs.relationType}** \u2192 ${rs.targetEntryId} "${rs.targetName}" (confidence: ${rs.confidence})`);
2964
2966
  }
2965
- lines.push(`_Create with: \`relations action=create fromId="${finalEntryId}" toId="TARGET-ID" type="TYPE"\`_`);
2967
+ lines.push(`_Create with: \`relations action=create from="${finalEntryId}" to="TARGET-ID" type="TYPE"\`_`);
2966
2968
  }
2967
2969
  if (authorityDomainFromCreate) {
2968
2970
  lines.push("");
2969
- lines.push(`domain: ${authorityDomainFromCreate.slug} (proposal pending \u2014 ratify at session close)`);
2971
+ lines.push(`domain: ${authorityDomainFromCreate.slug} (proposal pending \u2014 closing the session does not ratify it. A workspace owner or admin can review it with \`pb authority-domains review\` before deciding whether to ratify.)`);
2970
2972
  }
2971
2973
  if (formativeHints.length > 0) {
2972
2974
  trackCaptureQualityHints(wsCtx.workspaceId, {
@@ -3001,11 +3003,11 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
3001
3003
  }
3002
3004
  if (contradictionWarnings.length > 0) {
3003
3005
  lines.push("");
3004
- lines.push("\u26A0 Contradiction check: proposed entry matched existing governance entries:");
3006
+ lines.push("\u26A0 Contradiction check: captured entry matched existing governance entries:");
3005
3007
  for (const w of contradictionWarnings) {
3006
3008
  lines.push(`- ${w.name} (${w.collection}, ${w.entryId}) \u2014 has 'governs' relation to ${w.governsCount} entries`);
3007
3009
  }
3008
- lines.push("Run `context action=gather` on these entries before accepting.");
3010
+ lines.push("Review the matched governance entries and reconcile any actual conflict.");
3009
3011
  }
3010
3012
  if (entryWarnings.length > 0) {
3011
3013
  lines.push("");
@@ -3043,7 +3045,9 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
3043
3045
  }
3044
3046
  try {
3045
3047
  const readiness = await kernelQuery("chain.workspaceReadiness");
3046
- if (readiness && readiness.gaps && readiness.gaps.length > 0) {
3048
+ if (readiness?.sampleTruncated) {
3049
+ lines.push("", `## Workspace Readiness: ${readiness.score}% (provisional sample)`, READINESS_PROVISIONAL_LINE);
3050
+ } else if (readiness && readiness.gaps && readiness.gaps.length > 0) {
3047
3051
  const topGaps = readiness.gaps.slice(0, 2);
3048
3052
  lines.push("");
3049
3053
  lines.push(`## Workspace Readiness: ${readiness.score}%`);
@@ -3694,10 +3698,10 @@ _Use \`entries action=move\` to correct any misclassified entries._`);
3694
3698
  const entriesWithOverlaps = created.filter((r) => r.overlapCount && r.overlapCount > 0);
3695
3699
  if (entriesWithOverlaps.length > 0) {
3696
3700
  lines.push("");
3697
- lines.push("## Potential Overlaps");
3698
- lines.push(`${entriesWithOverlaps.length} ${entriesWithOverlaps.length === 1 ? "entry has" : "entries have"} similar existing entries. Consider reviewing for duplicates:`);
3701
+ lines.push("## Related-entry Suggestions");
3702
+ lines.push(`${entriesWithOverlaps.length} ${entriesWithOverlaps.length === 1 ? "entry has" : "entries have"} link candidates. Review their relevance before linking:`);
3699
3703
  for (const r of entriesWithOverlaps) {
3700
- lines.push(`- **${r.entryId}**: ${r.name} \u2014 ${r.overlapCount} similar ${r.overlapCount === 1 ? "entry" : "entries"}`);
3704
+ lines.push(`- **${r.entryId}**: ${r.name} \u2014 ${r.overlapCount} link ${r.overlapCount === 1 ? "candidate" : "candidates"}`);
3701
3705
  }
3702
3706
  }
3703
3707
  }
@@ -3865,7 +3869,7 @@ async function recordCommitFailure({
3865
3869
  }
3866
3870
  }
3867
3871
  }
3868
- async function runContradictionCheck(name, description) {
3872
+ async function runContradictionCheck(name, description, excludeEntryId, excludeDocId) {
3869
3873
  const warnings = [];
3870
3874
  try {
3871
3875
  const keyTerms = tokenizeText(`${name} ${description}`).filter((w) => w.length >= 4 && !STOP_WORDS.has(w)).slice(0, 8);
@@ -3875,7 +3879,13 @@ async function runContradictionCheck(name, description) {
3875
3879
  kernelQuery("chain.searchEntries", { query: searchQuery, collectionSlug: "business-rules" }),
3876
3880
  kernelQuery("chain.searchEntries", { query: searchQuery, collectionSlug: "architecture" })
3877
3881
  ]);
3878
- const allGov = [...govResults ?? [], ...archResults ?? []].slice(0, 5);
3882
+ const seen = /* @__PURE__ */ new Set();
3883
+ const allGov = [...govResults ?? [], ...archResults ?? []].filter((entry) => {
3884
+ const id = entry.entryId ?? entry._id;
3885
+ if (!id || excludeEntryId && entry.entryId === excludeEntryId || excludeDocId && entry._id === excludeDocId || seen.has(id)) return false;
3886
+ seen.add(id);
3887
+ return true;
3888
+ }).slice(0, 5);
3879
3889
  for (const entry of allGov) {
3880
3890
  const entryTokens = new Set(tokenizeText(`${entry.name} ${entry.data?.description ?? ""}`));
3881
3891
  const matched = keyTerms.filter((t) => entryTokens.has(t));
@@ -4859,7 +4869,7 @@ var entriesSchema = z10.object({
4859
4869
  entryIds: z10.array(z10.string().max(200)).min(1).max(20).optional().describe("Entry IDs for 'batch', e.g. ['TYPE-strategy', 'STR-jljeg7']"),
4860
4870
  collection: z10.string().max(200).optional().describe("Collection slug \u2014 for 'list'/'search': scope filter, e.g. 'glossary', 'tracking-events'. For 'preflight': the collection the entry would be composed into (required)."),
4861
4871
  status: entriesStatusFilterSchema.optional().describe(
4862
- "For 'list': one lifecycle value (draft|active|accepted|deprecated|archived; active/accepted are the same trust level). For 'search': one value OR an array of up to 5 (e.g. ['active','accepted']). For 'update': draft|active|deprecated|archived ONLY \u2014 'accepted' is set via 'commit', never 'update'. Workflow values use `workflowStatus` instead."
4872
+ "For 'list': defaults to accepted entries (active/accepted); use status='draft' to discover drafts. One lifecycle value (draft|active|accepted|deprecated|archived). For 'search': one value OR an array of up to 5 (e.g. ['active','accepted']). For 'update': draft|active|deprecated|archived ONLY \u2014 'accepted' is set via 'commit', never 'update'. Workflow values use `workflowStatus` instead."
4863
4873
  ),
4864
4874
  tag: z10.string().max(200).optional().describe("For 'list': filter by internal tag."),
4865
4875
  label: z10.string().max(200).optional().describe("For 'list': filter by label slug \u2014 matches entries across all collections."),
@@ -4943,7 +4953,7 @@ var entriesActionUnion = z10.discriminatedUnion("action", [
4943
4953
  ]);
4944
4954
  var ENTRIES_ACTION_SPECS = {
4945
4955
  preflight: { params: ["collection", "canonicalKey", "links"], description: "Inspect structure before composition; no writes or permission grant." },
4946
- list: { params: ["collection", "status", "tag", "label"], description: "Browse entries; all params optional." },
4956
+ list: { params: ["collection", "status", "tag", "label"], description: "Browse accepted entries by default; status='draft' lists drafts. All params optional." },
4947
4957
  get: { params: ["entryId"], description: "Fetch one entry by ID." },
4948
4958
  batch: { params: ["entryIds"], description: "Fetch up to 20 entries by ID." },
4949
4959
  search: { params: ["query", "collection", "status"], description: "Full-text search; query is required (min 2 chars)." },
@@ -5337,7 +5347,7 @@ async function handleBatch(entryIds) {
5337
5347
  async function handleList(collection, status2, tag, label) {
5338
5348
  let entries;
5339
5349
  if (label) {
5340
- entries = await kernelQuery("chain.listEntriesByLabel", { labelSlug: label });
5350
+ entries = await kernelQuery("chain.listEntriesByLabel", { labelSlug: label, acceptedOnly: !status2 });
5341
5351
  if (status2) {
5342
5352
  entries = entries.filter((e) => e.status === status2);
5343
5353
  }
@@ -5349,10 +5359,11 @@ async function handleList(collection, status2, tag, label) {
5349
5359
  });
5350
5360
  }
5351
5361
  if (entries.length === 0) {
5362
+ const emptyMessage = `No entries match the given filters.${!status2 ? " Default list scope is accepted entries (active/accepted); use status='draft' to discover drafts." : ` Status filter: ${status2}.`}`;
5352
5363
  return {
5353
- content: [{ type: "text", text: "No entries match the given filters." }],
5364
+ content: [{ type: "text", text: emptyMessage }],
5354
5365
  structuredContent: success(
5355
- "No entries match the given filters.",
5366
+ emptyMessage,
5356
5367
  { entries: [], total: 0 },
5357
5368
  [{ tool: "collections", description: "List collections", parameters: { action: "list" } }]
5358
5369
  )
@@ -7210,8 +7221,9 @@ function formatSteeringBrief(result2) {
7210
7221
  lines.push(`${delta.newEntries.length} new entries since last steering brief${delta.truncated ? " (TRUNCATED)" : ""}`);
7211
7222
  lines.push("");
7212
7223
  lines.push("## Workspace Readiness");
7213
- lines.push(`Score: **${readiness.score}%** (${readiness.passedChecks}/${readiness.totalChecks} checks passed)`);
7214
- if (readiness.gaps.length > 0) {
7224
+ lines.push(`Score: **${readiness.score}%**${readiness.sampleTruncated ? " (provisional sample)" : ""} (${readiness.passedChecks}/${readiness.totalChecks} checks passed)`);
7225
+ if (readiness.sampleTruncated) lines.push(READINESS_PROVISIONAL_LINE);
7226
+ if (!readiness.sampleTruncated && readiness.gaps.length > 0) {
7215
7227
  lines.push("");
7216
7228
  lines.push("**Gaps:**");
7217
7229
  for (const gap of readiness.gaps.slice(0, 5)) {
@@ -7228,7 +7240,7 @@ function formatSteeringBrief(result2) {
7228
7240
  return {
7229
7241
  content: [{ type: "text", text: lines.join("\n") }],
7230
7242
  structuredContent: success(
7231
- `Steering brief: ${changes.entries.length} changes, ${delta.newEntries.length} new entries, ${readiness.score}% readiness.`,
7243
+ `Steering brief: ${changes.entries.length} changes, ${delta.newEntries.length} new entries, ${readiness.score}% readiness${readiness.sampleTruncated ? " (provisional sample; gaps unverified)" : ""}.`,
7232
7244
  { ...result2, trustFilterActive: true }
7233
7245
  )
7234
7246
  };
@@ -8745,19 +8757,30 @@ function sanitizeSingleLineField(str) {
8745
8757
  }
8746
8758
 
8747
8759
  // src/tools/orient-rituals.ts
8760
+ var SPINE_UNAVAILABLE_LINE = "Spine check: unavailable \u2014 orient fetch failed, retry `orient`";
8748
8761
  function buildRitualsSurfaceLines(view, opts) {
8749
8762
  const lines = [""];
8750
8763
  if (opts?.fetchFailed) {
8751
8764
  lines.push("Direction: unavailable \u2014 orient fetch failed, retry `orient`");
8752
- lines.push("Spine check: unavailable \u2014 orient fetch failed, retry `orient`");
8765
+ lines.push(SPINE_UNAVAILABLE_LINE);
8753
8766
  return lines;
8754
8767
  }
8755
8768
  lines.push(view?.directionLine ?? "Direction: not computed yet \u2014 `pb direction refresh`");
8756
8769
  if (view?.verdictAskLine) lines.push(view.verdictAskLine);
8757
- lines.push(view?.spineLine ?? "Spine check: not computed yet \u2014 `pb quality spine-check`");
8770
+ if (view?.spineLine) lines.push(view.spineLine);
8758
8771
  if (view?.atCapLine) lines.push(view.atCapLine);
8759
8772
  return lines;
8760
8773
  }
8774
+ async function fetchAndRenderSetupSpineLine(kernelQuery2) {
8775
+ try {
8776
+ const view = await kernelQuery2("chain.getRitualsSurface", {
8777
+ now: Math.floor(Date.now() / 6e4) * 6e4
8778
+ });
8779
+ return view?.spineLine ? [view.spineLine] : [];
8780
+ } catch {
8781
+ return [SPINE_UNAVAILABLE_LINE];
8782
+ }
8783
+ }
8761
8784
  function toSafeCodeSpan(text) {
8762
8785
  const longestBacktickRun = (text.match(/`+/g) ?? []).reduce((max, run) => Math.max(max, run.length), 0);
8763
8786
  const fence = "`".repeat(longestBacktickRun + 1);
@@ -9096,15 +9119,18 @@ function pickNextTensionPrompt(openTensions) {
9096
9119
  cta: "Want to discuss this tension or capture a decision about it?"
9097
9120
  };
9098
9121
  }
9099
- async function buildSetupResponse(agentSessionId) {
9122
+ async function buildSetupResponse(agentSessionId, sampleTruncated) {
9100
9123
  await kernelMutation(
9101
9124
  "setup.stampMcpOnlySurface",
9102
9125
  {}
9103
9126
  );
9104
9127
  const skill = await loadSkillBody(PB_SETUP_SKILL_ENTRY_ID);
9105
9128
  const { oriented, orientationStatus } = await tryMarkOriented(agentSessionId);
9129
+ const spineLines = await fetchAndRenderSetupSpineLine(kernelQuery);
9106
9130
  return {
9107
- text: skill.body,
9131
+ text: `${skill.body}${spineLines.length > 0 ? `
9132
+ ${spineLines.join("\n")}` : ""}${sampleTruncated ? `
9133
+ ${READINESS_PROVISIONAL_LINE} Reuse existing knowledge when following setup.` : ""}`,
9108
9134
  structuredContent: success(
9109
9135
  `pb-setup ready. Delivering SKILL-pb-setup body (${skill.body.length} chars).`,
9110
9136
  {
@@ -9112,9 +9138,11 @@ async function buildSetupResponse(agentSessionId) {
9112
9138
  skillEntryId: skill.entryId,
9113
9139
  skillBody: skill.body,
9114
9140
  stage: "setup",
9141
+ sampleTruncated,
9115
9142
  oriented,
9116
9143
  orientationStatus,
9117
9144
  ...agentSessionId ? { sessionId: agentSessionId } : {},
9145
+ ...spineLines[0] !== void 0 ? { spineLine: spineLines[0] } : {},
9118
9146
  instructions: "Read skillBody and follow it; use entries/orient MCP tools per PAT-227."
9119
9147
  }
9120
9148
  )
@@ -9149,8 +9177,9 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
9149
9177
  errors.push(`Readiness: ${e instanceof Error ? e.message : String(e)}`);
9150
9178
  }
9151
9179
  const lines = [];
9152
- const isLowReadiness = readiness !== null && readiness.score < 50;
9153
- const isHighReadiness = readiness !== null && readiness.score >= 50;
9180
+ const isLowReadiness = readiness !== null && !readiness.sampleTruncated && readiness.score < 50;
9181
+ if (readiness?.sampleTruncated) lines.push(READINESS_PROVISIONAL_LINE, "");
9182
+ const showExistingContext = readiness !== null && (readiness.sampleTruncated || readiness.score >= 50);
9154
9183
  const stage = readiness?.stage ?? null;
9155
9184
  const captureBehaviorNote = wsCtx.governanceMode === "open" ? "_In Open mode, user-authored captures are accepted immediately unless you ask me to keep them as drafts._" : "_Everything I capture stays pending until you confirm._";
9156
9185
  const coherence = await buildCoherenceSection();
@@ -9208,9 +9237,9 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
9208
9237
  lines.push("");
9209
9238
  }
9210
9239
  }
9211
- lines.push("_Need a collection that doesn't exist yet? Just ask \u2014 I'll set it up._");
9240
+ lines.push("_Need a collection that doesn't exist yet? Creating one is not available through this tool. Ask a platform admin to provision it, or enable the module ontology pack that owns it._");
9212
9241
  lines.push("");
9213
- } else if (isHighReadiness) {
9242
+ } else if (showExistingContext) {
9214
9243
  let activeBets = [];
9215
9244
  try {
9216
9245
  const betsEntries = await kernelQuery("chain.listEntries", { collectionSlug: "work-packages" });
@@ -9276,7 +9305,7 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
9276
9305
  const committedCollections = new Set(committed.map((e) => e.collectionId));
9277
9306
  if (committed.length >= 10 && committedCollections.size >= 3) {
9278
9307
  lines.push("");
9279
- lines.push(`_${committed.length} entries across ${committedCollections.size} collections on the Chain._`);
9308
+ lines.push(`_Context sample: ${committed.length} accepted/active entries across ${committedCollections.size} collections._`);
9280
9309
  }
9281
9310
  } catch {
9282
9311
  }
@@ -9328,13 +9357,13 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
9328
9357
  lines.push("");
9329
9358
  }
9330
9359
  const { oriented, orientationStatus } = await tryMarkOriented(agentSessionId, coherence?.snapshot ?? void 0);
9331
- const stageLabel = stage ?? "active";
9360
+ const stageLabel = `${stage ?? "active"}${readiness?.sampleTruncated ? " (provisional sample)" : ""}`;
9332
9361
  const gapCount = readiness?.gaps?.length ?? 0;
9333
9362
  const summaryParts = [`Product Brain ready. Stage: ${stageLabel}.`];
9334
- if (gapCount > 0) summaryParts.push(`${gapCount} gap${gapCount === 1 ? "" : "s"}.`);
9363
+ if (gapCount > 0) summaryParts.push(`${gapCount} ${readiness?.sampleTruncated ? "potential " : ""}gap${gapCount === 1 ? "" : "s"}${readiness?.sampleTruncated ? " in sampled entries; absence unverified" : ""}.`);
9335
9364
  if (openTensions.length > 0) summaryParts.push(`${openTensions.length} open tension${openTensions.length === 1 ? "" : "s"}.`);
9336
9365
  const next = [];
9337
- if (gapCount > 0) {
9366
+ if (gapCount > 0 && !readiness?.sampleTruncated) {
9338
9367
  next.push({ tool: "capture", description: "Fill a gap", parameters: {} });
9339
9368
  }
9340
9369
  if (openTensions.length > 0) {
@@ -9346,6 +9375,7 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
9346
9375
  summaryParts.join(" "),
9347
9376
  {
9348
9377
  stage,
9378
+ sampleTruncated: readiness?.sampleTruncated ?? null,
9349
9379
  readinessScore: readiness?.score ?? null,
9350
9380
  totalGaps: gapCount,
9351
9381
  openTensions: openTensions.length,
@@ -9381,14 +9411,16 @@ async function handleStartPb({ task }) {
9381
9411
  };
9382
9412
  }
9383
9413
  let stage = null;
9414
+ let sampleTruncated = false;
9384
9415
  try {
9385
9416
  const readiness = await kernelQuery("chain.workspaceReadiness");
9386
9417
  stage = readiness?.stage ?? "blank";
9418
+ sampleTruncated = readiness?.sampleTruncated === true;
9387
9419
  } catch {
9388
9420
  errors.push("Readiness check unavailable \u2014 showing workspace summary.");
9389
9421
  }
9390
9422
  if (stage === "blank" || stage === "seeded") {
9391
- const result2 = await buildSetupResponse(agentSessionId);
9423
+ const result2 = await buildSetupResponse(agentSessionId, sampleTruncated);
9392
9424
  return {
9393
9425
  content: textContent(`${result2.text}
9394
9426
 
@@ -9396,7 +9428,7 @@ ${FEEDBACK_SUBMIT_HINT}`),
9396
9428
  structuredContent: result2.structuredContent
9397
9429
  };
9398
9430
  }
9399
- if (stage === "grounded" || stage === "connected") {
9431
+ if (!sampleTruncated && (stage === "grounded" || stage === "connected")) {
9400
9432
  void kernelMutation("chain.setOnboardingCompleted", {}).catch(() => {
9401
9433
  });
9402
9434
  }
@@ -9701,16 +9733,17 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
9701
9733
  } catch (e) {
9702
9734
  errors.push(`Readiness: ${e instanceof Error ? e.message : String(e)}`);
9703
9735
  }
9704
- if (readiness?.stage === "blank") {
9736
+ const sampleTruncated = readiness?.sampleTruncated === true || orientView?.sampleTruncated === true;
9737
+ if (readiness?.stage === "blank" && !sampleTruncated) {
9705
9738
  const scanLines = [
9706
9739
  `# Welcome to ${wsCtx?.workspaceName ?? "your workspace"}`,
9707
9740
  "",
9708
- "Your workspace is fresh \u2014 let's set it up.",
9741
+ "No accepted user-authored entries were found; drafts may already exist.",
9709
9742
  "",
9710
- "**Recommended:** call the `start` tool instead of `orient` for the best first-run experience.",
9711
- "It will guide you through setting up your workspace \u2014 you can tell me about your product, scan your codebase, or use a preset.",
9743
+ "For your current task, follow the spine guidance below: discover and reuse existing knowledge before creating anything.",
9744
+ "Capture only what the task needs, inspect its returned status, and read it back. Accept it only if it remains a draft; already accepted or committed entries need no extra acceptance.",
9712
9745
  "",
9713
- "Or tell me about your product and I'll start capturing knowledge directly."
9746
+ "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."
9714
9747
  ];
9715
9748
  scanLines.push(...buildRitualsSurfaceLines(orientView, { fetchFailed: orientFetchFailed || orientView?.fetchFailed === true }));
9716
9749
  let oriented = false;
@@ -9728,16 +9761,17 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
9728
9761
  const blankResult = {
9729
9762
  content: [{ type: "text", text: scanLines.join("\n") }],
9730
9763
  structuredContent: success(
9731
- "Workspace is blank. Use `orient action=start` for guided setup.",
9732
- { stage: "blank", readinessScore: readiness?.score ?? 0, oriented, orientationStatus: orientationStatus2, redirectHint: "Use `orient action=start` for the full guided setup experience." },
9733
- [{ tool: "orient", description: "Guided workspace setup", parameters: { action: "start" } }]
9764
+ "No accepted user-authored entries found. Follow the spine guidance for your task; full onboarding is optional.",
9765
+ { stage: "blank", readinessScore: readiness?.score ?? 0, oriented, orientationStatus: orientationStatus2, redirectHint: "Optional full onboarding: `orient action=start`. Continue an existing task using the spine guidance." },
9766
+ task ? [] : [{ tool: "orient", description: "Optional full workspace onboarding", parameters: { action: "start" } }]
9734
9767
  )
9735
9768
  };
9736
9769
  reportOrientWrapperBytes(blankResult, false, resolvedTier, taskMeaningful);
9737
9770
  return blankResult;
9738
9771
  }
9739
- const lines = [];
9740
- const isLowReadiness = readiness && readiness.score < 50;
9772
+ const lines = sampleTruncated ? [READINESS_PROVISIONAL_LINE, ""] : [];
9773
+ const isLowReadiness = !sampleTruncated && readiness && readiness.score < 50;
9774
+ const stageQualifier = sampleTruncated ? " (provisional sample)" : "";
9741
9775
  if (wsCtx) {
9742
9776
  lines.push(`# ${wsCtx.workspaceName}`);
9743
9777
  lines.push(`_Workspace \`${wsCtx.workspaceSlug}\` \u2014 Product Brain is healthy._`);
@@ -9757,7 +9791,7 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
9757
9791
  }
9758
9792
  if (mode === "brief") {
9759
9793
  const briefStage = readiness?.stage ?? (readiness?.score != null ? readiness.score < 50 ? "seeded" : "grounded" : "unknown");
9760
- lines.push(`Brain stage: ${briefStage}`);
9794
+ lines.push(`Brain stage: ${briefStage}${stageQualifier}`);
9761
9795
  lines.push(...buildDirectionLines(orientView));
9762
9796
  if (orientView?.strategicContext) {
9763
9797
  const sc = orientView.strategicContext;
@@ -9821,12 +9855,12 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
9821
9855
  lines.push("");
9822
9856
  lines.push(...briefWna);
9823
9857
  }
9824
- const briefGaps = readiness?.gaps ?? [];
9858
+ const briefGaps = sampleTruncated ? [] : readiness?.gaps ?? [];
9825
9859
  if (briefGaps.length > 0) {
9826
9860
  const oneLiner = formatGapOneLiner(briefGaps[0]);
9827
9861
  if (oneLiner) lines.push(oneLiner);
9828
9862
  }
9829
- const briefClassificationGaps = briefGaps.filter((g) => g.id?.startsWith?.(PURPOSE_GAP_PREFIX));
9863
+ const briefClassificationGaps = (readiness?.gaps ?? []).filter((g) => g.id?.startsWith?.(PURPOSE_GAP_PREFIX));
9830
9864
  if (briefClassificationGaps.length > 0) {
9831
9865
  lines.push(`${briefClassificationGaps.length} collection(s) with weak purpose \u2014 classification may be inaccurate.`);
9832
9866
  }
@@ -9912,10 +9946,11 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
9912
9946
  const briefResult = {
9913
9947
  content: [{ type: "text", text: lines.join("\n") }],
9914
9948
  structuredContent: success(
9915
- `Oriented (brief). Stage: ${readiness?.stage ?? "unknown"}.`,
9949
+ `Oriented (brief). Stage: ${readiness?.stage ?? "unknown"}${stageQualifier}.`,
9916
9950
  {
9917
9951
  mode: "brief",
9918
9952
  stage: readiness?.stage ?? "unknown",
9953
+ sampleTruncated,
9919
9954
  oriented: hasTaskGrounding ? isSessionOriented() : false,
9920
9955
  sessionId: agentSessionId,
9921
9956
  taskGroundingStatus: hasTaskGrounding ? "task_scoped" : task ? "missing_task_context" : "workspace_only",
@@ -9955,7 +9990,7 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
9955
9990
  if (isLowReadiness && wsCtx?.createdAt) {
9956
9991
  const ageDays = Math.floor((Date.now() - wsCtx.createdAt) / (1e3 * 60 * 60 * 24));
9957
9992
  if (ageDays >= 30) {
9958
- lines.push(`Your workspace has been around for ${ageDays} days and is still at the **${orientStage}** stage.`);
9993
+ lines.push(`Your workspace has been around for ${ageDays} days and is still at the **${orientStage}${stageQualifier}** stage.`);
9959
9994
  lines.push("Let's close the gaps \u2014 or if the current structure doesn't fit, we can reshape it.");
9960
9995
  lines.push("");
9961
9996
  }
@@ -9986,7 +10021,7 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
9986
10021
  lines.push("");
9987
10022
  }
9988
10023
  }
9989
- lines.push("_Need a collection that doesn't exist yet? Just ask \u2014 I'll set it up._");
10024
+ lines.push("_Need a collection that doesn't exist yet? Creating one is not available through this tool. Ask a platform admin to provision it, or enable the module ontology pack that owns it._");
9990
10025
  lines.push("");
9991
10026
  } else if (readiness) {
9992
10027
  const fmt = (e) => {
@@ -10006,7 +10041,7 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
10006
10041
  fullCoherenceSnapshot = fullCoherence.snapshot ?? void 0;
10007
10042
  }
10008
10043
  if (task) {
10009
- lines.push(`**Brain stage: ${orientStage}.** Working on: **${task}**`);
10044
+ lines.push(`**Brain stage: ${orientStage}${stageQualifier}.** Working on: **${task}**`);
10010
10045
  lines.push("");
10011
10046
  lines.push(...buildDirectionLines(orientView));
10012
10047
  if (orientView?.strategicContext) {
@@ -10233,7 +10268,7 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
10233
10268
  lines.push(...formatRecoveryBlock(recoveryBlock));
10234
10269
  }
10235
10270
  } else {
10236
- lines.push(`**Brain stage: ${orientStage}.**`);
10271
+ lines.push(`**Brain stage: ${orientStage}${stageQualifier}.**`);
10237
10272
  lines.push("");
10238
10273
  const scNoTaskLines = buildDirectionLines(orientView);
10239
10274
  if (scNoTaskLines.length > 0) lines.push(...scNoTaskLines, "");
@@ -10349,10 +10384,11 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
10349
10384
  const fullResult = {
10350
10385
  content: [{ type: "text", text: lines.join("\n") }],
10351
10386
  structuredContent: success(
10352
- `Oriented (full). Stage: ${orientStage}. ${isLowReadiness ? "Low readiness \u2014 gaps remain." : "Ready."}`,
10387
+ `Oriented (full). Stage: ${orientStage}${stageQualifier}. ${sampleTruncated ? "Provisional readiness sample." : isLowReadiness ? "Low readiness \u2014 gaps remain." : "Ready."}`,
10353
10388
  {
10354
10389
  mode: "full",
10355
10390
  stage: orientStage,
10391
+ sampleTruncated,
10356
10392
  oriented: hasTaskGrounding ? isSessionOriented() : false,
10357
10393
  sessionId: agentSessionId,
10358
10394
  taskGroundingStatus: hasTaskGrounding ? "task_scoped" : task ? "missing_task_context" : "workspace_only",
@@ -12194,6 +12230,51 @@ async function handleAuditRun(entryId, phase) {
12194
12230
  };
12195
12231
  }
12196
12232
 
12233
+ // src/lib/spineVerdictRead.ts
12234
+ function isRecord(value) {
12235
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12236
+ }
12237
+ function isStringArray(value) {
12238
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
12239
+ }
12240
+ function isStructural(value) {
12241
+ if (!isRecord(value) || typeof value.passed !== "boolean" || !isStringArray(value.missing) || !isRecord(value.duplicateSingletons) || !isStringArray(value.unconnectedStrategyIds) || value.visionConnectedToPurpose !== null && typeof value.visionConnectedToPurpose !== "boolean") return false;
12242
+ for (const key of ["purpose", "vision"]) {
12243
+ const ids = value.duplicateSingletons[key];
12244
+ if (ids !== null && !isStringArray(ids)) return false;
12245
+ }
12246
+ for (const key of [
12247
+ "strategiesWithUnknownConnectivity",
12248
+ "unconnectedObjectiveIds",
12249
+ "objectivesWithUnknownConnectivity",
12250
+ "unconnectedKeyResultIds",
12251
+ "keyResultsWithUnknownConnectivity"
12252
+ ]) {
12253
+ if (value[key] != null && !isStringArray(value[key])) return false;
12254
+ }
12255
+ return value.visionPurposeConnectivityTruncated === void 0 || typeof value.visionPurposeConnectivityTruncated === "boolean";
12256
+ }
12257
+ function isStrategy(value) {
12258
+ if (!isRecord(value) || typeof value.entryId !== "string" || !isRecord(value.refusal) || typeof value.refusal.hasRefusalSignal !== "boolean") return false;
12259
+ return value.rogerMartin === null || isRecord(value.rogerMartin) && typeof value.rogerMartin.isStrategicChoice === "boolean" && typeof value.rogerMartin.reasoning === "string";
12260
+ }
12261
+ function isStory(value) {
12262
+ return value === null || isRecord(value) && typeof value.passed === "boolean" && (value.failedCriterion === null || typeof value.failedCriterion === "string") && Array.isArray(value.criteria) && value.criteria.every((item) => isRecord(item) && typeof item.id === "string" && typeof item.passed === "boolean" && typeof item.reasoning === "string");
12263
+ }
12264
+ function readSpineVerdict(value) {
12265
+ if (!isRecord(value) || typeof value.noSpineEntries !== "boolean") return { kind: "malformed" };
12266
+ if (value.noSpineEntries) return { kind: "empty" };
12267
+ if (!isStructural(value.structural) || !Array.isArray(value.perStrategy) || !value.perStrategy.every(isStrategy) || !isStory(value.story)) return { kind: "malformed" };
12268
+ for (const key of ["strategiesTruncated", "perStrategyEvaluationTruncated", "objectivesTruncated", "keyResultsTruncated"]) {
12269
+ if (value[key] !== void 0 && typeof value[key] !== "boolean") return { kind: "malformed" };
12270
+ }
12271
+ return { kind: "graded", verdict: value };
12272
+ }
12273
+ function describeSpineStaleness(changedMemberCount, membershipTruncated = false) {
12274
+ const reason = changedMemberCount > 0 ? `${membershipTruncated ? "at least " : ""}${changedMemberCount} entr${changedMemberCount === 1 ? "y" : "ies"} changed or joined/left the spine since this was computed` : "freshness could not be verified; the changed-entry count is unknown";
12275
+ return reason + (membershipTruncated ? "; membership scan incomplete" : "");
12276
+ }
12277
+
12197
12278
  // src/tools/spine-check.ts
12198
12279
  async function handleSpineCheck(story, refresh = false) {
12199
12280
  const scheduleResult = await kernelMutation(
@@ -12203,26 +12284,59 @@ async function handleSpineCheck(story, refresh = false) {
12203
12284
  const row = await kernelQuery("quality.getLatestSpineVerdict", {});
12204
12285
  const stillRunning = scheduleResult.scheduled;
12205
12286
  if (!row) {
12206
- const text = stillRunning ? "Spine check started \u2014 no prior result exists yet. It runs asynchronously (LLM calls can take 25s+ each); re-run this in a few seconds to read the result." : "No spine check has been run yet, and none is currently running. Pass refresh:true to start one.";
12287
+ const text = stillRunning ? "Spine check started \u2014 no prior result exists yet. It runs asynchronously (LLM calls can take 25s+ each); re-run this in a few seconds to read the result." : scheduleResult.reason === "already-in-flight" ? "A spine check is already running \u2014 no prior result exists yet. Re-run quality action=spine-check in a few seconds to read its result." : "No spine check has been run yet, and none is currently running. Pass refresh:true to start one.";
12207
12288
  return {
12208
12289
  content: [{ type: "text", text }],
12209
- structuredContent: success(text, { hasVerdict: false, checkStarted: stillRunning })
12290
+ structuredContent: success(text, {
12291
+ hasVerdict: false,
12292
+ checkStarted: stillRunning,
12293
+ stillRunning: stillRunning || scheduleResult.reason === "already-in-flight"
12294
+ })
12210
12295
  };
12211
12296
  }
12212
- const { verdict } = row;
12213
- if (verdict.noSpineEntries) {
12297
+ const parsed = readSpineVerdict(row.verdict);
12298
+ const freshnessData = { stale: row.stale, staleMemberIds: row.staleMemberIds, membershipTruncated: row.membershipTruncated ?? false };
12299
+ const staleReason = describeSpineStaleness(row.staleMemberIds.length, row.membershipTruncated);
12300
+ if (parsed.kind === "empty") {
12301
+ const pending = stillRunning || scheduleResult.reason === "already-in-flight";
12302
+ const freshness = (row.stale ? ` Stale: ${staleReason}.` : "") + (pending ? " A check is running; re-run quality action=spine-check in a few seconds to read its result." : row.stale ? " Run quality action=spine-check refresh=true to refresh it." : "");
12303
+ const text = `The last spine check found no accepted purpose, vision or strategy.${freshness}`;
12214
12304
  return {
12215
- content: [{ type: "text", text: "No spine entries yet \u2014 nothing to check." }],
12216
- structuredContent: success("No spine entries yet.", { hasVerdict: true, noSpineEntries: true })
12305
+ content: [{ type: "text", text }],
12306
+ structuredContent: success(text, {
12307
+ hasVerdict: true,
12308
+ noSpineEntries: true,
12309
+ ...freshnessData,
12310
+ stillRunning: pending,
12311
+ scheduled: scheduleResult.scheduled,
12312
+ scheduleReason: scheduleResult.reason
12313
+ })
12217
12314
  };
12218
12315
  }
12316
+ if (parsed.kind === "malformed") {
12317
+ const pending = stillRunning || scheduleResult.reason === "already-in-flight";
12318
+ const text = `Stored spine verdict is malformed \u2014 ${pending ? "a check is running; re-run quality action=spine-check in a few seconds to read its result." : "run quality action=spine-check refresh=true to replace it."}`;
12319
+ return {
12320
+ content: [{ type: "text", text }],
12321
+ structuredContent: success(text, {
12322
+ hasVerdict: true,
12323
+ malformed: true,
12324
+ ...freshnessData,
12325
+ scheduled: scheduleResult.scheduled,
12326
+ stillRunning: pending,
12327
+ scheduleReason: scheduleResult.reason
12328
+ })
12329
+ };
12330
+ }
12331
+ const { verdict } = parsed;
12219
12332
  const lines = ["## Spine Check"];
12220
12333
  if (stillRunning) {
12221
12334
  lines.push(`> A new check was just triggered and is still running asynchronously \u2014 the result below is from the PREVIOUS run (${new Date(row.createdAt).toISOString()}). Re-run in a few seconds for the fresh result.`, "");
12222
12335
  } else if (scheduleResult.reason === "already-in-flight") {
12223
12336
  lines.push(`> A check is already running (started earlier, not by this call) \u2014 showing the best available result (${new Date(row.createdAt).toISOString()}). Re-run in a few seconds.`, "");
12224
- } else if (row.stale) {
12225
- lines.push(`> STALE \u2014 ${row.staleMemberIds.length} member entry(ies) changed since this was computed. Pass refresh:true to recompute.`, "");
12337
+ }
12338
+ if (row.stale) {
12339
+ lines.push(`> STALE \u2014 ${staleReason}.${stillRunning || scheduleResult.reason === "already-in-flight" ? "" : " Pass refresh:true to recompute."}`, "");
12226
12340
  }
12227
12341
  const s = verdict.structural;
12228
12342
  lines.push(`Structural completeness: ${s.passed ? "PASS" : "FAIL"}`);
@@ -12249,7 +12363,7 @@ async function handleSpineCheck(story, refresh = false) {
12249
12363
  lines.push("", `One-story judgment [unvalidated, on-demand]: ${verdict.story.passed ? "PASS" : "FAIL"}`);
12250
12364
  if (!verdict.story.passed && verdict.story.failedCriterion) lines.push(`Failed criterion: ${verdict.story.failedCriterion}`);
12251
12365
  } else if (row.includesStoryPass) {
12252
- lines.push("", "One-story judgment: requested, but the LLM pass did not return a usable result (timeout or parse failure). Pass refresh:true to retry \u2014 a plain re-run will not reschedule it.");
12366
+ lines.push("", "One-story judgment: requested, but the LLM pass did not return a usable result (timeout or parse failure). Run quality action=spine-check story=true refresh=true to retry \u2014 a plain re-run will not reschedule it.");
12253
12367
  } else {
12254
12368
  lines.push("", "One-story judgment: not run this pass \u2014 pass story=true to run it.");
12255
12369
  }
@@ -12262,7 +12376,7 @@ async function handleSpineCheck(story, refresh = false) {
12262
12376
  noSpineEntries: false,
12263
12377
  structuralPassed: s.passed,
12264
12378
  storyPassed: verdict.story?.passed ?? null,
12265
- stale: row.stale,
12379
+ ...freshnessData,
12266
12380
  stillRunning,
12267
12381
  scheduled: scheduleResult.scheduled,
12268
12382
  scheduleReason: scheduleResult.reason
@@ -12351,7 +12465,12 @@ var qualitySpineCheckOutputSchema = z24.object({
12351
12465
  noSpineEntries: z24.boolean().optional(),
12352
12466
  structuralPassed: z24.boolean().optional(),
12353
12467
  storyPassed: z24.boolean().nullable().optional(),
12354
- stale: z24.boolean().optional()
12468
+ stale: z24.boolean().optional(),
12469
+ // TASK-156: a capped membership scan must stay distinguishable from a complete one, and a
12470
+ // malformed verdict needs its own discriminator — both were silently stripped before this.
12471
+ staleMemberIds: z24.array(z24.string()).optional(),
12472
+ membershipTruncated: z24.boolean().optional(),
12473
+ malformed: z24.boolean().optional()
12355
12474
  });
12356
12475
  function registerQualityTools(server) {
12357
12476
  const qualityHandlers = {
@@ -14629,8 +14748,8 @@ async function handleWorkspaceStatus() {
14629
14748
  const { score, totalChecks, passedChecks, checks, gaps, stats, governanceMode } = result2;
14630
14749
  const scoringVersion = result2.scoringVersion ?? "v1";
14631
14750
  const stage = result2.stage ?? "seeded";
14632
- const stageLabel = STAGE_LABELS[stage] ?? stage;
14633
- const stageDescription = STAGE_DESCRIPTIONS[stage] ?? "";
14751
+ const stageLabel = `${STAGE_LABELS[stage] ?? stage}${result2.sampleTruncated ? " (provisional sample)" : ""}`;
14752
+ const stageDescription = result2.sampleTruncated ? READINESS_PROVISIONAL_LINE : STAGE_DESCRIPTIONS[stage] ?? "";
14634
14753
  const scoreBar = "\u2588".repeat(Math.round(score / 10)) + "\u2591".repeat(10 - Math.round(score / 10));
14635
14754
  const lines = [
14636
14755
  `# Brain Status: ${stageLabel}`,
@@ -14639,14 +14758,14 @@ async function handleWorkspaceStatus() {
14639
14758
  `${scoreBar} ${stageLabel} \xB7 ${score}%`,
14640
14759
  `**Governance:** ${governanceMode ?? "open"}${(governanceMode ?? "open") !== "open" ? " (accepts require proposal)" : ""}`,
14641
14760
  "",
14642
- "## Stats",
14643
- `- **Entries:** ${stats.totalEntries} (${stats.activeCount} active, ${stats.draftCount} draft)`,
14761
+ "## Sampled counts",
14762
+ `- **Entries:** ${stats.totalEntries} (${stats.activeCount} accepted/active, ${stats.draftCount} draft)`,
14644
14763
  `- **Relations:** ${stats.totalRelations}`,
14645
14764
  `- **Collections:** ${stats.collectionCount}`,
14646
14765
  `- **Orphaned:** ${stats.orphanedCount} committed entries with no relations`,
14647
14766
  ""
14648
14767
  ];
14649
- if (gaps.length > 0) {
14768
+ if (gaps.length > 0 && !result2.sampleTruncated) {
14650
14769
  lines.push("## Gaps");
14651
14770
  for (const gap of gaps) {
14652
14771
  const action = gap.capabilityGuidance ?? gap.guidance;
@@ -14657,7 +14776,7 @@ async function handleWorkspaceStatus() {
14657
14776
  }
14658
14777
  const passed = checks.filter((c) => c.passed);
14659
14778
  if (passed.length > 0) {
14660
- lines.push("## Passing checks");
14779
+ lines.push(result2.sampleTruncated ? "## Checks observed in the provisional sample" : "## Passing checks");
14661
14780
  for (const check of passed) {
14662
14781
  lines.push(`- [x] ${check.label} (${check.current}/${check.required})`);
14663
14782
  }
@@ -14671,6 +14790,7 @@ async function handleWorkspaceStatus() {
14671
14790
  }
14672
14791
  const statusData = {
14673
14792
  stage,
14793
+ sampleTruncated: result2.sampleTruncated ?? null,
14674
14794
  scoringVersion,
14675
14795
  readinessScore: score,
14676
14796
  activeEntries: stats.activeCount,
@@ -14679,14 +14799,14 @@ async function handleWorkspaceStatus() {
14679
14799
  gaps: gaps.map((g) => ({
14680
14800
  id: g.id,
14681
14801
  label: g.label,
14682
- guidance: g.capabilityGuidance ?? g.guidance
14802
+ guidance: result2.sampleTruncated ? "Unverified gap in sampled entries. Review existing knowledge before adding more." : g.capabilityGuidance ?? g.guidance
14683
14803
  })),
14684
14804
  ...orgHealth && { organisationHealth: orgHealth }
14685
14805
  };
14686
14806
  return {
14687
14807
  content: [{ type: "text", text: lines.join("\n") }],
14688
14808
  structuredContent: success(
14689
- `Brain: ${stageLabel} (${score}%). ${stats.activeCount} active entries, ${gaps.length} gap(s).`,
14809
+ `Brain: ${stageLabel} (${score}%). ${stats.activeCount} accepted/active entries in the sample, ${gaps.length} ${result2.sampleTruncated ? "potential gap(s); absence unverified" : "gap(s)"}.`,
14690
14810
  statusData
14691
14811
  )
14692
14812
  };
@@ -14747,6 +14867,7 @@ var organisationHealthSchema = z29.object({
14747
14867
  });
14748
14868
  var healthStatusOutputSchema = z29.object({
14749
14869
  stage: z29.enum(["blank", "seeded", "grounded", "connected"]).optional().default("seeded"),
14870
+ sampleTruncated: z29.boolean().nullable().optional(),
14750
14871
  scoringVersion: z29.enum(["v1", "v2"]).optional().default("v1"),
14751
14872
  readinessScore: z29.number(),
14752
14873
  activeEntries: z29.number(),
@@ -16941,4 +17062,4 @@ export {
16941
17062
  createProductBrainServer,
16942
17063
  initFeatureFlags
16943
17064
  };
16944
- //# sourceMappingURL=chunk-PTC2REQA.js.map
17065
+ //# sourceMappingURL=chunk-7T4HSTNG.js.map