@productbrain/mcp 0.0.1-beta.5916 → 0.0.1-beta.5955

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1072,7 +1072,8 @@ var batchCaptureRealOutputSchema = z3.object({
1072
1072
  // stays a draft) — the same path that now also emits coherencyRefusal. The strict schema must
1073
1073
  // accept it or a refused response fails to validate on `status` (codex review, completing the
1074
1074
  // coherencyRefusal contract fix).
1075
- status: z3.enum(["draft", "committed", "proposed", "draft_on_failure"]),
1075
+ status: z3.enum(["draft", "committed", "proposed", "draft_on_failure", "draft_spine_floor"]),
1076
+ spineFloor: z3.object({ relationType: z3.string(), parentKind: z3.string(), exempt: z3.boolean() }).optional(),
1076
1077
  classifiedBy: z3.enum(["llm", "heuristic", "explicit"]).optional(),
1077
1078
  confidence: z3.number().optional(),
1078
1079
  confidenceTier: z3.enum(["high", "medium", "low"]).optional(),
@@ -1096,6 +1097,7 @@ var batchCaptureRealOutputSchema = z3.object({
1096
1097
  committed: z3.number(),
1097
1098
  proposed: z3.number(),
1098
1099
  drafts: z3.number(),
1100
+ spineFloored: z3.number().optional(),
1099
1101
  classified: z3.number().optional(),
1100
1102
  autoCommitApplied: z3.boolean(),
1101
1103
  skippedLowConfidence: z3.array(z3.object({
@@ -1554,6 +1556,8 @@ var groundingGovernanceEntrySchema = z4.object({
1554
1556
  collectionSlug: z4.string()
1555
1557
  });
1556
1558
  var groundingReportSchema = z4.object({
1559
+ // TASK-156: null preserves uncertainty for unavailable checks or an older server response.
1560
+ scanTruncated: z4.boolean().nullable(),
1557
1561
  related: z4.array(groundingRelatedEntrySchema),
1558
1562
  duplicates: z4.array(groundingDuplicateEntrySchema),
1559
1563
  governance: z4.array(groundingGovernanceEntrySchema)
@@ -1680,6 +1684,9 @@ function reconcileBatchPreviewDuplicate(tracker, args) {
1680
1684
  return null;
1681
1685
  }
1682
1686
 
1687
+ // src/lib/readinessNotices.ts
1688
+ 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.";
1689
+
1683
1690
  // src/tools/smart-capture.ts
1684
1691
  var GENERIC_PROFILE = {
1685
1692
  governedDraft: false,
@@ -1759,6 +1766,8 @@ function inferRelationType(_sourceCollection, _targetCollection, profile) {
1759
1766
  var AUTO_LINK_CONFIDENCE_THRESHOLD = 35;
1760
1767
  var MAX_AUTO_LINKS = 5;
1761
1768
  var MAX_SUGGESTIONS = 5;
1769
+ var SPINE_FLOOR_SENTENCE = "kept as a draft; it can be accepted once its spine above is accepted \u2014 the promote door checks the whole spine and names the first missing rung.";
1770
+ var SPINE_FLOOR_EXEMPT_SENTENCE = "kept as a draft, though this workspace is not yet enforcing the spine above it \u2014 it can be accepted as-is.";
1762
1771
  var BR_STD_ENTRY_ID_REGEX = /^(BR|STD)-\d+$/;
1763
1772
  var entryIdSchema = z5.string().regex(BR_STD_ENTRY_ID_REGEX).optional().describe("Only for business-rules and standards. Must match BR-NNN or STD-NNN. Omit for all other collections \u2014 IDs are auto-generated.");
1764
1773
  var captureSchema = z5.object({
@@ -2066,12 +2075,14 @@ var captureSuccessOutputSchema = z5.object({
2066
2075
  entryId: z5.string(),
2067
2076
  collection: z5.string(),
2068
2077
  name: z5.string(),
2069
- // "draft_on_failure" is emitted when an auto-commit is REFUSED/failed (stays a draft) — the
2070
- // same path that also emits coherencyRefusal; the strict schema must accept it or fail.
2071
- status: z5.enum(["draft", "committed", "proposed", "draft_on_failure"]),
2078
+ // Single captures report the persisted draft; batch captures distinguish the draft reason.
2079
+ status: z5.enum(["draft", "committed", "proposed", "draft_on_failure", "draft_spine_floor"]),
2072
2080
  // WP-480 S1: `qualityScore` (required, client N/10) deleted — a named, accepted
2073
2081
  // breaking output-contract change. The server verdict stays as `qualityVerdict`.
2074
2082
  qualityVerdict: z5.record(z5.unknown()).optional(),
2083
+ qualityHints: z5.array(z5.record(z5.unknown())).optional(),
2084
+ relationSuggestions: z5.array(z5.record(z5.unknown())).optional(),
2085
+ groundingReport: captureSinglePreviewOutputSchema.shape.groundingReport.optional(),
2075
2086
  classifier: captureClassifierSchema.optional(),
2076
2087
  studioUrl: z5.string().optional(),
2077
2088
  warnings: z5.array(z5.string()).optional(),
@@ -2089,6 +2100,8 @@ var captureSuccessOutputSchema = z5.object({
2089
2100
  slug: z5.string(),
2090
2101
  status: z5.literal("proposal-pending")
2091
2102
  }).optional(),
2103
+ // TASK-160: the server's birth-floor verdict must survive the strict output contract.
2104
+ spineFloor: z5.object({ relationType: z5.string(), parentKind: z5.string(), exempt: z5.boolean() }).optional(),
2092
2105
  // WP-465 surface parity: structured coherency refusal when an auto-commit is gate-refused.
2093
2106
  // Loose record — the CoherencyRefusal contract is authored in
2094
2107
  // convex/lib/gates/coherencyControls.ts; re-declaring it here would be a second SSOT.
@@ -2178,7 +2191,7 @@ function registerSmartCaptureTools(server) {
2178
2191
  "capture",
2179
2192
  {
2180
2193
  title: "Capture",
2181
- description: "The tool for creating knowledge entries \u2014 single or batch. Two actions:\n\n- **capture**: Create a single entry. Creates an entry, auto-links related entries, and returns a quality scorecard \u2014 all in one call. Provide a name and description; `collection` is optional \u2014 the classifier auto-routes when omitted.\n- **batch**: Create multiple entries in one call via `items[]` (absorbs batch-capture). Each item may carry inline `relations` created right after that entry is captured.\n\n**action=capture details:**\nSupported collections with smart profiles: tensions, business-rules, glossary, decisions, features, audiences, strategy, standards, chains, insights, principles.\nAll other collections get an ENT-{random} ID and sensible defaults.\n\n**Explicit data:** When you know the schema, pass `data: { field: value }` to set fields directly. Top-level `name` and `description` always win for those fields. `data` wins over inference for all other fields.\n\n**Compound capture:** Pass `links` to create relations in the same call (skips auto-link discovery). In Open mode, entries commit by default when `autoCommit` is omitted. Pass `autoCommit: true` to promote the entry from draft to SSOT immediately after linking when you want to be explicit. Governed collections (glossary, business-rules, principles, standards, strategy, features, architecture) will warn but still commit \u2014 use only when you're certain.\n\nEntries are created as `draft` first, then may publish immediately depending on governance and `autoCommit`. Use `entries action=update` for post-creation adjustments.",
2194
+ description: "The tool for creating knowledge entries \u2014 single or batch. Two actions:\n\n- **capture**: Create a single entry. Creates an entry, auto-links related entries, and returns a quality scorecard \u2014 all in one call. Provide a name and description; `collection` is optional \u2014 the classifier auto-routes when omitted.\n- **batch**: Create multiple entries in one call via `items[]` (absorbs batch-capture). Each item may carry inline `relations` created right after that entry is captured.\n\n**action=capture details:**\nSupported collections with smart profiles: tensions, business-rules, glossary, decisions, features, audiences, strategy, standards, chains, insights, principles.\nAll other collections get an ENT-{random} ID and sensible defaults.\n\n**Explicit data:** When you know the schema, pass `data: { field: value }` to set fields directly. Top-level `name` and `description` always win for those fields. `data` wins over inference for all other fields.\n\n**Compound capture:** Pass `links` to create relations in the same call (skips auto-link discovery). In Open mode, entries commit by default when `autoCommit` is omitted. Pass `autoCommit: true` to promote the entry from draft to SSOT immediately after linking when you want to be explicit. Governed collections (glossary, business-rules, principles, standards, strategy, features, architecture) will warn but still commit \u2014 use only when you're certain. An objective, key result or work package is always born draft first \u2014 `autoCommit: true` has no effect on that first birth even with a complete spine; commit it explicitly afterward.\n\nEntries are created as `draft` first, then may publish immediately depending on governance and `autoCommit`. Use `entries action=update` for post-creation adjustments.",
2182
2195
  inputSchema: captureCompoundSchema,
2183
2196
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2184
2197
  },
@@ -2265,9 +2278,8 @@ async function handleCapture(server, { collection, name, description, context, e
2265
2278
  };
2266
2279
  }
2267
2280
  const profile = await getProfile(resolvedCollection);
2268
- const skipAutoDiscoveryEarly = links && links.length > 0;
2269
2281
  const groundingStart = Date.now();
2270
- const groundingPromise = !skipAutoDiscoveryEarly && (name || description) ? kernelQuery("chain.suggestLinksForCapture", {
2282
+ const groundingPromise = name || description ? kernelQuery("chain.suggestLinksForCapture", {
2271
2283
  entries: [{ name, description: description ?? "", collectionHint: resolvedCollection }],
2272
2284
  limit: 5,
2273
2285
  threshold: 2
@@ -2286,6 +2298,8 @@ async function handleCapture(server, { collection, name, description, context, e
2286
2298
  collectionSlug: g.collectionSlug
2287
2299
  }));
2288
2300
  const groundingReport = {
2301
+ // null preserves uncertainty for unavailable checks or an older server response.
2302
+ scanTruncated: typeof groundingEntry?.scanTruncated === "boolean" ? groundingEntry.scanTruncated : null,
2289
2303
  related: groundingRelated.map((r) => ({
2290
2304
  entryId: r.entryId,
2291
2305
  name: r.name,
@@ -2303,9 +2317,10 @@ async function handleCapture(server, { collection, name, description, context, e
2303
2317
  })),
2304
2318
  governance: groundingGovernance
2305
2319
  };
2320
+ 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
2321
  if (suggestOnly) {
2307
2322
  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.`;
2323
+ 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
2324
  return {
2310
2325
  content: [{
2311
2326
  type: "text",
@@ -2313,7 +2328,9 @@ async function handleCapture(server, { collection, name, description, context, e
2313
2328
 
2314
2329
  ${summaryText}
2315
2330
 
2316
- ` + (groundingReport.related.length > 0 ? `## Related entries
2331
+ ` + (groundingCaveat ? `${groundingCaveat}
2332
+
2333
+ ` : "") + (groundingReport.related.length > 0 ? `## Related entries
2317
2334
  ${groundingReport.related.map((r) => `- **${r.entryId}** ${r.name} [${r.collectionSlug}] \u2014 ${r.reasoning}`).join("\n")}
2318
2335
 
2319
2336
  ` : "") + (groundingReport.duplicates.length > 0 ? `## Possible duplicates
@@ -2420,6 +2437,7 @@ ${groundingReport.governance.map((g) => `- **${g.entryId}** ${g.name} [${g.colle
2420
2437
  let authorityDomainFromCreate;
2421
2438
  let coachingFromCreate;
2422
2439
  let wasAutoCommittedServerSide = false;
2440
+ let spineFloor;
2423
2441
  try {
2424
2442
  const result2 = await kernelMutation("chain.createEntry", {
2425
2443
  collectionSlug: resolvedCollection,
@@ -2527,6 +2545,7 @@ No DB writes \u2014 call without \`preview:true\` to capture for real.${result2.
2527
2545
  if (created.status === "active") {
2528
2546
  wasAutoCommittedServerSide = true;
2529
2547
  }
2548
+ spineFloor = created.spineFloor;
2530
2549
  } catch (error) {
2531
2550
  const msg = error instanceof Error ? error.message : String(error);
2532
2551
  if (isGatewayTimeout(error)) return buildCaptureOutcomeUnknownResult(error, name, msg);
@@ -2569,7 +2588,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2569
2588
  const linksSuggested = [];
2570
2589
  const userLinkResults = [];
2571
2590
  const advisedLinks = [];
2572
- let conflictCandidates = [];
2591
+ 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
2592
  const pendingRelations = [];
2574
2593
  const skipAutoDiscovery = links && links.length > 0;
2575
2594
  const searchQuery = extractSearchTerms(name, description);
@@ -2646,7 +2665,6 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2646
2665
  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
2666
  linksSuggested.push({ entryId: c.entryId, name: c.name, collection: c.collSlug, reason, preview: c.preview });
2648
2667
  }
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
2668
  }
2651
2669
  if (links && links.length > 0 && finalEntryId) {
2652
2670
  for (const link of links) {
@@ -2678,8 +2696,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2678
2696
  entryWarnings.push(`Auto-linking partially failed: ${err instanceof Error ? err.message : "unknown error"}`);
2679
2697
  return null;
2680
2698
  }) : Promise.resolve(null);
2681
- const cardinalityPromise = resolvedCK ? kernelQuery("chain.checkCardinalityWarning", { canonicalKey: resolvedCK }).catch(() => null) : Promise.resolve(null);
2682
- const contradictionPromise = runContradictionCheck(name, description);
2699
+ const cardinalityPromise = resolvedCK ? kernelQuery("chain.checkCardinalityWarning", { canonicalKey: resolvedCK, excludeEntryId: internalId }).catch(() => null) : Promise.resolve(null);
2700
+ const contradictionPromise = runContradictionCheck(name, description, finalEntryId, internalId);
2683
2701
  const coachingPromise = coachingFromCreate !== void 0 ? Promise.resolve(coachingFromCreate) : kernelMutation("quality.evaluateHeuristicAndSchedule", {
2684
2702
  entryId: finalEntryId,
2685
2703
  context: "capture"
@@ -2768,14 +2786,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2768
2786
  }
2769
2787
  }
2770
2788
  const freshGovernanceMode = await refreshWorkspaceGovernanceMode();
2771
- 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
- }
2789
+ const shouldAutoCommit = !spineFloor && shouldAutoCommitCapture(autoCommit, freshGovernanceMode);
2779
2790
  let finalStatus = "draft";
2780
2791
  let commitError = null;
2781
2792
  let commitRefusal = null;
@@ -2906,6 +2917,11 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2906
2917
  } else {
2907
2918
  lines.push(`Error: ${commitError}. Entry saved as draft \u2014 use \`entries action=commit entryId="${finalEntryId}"\` to promote when ready.`);
2908
2919
  }
2920
+ } else if (spineFloor) {
2921
+ lines.push("");
2922
+ lines.push("## Saved as draft");
2923
+ lines.push(`**${name}** ${spineFloor.exempt ? SPINE_FLOOR_EXEMPT_SENTENCE : SPINE_FLOOR_SENTENCE}`);
2924
+ lines.push(`Next: \`entries action=commit entryId="${finalEntryId}"\`.`);
2909
2925
  }
2910
2926
  if (shouldAutoCommit && conflictCandidates.length > 0) {
2911
2927
  lines.push("");
@@ -2925,6 +2941,7 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2925
2941
  lines.push(`${i + 1}. **${s.entryId ?? "(no ID)"}**: ${s.name} [${s.collection}]${preview2}`);
2926
2942
  }
2927
2943
  }
2944
+ if (groundingCaveat) lines.push("", groundingCaveat);
2928
2945
  if (format === "human") {
2929
2946
  const hasDuplicates = groundingReport.duplicates.length > 0;
2930
2947
  const hasRelated = groundingReport.related.length > 0;
@@ -2962,11 +2979,11 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
2962
2979
  for (const rs of relationSuggestionsFromCreate) {
2963
2980
  lines.push(`- **${rs.relationType}** \u2192 ${rs.targetEntryId} "${rs.targetName}" (confidence: ${rs.confidence})`);
2964
2981
  }
2965
- lines.push(`_Create with: \`relations action=create fromId="${finalEntryId}" toId="TARGET-ID" type="TYPE"\`_`);
2982
+ lines.push(`_Create with: \`relations action=create from="${finalEntryId}" to="TARGET-ID" type="TYPE"\`_`);
2966
2983
  }
2967
2984
  if (authorityDomainFromCreate) {
2968
2985
  lines.push("");
2969
- lines.push(`domain: ${authorityDomainFromCreate.slug} (proposal pending \u2014 ratify at session close)`);
2986
+ 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
2987
  }
2971
2988
  if (formativeHints.length > 0) {
2972
2989
  trackCaptureQualityHints(wsCtx.workspaceId, {
@@ -3001,11 +3018,11 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
3001
3018
  }
3002
3019
  if (contradictionWarnings.length > 0) {
3003
3020
  lines.push("");
3004
- lines.push("\u26A0 Contradiction check: proposed entry matched existing governance entries:");
3021
+ lines.push("\u26A0 Contradiction check: captured entry matched existing governance entries:");
3005
3022
  for (const w of contradictionWarnings) {
3006
3023
  lines.push(`- ${w.name} (${w.collection}, ${w.entryId}) \u2014 has 'governs' relation to ${w.governsCount} entries`);
3007
3024
  }
3008
- lines.push("Run `context action=gather` on these entries before accepting.");
3025
+ lines.push("Review the matched governance entries and reconcile any actual conflict.");
3009
3026
  }
3010
3027
  if (entryWarnings.length > 0) {
3011
3028
  lines.push("");
@@ -3043,7 +3060,9 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
3043
3060
  }
3044
3061
  try {
3045
3062
  const readiness = await kernelQuery("chain.workspaceReadiness");
3046
- if (readiness && readiness.gaps && readiness.gaps.length > 0) {
3063
+ if (readiness?.sampleTruncated) {
3064
+ lines.push("", `## Workspace Readiness: ${readiness.score}% (provisional sample)`, READINESS_PROVISIONAL_LINE);
3065
+ } else if (readiness && readiness.gaps && readiness.gaps.length > 0) {
3047
3066
  const topGaps = readiness.gaps.slice(0, 2);
3048
3067
  lines.push("");
3049
3068
  lines.push(`## Workspace Readiness: ${readiness.score}%`);
@@ -3126,6 +3145,8 @@ Use \`entries action=get\` to inspect the existing entry, or \`entries action=up
3126
3145
  relationSuggestions: relationSuggestionsFromCreate,
3127
3146
  // TEN-2365: capture-time authority-domain proposal (PENDING ratification), when filed.
3128
3147
  ...authorityDomainFromCreate && { authorityDomain: authorityDomainFromCreate },
3148
+ // WP-745 TASK-160 (PR #901 review): the machine-readable projection — was reaching the human text only.
3149
+ ...spineFloor && { spineFloor },
3129
3150
  // WP-318 S2: Pre-write grounding report — always included (empty arrays when no suggestions)
3130
3151
  groundingReport
3131
3152
  },
@@ -3145,7 +3166,8 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
3145
3166
  const createdBy = agentId ? `agent:${agentId}` : "capture";
3146
3167
  const wsCtx = await getWorkspaceContext();
3147
3168
  const freshGovernanceModeForBatch = await refreshWorkspaceGovernanceMode();
3148
- const autoCommitApplied = shouldAutoCommitCapture(autoCommit, freshGovernanceModeForBatch);
3169
+ const shouldAutoCommitBatch = shouldAutoCommitCapture(autoCommit, freshGovernanceModeForBatch);
3170
+ let autoCommitApplied = false;
3149
3171
  const needsClassification = entries.map((e, i) => ({ ...e, index: i })).filter((e) => !e.collection);
3150
3172
  let classificationMap = /* @__PURE__ */ new Map();
3151
3173
  if (needsClassification.length > 0) {
@@ -3399,11 +3421,13 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
3399
3421
  ];
3400
3422
  if (!preview) resolveGapsForEntry(entry.name, result2.entryId);
3401
3423
  const batchWasAutoCommittedServerSide = result2.status === "active";
3402
- let finalStatus = batchWasAutoCommittedServerSide ? "committed" : "draft";
3424
+ const batchSpineFloor = result2.spineFloor;
3425
+ let finalStatus = batchWasAutoCommittedServerSide ? "committed" : batchSpineFloor ? "draft_spine_floor" : "draft";
3403
3426
  let commitError;
3404
3427
  let commitRefusal;
3405
3428
  let contradictionAdvisory;
3406
3429
  if (batchWasAutoCommittedServerSide) {
3430
+ if (!preview) autoCommitApplied = true;
3407
3431
  await recordSessionActivity({ entryModified: internalId });
3408
3432
  trackChainEntryCommitted(wsCtx.workspaceId, {
3409
3433
  entry_id: finalEntryId,
@@ -3498,9 +3522,10 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
3498
3522
  if (relationsProposedCount > 0) {
3499
3523
  batchEntryWarnings.push(`${relationsProposedCount} inline relation(s) converted to an agent proposal (misuse pattern) \u2014 review in Cortex UI.`);
3500
3524
  }
3501
- if (!preview && autoCommitApplied && !batchWasAutoCommittedServerSide) {
3525
+ if (!preview && shouldAutoCommitBatch && !batchWasAutoCommittedServerSide && !batchSpineFloor) {
3502
3526
  try {
3503
3527
  const semanticConflicts = await discoverSemanticConflicts(entry.name, entry.description, resolvedSlug);
3528
+ autoCommitApplied = true;
3504
3529
  const commitResult = await kernelMutation("chain.commitEntry", {
3505
3530
  entryId: finalEntryId,
3506
3531
  author: agentId ? `agent:${agentId}` : void 0,
@@ -3555,6 +3580,8 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
3555
3580
  ...commitError ? { commitError } : {},
3556
3581
  // WP-465 surface parity: thread full refusal payload for coherency-refused batch entries.
3557
3582
  ...commitRefusal ? { commitRefusal } : {},
3583
+ ...batchSpineFloor ? { spineFloor: batchSpineFloor } : {},
3584
+ // WP-745 TASK-160: read by the render below.
3558
3585
  // WP-485 Slice 2b (R2, FEAT-1370): the server's contradiction advisory (DEC-1321 —
3559
3586
  // advisory only, never blocking) — visible, never a silent success.
3560
3587
  ...contradictionAdvisory ? { contradictionAdvisory } : {},
@@ -3594,6 +3621,7 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
3594
3621
  const proposed = created.filter((r) => r.status === "proposed");
3595
3622
  const drafts = created.filter((r) => r.status === "draft");
3596
3623
  const commitFailed = created.filter((r) => r.status === "draft_on_failure");
3624
+ const spineFloored = created.filter((r) => r.status === "draft_spine_floor");
3597
3625
  const classifiedCount = created.filter((r) => r.classifiedBy && r.classifiedBy !== "explicit").length;
3598
3626
  await server.sendLoggingMessage({
3599
3627
  level: "info",
@@ -3621,8 +3649,9 @@ async function handleBatchCapture(server, { entries, autoCommit, preview }) {
3621
3649
  }
3622
3650
  if (created.length > 0) {
3623
3651
  const commitFailedSegment = commitFailed.length > 0 ? `, ${commitFailed.length} commit-failed` : "";
3652
+ const spineFloorSegment = spineFloored.length > 0 ? `, ${spineFloored.length} spine-floored` : "";
3624
3653
  lines.push(
3625
- `**Statuses:** ${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${commitFailedSegment}.`
3654
+ `**Statuses:** ${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${spineFloorSegment}${commitFailedSegment}.`
3626
3655
  );
3627
3656
  lines.push("");
3628
3657
  }
@@ -3666,11 +3695,13 @@ _Use \`entries action=move\` to correct any misclassified entries._`);
3666
3695
  lines.push("");
3667
3696
  lines.push("_Re-capture these with an explicit `collection` or use the suggested collection._");
3668
3697
  }
3669
- if (commitFailed.length > 0) {
3698
+ if (commitFailed.length > 0 || spineFloored.length > 0) {
3670
3699
  lines.push("");
3671
3700
  lines.push("## Saved as draft");
3672
- for (const r of commitFailed) {
3673
- if (r.commitRefusal) {
3701
+ for (const r of [...spineFloored, ...commitFailed]) {
3702
+ if (r.spineFloor) {
3703
+ lines.push(`- **${r.entryId}**: ${r.name} [${r.collection}] \u2014 ${r.spineFloor.exempt ? SPINE_FLOOR_EXEMPT_SENTENCE : SPINE_FLOOR_SENTENCE} Next: \`entries action=commit entryId="${r.entryId}"\`.`);
3704
+ } else if (r.commitRefusal) {
3674
3705
  lines.push(`- **${r.entryId}**: ${r.name} [${r.collection}]`);
3675
3706
  const refusalText = coherencyRefusedResult("batch-capture", r.entryId ?? "", r.commitRefusal, { headingLevel: 3 }).content[0].text;
3676
3707
  lines.push(...refusalText.split("\n"));
@@ -3694,10 +3725,10 @@ _Use \`entries action=move\` to correct any misclassified entries._`);
3694
3725
  const entriesWithOverlaps = created.filter((r) => r.overlapCount && r.overlapCount > 0);
3695
3726
  if (entriesWithOverlaps.length > 0) {
3696
3727
  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:`);
3728
+ lines.push("## Related-entry Suggestions");
3729
+ lines.push(`${entriesWithOverlaps.length} ${entriesWithOverlaps.length === 1 ? "entry has" : "entries have"} link candidates. Review their relevance before linking:`);
3699
3730
  for (const r of entriesWithOverlaps) {
3700
- lines.push(`- **${r.entryId}**: ${r.name} \u2014 ${r.overlapCount} similar ${r.overlapCount === 1 ? "entry" : "entries"}`);
3731
+ lines.push(`- **${r.entryId}**: ${r.name} \u2014 ${r.overlapCount} link ${r.overlapCount === 1 ? "candidate" : "candidates"}`);
3701
3732
  }
3702
3733
  }
3703
3734
  }
@@ -3739,8 +3770,9 @@ _Use \`entries action=move\` to correct any misclassified entries._`);
3739
3770
  const skippedNote = skippedLowConfidence.length > 0 ? `, ${skippedLowConfidence.length} skipped (low confidence)` : "";
3740
3771
  const classifiedNote = classifiedCount > 0 ? `, ${classifiedCount} auto-classified` : "";
3741
3772
  const commitFailedNote = commitFailed.length > 0 ? `, ${commitFailed.length} commit-failed` : "";
3742
- const summary = failed.length > 0 || unknownOutcomeEntries.length > 0 || skippedLowConfidence.length > 0 || commitFailed.length > 0 ? `Batch captured ${created.length}/${entries.length} entries (${failed.length} failed${unknownOutcomeSummaryNote(unknownOutcomeEntries)}${skippedNote}, ${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${commitFailedNote}${classifiedNote}).` : `Batch captured ${created.length} entries successfully (${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${classifiedNote}).`;
3743
- const firstDraft = drafts[0];
3773
+ const spineFloorNote = spineFloored.length > 0 ? `, ${spineFloored.length} spine-floored` : "";
3774
+ const summary = failed.length > 0 || unknownOutcomeEntries.length > 0 || skippedLowConfidence.length > 0 || commitFailed.length > 0 || spineFloored.length > 0 ? `Batch captured ${created.length}/${entries.length} entries (${failed.length} failed${unknownOutcomeSummaryNote(unknownOutcomeEntries)}${skippedNote}, ${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${spineFloorNote}${commitFailedNote}${classifiedNote}).` : `Batch captured ${created.length} entries successfully (${committed.length} committed, ${proposed.length} proposed, ${drafts.length} draft${classifiedNote}).`;
3775
+ const firstDraft = drafts[0] ?? spineFloored[0] ?? commitFailed[0];
3744
3776
  const next = [];
3745
3777
  if (created.length > 0) {
3746
3778
  next.push({ tool: "relations", description: "Discover connections", parameters: { action: "suggest", entryId: created[0].entryId } });
@@ -3776,6 +3808,8 @@ _Use \`entries action=move\` to correct any misclassified entries._`);
3776
3808
  ...r.commitError ? { commitError: r.commitError } : {},
3777
3809
  // WP-465 surface parity: thread full refusal payload into batch structured output.
3778
3810
  ...r.commitRefusal ? { coherencyRefusal: r.commitRefusal } : {},
3811
+ ...r.spineFloor ? { spineFloor: r.spineFloor } : {},
3812
+ // WP-745 TASK-160: the projection a machine consumer receives; was human-render only (Copilot review, PR #901).
3779
3813
  ...r.authorityDomain ? { domain: r.authorityDomain.slug } : {},
3780
3814
  // TEN-957 (WP-484 S2): inline relations created for this entry.
3781
3815
  ...r.relationsCreated ? { relationsCreated: r.relationsCreated } : {},
@@ -3792,6 +3826,7 @@ _Use \`entries action=move\` to correct any misclassified entries._`);
3792
3826
  proposed: proposed.length,
3793
3827
  drafts: drafts.length,
3794
3828
  ...commitFailed.length > 0 ? { commitFailed: commitFailed.length } : {},
3829
+ ...spineFloored.length > 0 ? { spineFloored: spineFloored.length } : {},
3795
3830
  ...classifiedCount > 0 ? { classified: classifiedCount } : {},
3796
3831
  autoCommitApplied,
3797
3832
  ...skippedLowConfidence.length > 0 && {
@@ -3865,7 +3900,7 @@ async function recordCommitFailure({
3865
3900
  }
3866
3901
  }
3867
3902
  }
3868
- async function runContradictionCheck(name, description) {
3903
+ async function runContradictionCheck(name, description, excludeEntryId, excludeDocId) {
3869
3904
  const warnings = [];
3870
3905
  try {
3871
3906
  const keyTerms = tokenizeText(`${name} ${description}`).filter((w) => w.length >= 4 && !STOP_WORDS.has(w)).slice(0, 8);
@@ -3875,7 +3910,13 @@ async function runContradictionCheck(name, description) {
3875
3910
  kernelQuery("chain.searchEntries", { query: searchQuery, collectionSlug: "business-rules" }),
3876
3911
  kernelQuery("chain.searchEntries", { query: searchQuery, collectionSlug: "architecture" })
3877
3912
  ]);
3878
- const allGov = [...govResults ?? [], ...archResults ?? []].slice(0, 5);
3913
+ const seen = /* @__PURE__ */ new Set();
3914
+ const allGov = [...govResults ?? [], ...archResults ?? []].filter((entry) => {
3915
+ const id = entry.entryId ?? entry._id;
3916
+ if (!id || excludeEntryId && entry.entryId === excludeEntryId || excludeDocId && entry._id === excludeDocId || seen.has(id)) return false;
3917
+ seen.add(id);
3918
+ return true;
3919
+ }).slice(0, 5);
3879
3920
  for (const entry of allGov) {
3880
3921
  const entryTokens = new Set(tokenizeText(`${entry.name} ${entry.data?.description ?? ""}`));
3881
3922
  const matched = keyTerms.filter((t) => entryTokens.has(t));
@@ -4859,7 +4900,7 @@ var entriesSchema = z10.object({
4859
4900
  entryIds: z10.array(z10.string().max(200)).min(1).max(20).optional().describe("Entry IDs for 'batch', e.g. ['TYPE-strategy', 'STR-jljeg7']"),
4860
4901
  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
4902
  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."
4903
+ "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
4904
  ),
4864
4905
  tag: z10.string().max(200).optional().describe("For 'list': filter by internal tag."),
4865
4906
  label: z10.string().max(200).optional().describe("For 'list': filter by label slug \u2014 matches entries across all collections."),
@@ -4943,7 +4984,7 @@ var entriesActionUnion = z10.discriminatedUnion("action", [
4943
4984
  ]);
4944
4985
  var ENTRIES_ACTION_SPECS = {
4945
4986
  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." },
4987
+ list: { params: ["collection", "status", "tag", "label"], description: "Browse accepted entries by default; status='draft' lists drafts. All params optional." },
4947
4988
  get: { params: ["entryId"], description: "Fetch one entry by ID." },
4948
4989
  batch: { params: ["entryIds"], description: "Fetch up to 20 entries by ID." },
4949
4990
  search: { params: ["query", "collection", "status"], description: "Full-text search; query is required (min 2 chars)." },
@@ -5337,7 +5378,7 @@ async function handleBatch(entryIds) {
5337
5378
  async function handleList(collection, status2, tag, label) {
5338
5379
  let entries;
5339
5380
  if (label) {
5340
- entries = await kernelQuery("chain.listEntriesByLabel", { labelSlug: label });
5381
+ entries = await kernelQuery("chain.listEntriesByLabel", { labelSlug: label, acceptedOnly: !status2 });
5341
5382
  if (status2) {
5342
5383
  entries = entries.filter((e) => e.status === status2);
5343
5384
  }
@@ -5349,10 +5390,11 @@ async function handleList(collection, status2, tag, label) {
5349
5390
  });
5350
5391
  }
5351
5392
  if (entries.length === 0) {
5393
+ 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
5394
  return {
5353
- content: [{ type: "text", text: "No entries match the given filters." }],
5395
+ content: [{ type: "text", text: emptyMessage }],
5354
5396
  structuredContent: success(
5355
- "No entries match the given filters.",
5397
+ emptyMessage,
5356
5398
  { entries: [], total: 0 },
5357
5399
  [{ tool: "collections", description: "List collections", parameters: { action: "list" } }]
5358
5400
  )
@@ -7210,8 +7252,9 @@ function formatSteeringBrief(result2) {
7210
7252
  lines.push(`${delta.newEntries.length} new entries since last steering brief${delta.truncated ? " (TRUNCATED)" : ""}`);
7211
7253
  lines.push("");
7212
7254
  lines.push("## Workspace Readiness");
7213
- lines.push(`Score: **${readiness.score}%** (${readiness.passedChecks}/${readiness.totalChecks} checks passed)`);
7214
- if (readiness.gaps.length > 0) {
7255
+ lines.push(`Score: **${readiness.score}%**${readiness.sampleTruncated ? " (provisional sample)" : ""} (${readiness.passedChecks}/${readiness.totalChecks} checks passed)`);
7256
+ if (readiness.sampleTruncated) lines.push(READINESS_PROVISIONAL_LINE);
7257
+ if (!readiness.sampleTruncated && readiness.gaps.length > 0) {
7215
7258
  lines.push("");
7216
7259
  lines.push("**Gaps:**");
7217
7260
  for (const gap of readiness.gaps.slice(0, 5)) {
@@ -7228,7 +7271,7 @@ function formatSteeringBrief(result2) {
7228
7271
  return {
7229
7272
  content: [{ type: "text", text: lines.join("\n") }],
7230
7273
  structuredContent: success(
7231
- `Steering brief: ${changes.entries.length} changes, ${delta.newEntries.length} new entries, ${readiness.score}% readiness.`,
7274
+ `Steering brief: ${changes.entries.length} changes, ${delta.newEntries.length} new entries, ${readiness.score}% readiness${readiness.sampleTruncated ? " (provisional sample; gaps unverified)" : ""}.`,
7232
7275
  { ...result2, trustFilterActive: true }
7233
7276
  )
7234
7277
  };
@@ -8745,19 +8788,30 @@ function sanitizeSingleLineField(str) {
8745
8788
  }
8746
8789
 
8747
8790
  // src/tools/orient-rituals.ts
8791
+ var SPINE_UNAVAILABLE_LINE = "Spine check: unavailable \u2014 orient fetch failed, retry `orient`";
8748
8792
  function buildRitualsSurfaceLines(view, opts) {
8749
8793
  const lines = [""];
8750
8794
  if (opts?.fetchFailed) {
8751
8795
  lines.push("Direction: unavailable \u2014 orient fetch failed, retry `orient`");
8752
- lines.push("Spine check: unavailable \u2014 orient fetch failed, retry `orient`");
8796
+ lines.push(SPINE_UNAVAILABLE_LINE);
8753
8797
  return lines;
8754
8798
  }
8755
8799
  lines.push(view?.directionLine ?? "Direction: not computed yet \u2014 `pb direction refresh`");
8756
8800
  if (view?.verdictAskLine) lines.push(view.verdictAskLine);
8757
- lines.push(view?.spineLine ?? "Spine check: not computed yet \u2014 `pb quality spine-check`");
8801
+ if (view?.spineLine) lines.push(view.spineLine);
8758
8802
  if (view?.atCapLine) lines.push(view.atCapLine);
8759
8803
  return lines;
8760
8804
  }
8805
+ async function fetchAndRenderSetupSpineLine(kernelQuery2) {
8806
+ try {
8807
+ const view = await kernelQuery2("chain.getRitualsSurface", {
8808
+ now: Math.floor(Date.now() / 6e4) * 6e4
8809
+ });
8810
+ return view?.spineLine ? [view.spineLine] : [];
8811
+ } catch {
8812
+ return [SPINE_UNAVAILABLE_LINE];
8813
+ }
8814
+ }
8761
8815
  function toSafeCodeSpan(text) {
8762
8816
  const longestBacktickRun = (text.match(/`+/g) ?? []).reduce((max, run) => Math.max(max, run.length), 0);
8763
8817
  const fence = "`".repeat(longestBacktickRun + 1);
@@ -9096,15 +9150,18 @@ function pickNextTensionPrompt(openTensions) {
9096
9150
  cta: "Want to discuss this tension or capture a decision about it?"
9097
9151
  };
9098
9152
  }
9099
- async function buildSetupResponse(agentSessionId) {
9153
+ async function buildSetupResponse(agentSessionId, sampleTruncated) {
9100
9154
  await kernelMutation(
9101
9155
  "setup.stampMcpOnlySurface",
9102
9156
  {}
9103
9157
  );
9104
9158
  const skill = await loadSkillBody(PB_SETUP_SKILL_ENTRY_ID);
9105
9159
  const { oriented, orientationStatus } = await tryMarkOriented(agentSessionId);
9160
+ const spineLines = await fetchAndRenderSetupSpineLine(kernelQuery);
9106
9161
  return {
9107
- text: skill.body,
9162
+ text: `${skill.body}${spineLines.length > 0 ? `
9163
+ ${spineLines.join("\n")}` : ""}${sampleTruncated ? `
9164
+ ${READINESS_PROVISIONAL_LINE} Reuse existing knowledge when following setup.` : ""}`,
9108
9165
  structuredContent: success(
9109
9166
  `pb-setup ready. Delivering SKILL-pb-setup body (${skill.body.length} chars).`,
9110
9167
  {
@@ -9112,9 +9169,11 @@ async function buildSetupResponse(agentSessionId) {
9112
9169
  skillEntryId: skill.entryId,
9113
9170
  skillBody: skill.body,
9114
9171
  stage: "setup",
9172
+ sampleTruncated,
9115
9173
  oriented,
9116
9174
  orientationStatus,
9117
9175
  ...agentSessionId ? { sessionId: agentSessionId } : {},
9176
+ ...spineLines[0] !== void 0 ? { spineLine: spineLines[0] } : {},
9118
9177
  instructions: "Read skillBody and follow it; use entries/orient MCP tools per PAT-227."
9119
9178
  }
9120
9179
  )
@@ -9149,8 +9208,9 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
9149
9208
  errors.push(`Readiness: ${e instanceof Error ? e.message : String(e)}`);
9150
9209
  }
9151
9210
  const lines = [];
9152
- const isLowReadiness = readiness !== null && readiness.score < 50;
9153
- const isHighReadiness = readiness !== null && readiness.score >= 50;
9211
+ const isLowReadiness = readiness !== null && !readiness.sampleTruncated && readiness.score < 50;
9212
+ if (readiness?.sampleTruncated) lines.push(READINESS_PROVISIONAL_LINE, "");
9213
+ const showExistingContext = readiness !== null && (readiness.sampleTruncated || readiness.score >= 50);
9154
9214
  const stage = readiness?.stage ?? null;
9155
9215
  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
9216
  const coherence = await buildCoherenceSection();
@@ -9208,9 +9268,9 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
9208
9268
  lines.push("");
9209
9269
  }
9210
9270
  }
9211
- lines.push("_Need a collection that doesn't exist yet? Just ask \u2014 I'll set it up._");
9271
+ 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
9272
  lines.push("");
9213
- } else if (isHighReadiness) {
9273
+ } else if (showExistingContext) {
9214
9274
  let activeBets = [];
9215
9275
  try {
9216
9276
  const betsEntries = await kernelQuery("chain.listEntries", { collectionSlug: "work-packages" });
@@ -9276,7 +9336,7 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
9276
9336
  const committedCollections = new Set(committed.map((e) => e.collectionId));
9277
9337
  if (committed.length >= 10 && committedCollections.size >= 3) {
9278
9338
  lines.push("");
9279
- lines.push(`_${committed.length} entries across ${committedCollections.size} collections on the Chain._`);
9339
+ lines.push(`_Context sample: ${committed.length} accepted/active entries across ${committedCollections.size} collections._`);
9280
9340
  }
9281
9341
  } catch {
9282
9342
  }
@@ -9328,13 +9388,13 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
9328
9388
  lines.push("");
9329
9389
  }
9330
9390
  const { oriented, orientationStatus } = await tryMarkOriented(agentSessionId, coherence?.snapshot ?? void 0);
9331
- const stageLabel = stage ?? "active";
9391
+ const stageLabel = `${stage ?? "active"}${readiness?.sampleTruncated ? " (provisional sample)" : ""}`;
9332
9392
  const gapCount = readiness?.gaps?.length ?? 0;
9333
9393
  const summaryParts = [`Product Brain ready. Stage: ${stageLabel}.`];
9334
- if (gapCount > 0) summaryParts.push(`${gapCount} gap${gapCount === 1 ? "" : "s"}.`);
9394
+ if (gapCount > 0) summaryParts.push(`${gapCount} ${readiness?.sampleTruncated ? "potential " : ""}gap${gapCount === 1 ? "" : "s"}${readiness?.sampleTruncated ? " in sampled entries; absence unverified" : ""}.`);
9335
9395
  if (openTensions.length > 0) summaryParts.push(`${openTensions.length} open tension${openTensions.length === 1 ? "" : "s"}.`);
9336
9396
  const next = [];
9337
- if (gapCount > 0) {
9397
+ if (gapCount > 0 && !readiness?.sampleTruncated) {
9338
9398
  next.push({ tool: "capture", description: "Fill a gap", parameters: {} });
9339
9399
  }
9340
9400
  if (openTensions.length > 0) {
@@ -9346,6 +9406,7 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors, task) {
9346
9406
  summaryParts.join(" "),
9347
9407
  {
9348
9408
  stage,
9409
+ sampleTruncated: readiness?.sampleTruncated ?? null,
9349
9410
  readinessScore: readiness?.score ?? null,
9350
9411
  totalGaps: gapCount,
9351
9412
  openTensions: openTensions.length,
@@ -9381,14 +9442,16 @@ async function handleStartPb({ task }) {
9381
9442
  };
9382
9443
  }
9383
9444
  let stage = null;
9445
+ let sampleTruncated = false;
9384
9446
  try {
9385
9447
  const readiness = await kernelQuery("chain.workspaceReadiness");
9386
9448
  stage = readiness?.stage ?? "blank";
9449
+ sampleTruncated = readiness?.sampleTruncated === true;
9387
9450
  } catch {
9388
9451
  errors.push("Readiness check unavailable \u2014 showing workspace summary.");
9389
9452
  }
9390
9453
  if (stage === "blank" || stage === "seeded") {
9391
- const result2 = await buildSetupResponse(agentSessionId);
9454
+ const result2 = await buildSetupResponse(agentSessionId, sampleTruncated);
9392
9455
  return {
9393
9456
  content: textContent(`${result2.text}
9394
9457
 
@@ -9396,7 +9459,7 @@ ${FEEDBACK_SUBMIT_HINT}`),
9396
9459
  structuredContent: result2.structuredContent
9397
9460
  };
9398
9461
  }
9399
- if (stage === "grounded" || stage === "connected") {
9462
+ if (!sampleTruncated && (stage === "grounded" || stage === "connected")) {
9400
9463
  void kernelMutation("chain.setOnboardingCompleted", {}).catch(() => {
9401
9464
  });
9402
9465
  }
@@ -9701,16 +9764,17 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
9701
9764
  } catch (e) {
9702
9765
  errors.push(`Readiness: ${e instanceof Error ? e.message : String(e)}`);
9703
9766
  }
9704
- if (readiness?.stage === "blank") {
9767
+ const sampleTruncated = readiness?.sampleTruncated === true || orientView?.sampleTruncated === true;
9768
+ if (readiness?.stage === "blank" && !sampleTruncated) {
9705
9769
  const scanLines = [
9706
9770
  `# Welcome to ${wsCtx?.workspaceName ?? "your workspace"}`,
9707
9771
  "",
9708
- "Your workspace is fresh \u2014 let's set it up.",
9772
+ "No accepted user-authored entries were found; drafts may already exist.",
9709
9773
  "",
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.",
9774
+ "For your current task, follow the spine guidance below: discover and reuse existing knowledge before creating anything.",
9775
+ "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
9776
  "",
9713
- "Or tell me about your product and I'll start capturing knowledge directly."
9777
+ "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
9778
  ];
9715
9779
  scanLines.push(...buildRitualsSurfaceLines(orientView, { fetchFailed: orientFetchFailed || orientView?.fetchFailed === true }));
9716
9780
  let oriented = false;
@@ -9728,16 +9792,17 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
9728
9792
  const blankResult = {
9729
9793
  content: [{ type: "text", text: scanLines.join("\n") }],
9730
9794
  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" } }]
9795
+ "No accepted user-authored entries found. Follow the spine guidance for your task; full onboarding is optional.",
9796
+ { stage: "blank", readinessScore: readiness?.score ?? 0, oriented, orientationStatus: orientationStatus2, redirectHint: "Optional full onboarding: `orient action=start`. Continue an existing task using the spine guidance." },
9797
+ task ? [] : [{ tool: "orient", description: "Optional full workspace onboarding", parameters: { action: "start" } }]
9734
9798
  )
9735
9799
  };
9736
9800
  reportOrientWrapperBytes(blankResult, false, resolvedTier, taskMeaningful);
9737
9801
  return blankResult;
9738
9802
  }
9739
- const lines = [];
9740
- const isLowReadiness = readiness && readiness.score < 50;
9803
+ const lines = sampleTruncated ? [READINESS_PROVISIONAL_LINE, ""] : [];
9804
+ const isLowReadiness = !sampleTruncated && readiness && readiness.score < 50;
9805
+ const stageQualifier = sampleTruncated ? " (provisional sample)" : "";
9741
9806
  if (wsCtx) {
9742
9807
  lines.push(`# ${wsCtx.workspaceName}`);
9743
9808
  lines.push(`_Workspace \`${wsCtx.workspaceSlug}\` \u2014 Product Brain is healthy._`);
@@ -9757,7 +9822,7 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
9757
9822
  }
9758
9823
  if (mode === "brief") {
9759
9824
  const briefStage = readiness?.stage ?? (readiness?.score != null ? readiness.score < 50 ? "seeded" : "grounded" : "unknown");
9760
- lines.push(`Brain stage: ${briefStage}`);
9825
+ lines.push(`Brain stage: ${briefStage}${stageQualifier}`);
9761
9826
  lines.push(...buildDirectionLines(orientView));
9762
9827
  if (orientView?.strategicContext) {
9763
9828
  const sc = orientView.strategicContext;
@@ -9821,12 +9886,12 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
9821
9886
  lines.push("");
9822
9887
  lines.push(...briefWna);
9823
9888
  }
9824
- const briefGaps = readiness?.gaps ?? [];
9889
+ const briefGaps = sampleTruncated ? [] : readiness?.gaps ?? [];
9825
9890
  if (briefGaps.length > 0) {
9826
9891
  const oneLiner = formatGapOneLiner(briefGaps[0]);
9827
9892
  if (oneLiner) lines.push(oneLiner);
9828
9893
  }
9829
- const briefClassificationGaps = briefGaps.filter((g) => g.id?.startsWith?.(PURPOSE_GAP_PREFIX));
9894
+ const briefClassificationGaps = (readiness?.gaps ?? []).filter((g) => g.id?.startsWith?.(PURPOSE_GAP_PREFIX));
9830
9895
  if (briefClassificationGaps.length > 0) {
9831
9896
  lines.push(`${briefClassificationGaps.length} collection(s) with weak purpose \u2014 classification may be inaccurate.`);
9832
9897
  }
@@ -9912,10 +9977,11 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
9912
9977
  const briefResult = {
9913
9978
  content: [{ type: "text", text: lines.join("\n") }],
9914
9979
  structuredContent: success(
9915
- `Oriented (brief). Stage: ${readiness?.stage ?? "unknown"}.`,
9980
+ `Oriented (brief). Stage: ${readiness?.stage ?? "unknown"}${stageQualifier}.`,
9916
9981
  {
9917
9982
  mode: "brief",
9918
9983
  stage: readiness?.stage ?? "unknown",
9984
+ sampleTruncated,
9919
9985
  oriented: hasTaskGrounding ? isSessionOriented() : false,
9920
9986
  sessionId: agentSessionId,
9921
9987
  taskGroundingStatus: hasTaskGrounding ? "task_scoped" : task ? "missing_task_context" : "workspace_only",
@@ -9955,7 +10021,7 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
9955
10021
  if (isLowReadiness && wsCtx?.createdAt) {
9956
10022
  const ageDays = Math.floor((Date.now() - wsCtx.createdAt) / (1e3 * 60 * 60 * 24));
9957
10023
  if (ageDays >= 30) {
9958
- lines.push(`Your workspace has been around for ${ageDays} days and is still at the **${orientStage}** stage.`);
10024
+ lines.push(`Your workspace has been around for ${ageDays} days and is still at the **${orientStage}${stageQualifier}** stage.`);
9959
10025
  lines.push("Let's close the gaps \u2014 or if the current structure doesn't fit, we can reshape it.");
9960
10026
  lines.push("");
9961
10027
  }
@@ -9986,7 +10052,7 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
9986
10052
  lines.push("");
9987
10053
  }
9988
10054
  }
9989
- lines.push("_Need a collection that doesn't exist yet? Just ask \u2014 I'll set it up._");
10055
+ 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
10056
  lines.push("");
9991
10057
  } else if (readiness) {
9992
10058
  const fmt = (e) => {
@@ -10006,7 +10072,7 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
10006
10072
  fullCoherenceSnapshot = fullCoherence.snapshot ?? void 0;
10007
10073
  }
10008
10074
  if (task) {
10009
- lines.push(`**Brain stage: ${orientStage}.** Working on: **${task}**`);
10075
+ lines.push(`**Brain stage: ${orientStage}${stageQualifier}.** Working on: **${task}**`);
10010
10076
  lines.push("");
10011
10077
  lines.push(...buildDirectionLines(orientView));
10012
10078
  if (orientView?.strategicContext) {
@@ -10233,7 +10299,7 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
10233
10299
  lines.push(...formatRecoveryBlock(recoveryBlock));
10234
10300
  }
10235
10301
  } else {
10236
- lines.push(`**Brain stage: ${orientStage}.**`);
10302
+ lines.push(`**Brain stage: ${orientStage}${stageQualifier}.**`);
10237
10303
  lines.push("");
10238
10304
  const scNoTaskLines = buildDirectionLines(orientView);
10239
10305
  if (scNoTaskLines.length > 0) lines.push(...scNoTaskLines, "");
@@ -10349,10 +10415,11 @@ async function _handleOrient({ mode = "full", tier, task, scope, startupSignals,
10349
10415
  const fullResult = {
10350
10416
  content: [{ type: "text", text: lines.join("\n") }],
10351
10417
  structuredContent: success(
10352
- `Oriented (full). Stage: ${orientStage}. ${isLowReadiness ? "Low readiness \u2014 gaps remain." : "Ready."}`,
10418
+ `Oriented (full). Stage: ${orientStage}${stageQualifier}. ${sampleTruncated ? "Provisional readiness sample." : isLowReadiness ? "Low readiness \u2014 gaps remain." : "Ready."}`,
10353
10419
  {
10354
10420
  mode: "full",
10355
10421
  stage: orientStage,
10422
+ sampleTruncated,
10356
10423
  oriented: hasTaskGrounding ? isSessionOriented() : false,
10357
10424
  sessionId: agentSessionId,
10358
10425
  taskGroundingStatus: hasTaskGrounding ? "task_scoped" : task ? "missing_task_context" : "workspace_only",
@@ -12194,6 +12261,51 @@ async function handleAuditRun(entryId, phase) {
12194
12261
  };
12195
12262
  }
12196
12263
 
12264
+ // src/lib/spineVerdictRead.ts
12265
+ function isRecord(value) {
12266
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12267
+ }
12268
+ function isStringArray(value) {
12269
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
12270
+ }
12271
+ function isStructural(value) {
12272
+ 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;
12273
+ for (const key of ["purpose", "vision"]) {
12274
+ const ids = value.duplicateSingletons[key];
12275
+ if (ids !== null && !isStringArray(ids)) return false;
12276
+ }
12277
+ for (const key of [
12278
+ "strategiesWithUnknownConnectivity",
12279
+ "unconnectedObjectiveIds",
12280
+ "objectivesWithUnknownConnectivity",
12281
+ "unconnectedKeyResultIds",
12282
+ "keyResultsWithUnknownConnectivity"
12283
+ ]) {
12284
+ if (value[key] != null && !isStringArray(value[key])) return false;
12285
+ }
12286
+ return value.visionPurposeConnectivityTruncated === void 0 || typeof value.visionPurposeConnectivityTruncated === "boolean";
12287
+ }
12288
+ function isStrategy(value) {
12289
+ if (!isRecord(value) || typeof value.entryId !== "string" || !isRecord(value.refusal) || typeof value.refusal.hasRefusalSignal !== "boolean") return false;
12290
+ return value.rogerMartin === null || isRecord(value.rogerMartin) && typeof value.rogerMartin.isStrategicChoice === "boolean" && typeof value.rogerMartin.reasoning === "string";
12291
+ }
12292
+ function isStory(value) {
12293
+ 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");
12294
+ }
12295
+ function readSpineVerdict(value) {
12296
+ if (!isRecord(value) || typeof value.noSpineEntries !== "boolean") return { kind: "malformed" };
12297
+ if (value.noSpineEntries) return { kind: "empty" };
12298
+ if (!isStructural(value.structural) || !Array.isArray(value.perStrategy) || !value.perStrategy.every(isStrategy) || !isStory(value.story)) return { kind: "malformed" };
12299
+ for (const key of ["strategiesTruncated", "perStrategyEvaluationTruncated", "objectivesTruncated", "keyResultsTruncated"]) {
12300
+ if (value[key] !== void 0 && typeof value[key] !== "boolean") return { kind: "malformed" };
12301
+ }
12302
+ return { kind: "graded", verdict: value };
12303
+ }
12304
+ function describeSpineStaleness(changedMemberCount, membershipTruncated = false) {
12305
+ 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";
12306
+ return reason + (membershipTruncated ? "; membership scan incomplete" : "");
12307
+ }
12308
+
12197
12309
  // src/tools/spine-check.ts
12198
12310
  async function handleSpineCheck(story, refresh = false) {
12199
12311
  const scheduleResult = await kernelMutation(
@@ -12203,26 +12315,59 @@ async function handleSpineCheck(story, refresh = false) {
12203
12315
  const row = await kernelQuery("quality.getLatestSpineVerdict", {});
12204
12316
  const stillRunning = scheduleResult.scheduled;
12205
12317
  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.";
12318
+ 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.";
12319
+ return {
12320
+ content: [{ type: "text", text }],
12321
+ structuredContent: success(text, {
12322
+ hasVerdict: false,
12323
+ checkStarted: stillRunning,
12324
+ stillRunning: stillRunning || scheduleResult.reason === "already-in-flight"
12325
+ })
12326
+ };
12327
+ }
12328
+ const parsed = readSpineVerdict(row.verdict);
12329
+ const freshnessData = { stale: row.stale, staleMemberIds: row.staleMemberIds, membershipTruncated: row.membershipTruncated ?? false };
12330
+ const staleReason = describeSpineStaleness(row.staleMemberIds.length, row.membershipTruncated);
12331
+ if (parsed.kind === "empty") {
12332
+ const pending = stillRunning || scheduleResult.reason === "already-in-flight";
12333
+ 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." : "");
12334
+ const text = `The last spine check found no accepted purpose, vision or strategy.${freshness}`;
12207
12335
  return {
12208
12336
  content: [{ type: "text", text }],
12209
- structuredContent: success(text, { hasVerdict: false, checkStarted: stillRunning })
12337
+ structuredContent: success(text, {
12338
+ hasVerdict: true,
12339
+ noSpineEntries: true,
12340
+ ...freshnessData,
12341
+ stillRunning: pending,
12342
+ scheduled: scheduleResult.scheduled,
12343
+ scheduleReason: scheduleResult.reason
12344
+ })
12210
12345
  };
12211
12346
  }
12212
- const { verdict } = row;
12213
- if (verdict.noSpineEntries) {
12347
+ if (parsed.kind === "malformed") {
12348
+ const pending = stillRunning || scheduleResult.reason === "already-in-flight";
12349
+ 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."}`;
12214
12350
  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 })
12351
+ content: [{ type: "text", text }],
12352
+ structuredContent: success(text, {
12353
+ hasVerdict: true,
12354
+ malformed: true,
12355
+ ...freshnessData,
12356
+ scheduled: scheduleResult.scheduled,
12357
+ stillRunning: pending,
12358
+ scheduleReason: scheduleResult.reason
12359
+ })
12217
12360
  };
12218
12361
  }
12362
+ const { verdict } = parsed;
12219
12363
  const lines = ["## Spine Check"];
12220
12364
  if (stillRunning) {
12221
12365
  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
12366
  } else if (scheduleResult.reason === "already-in-flight") {
12223
12367
  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.`, "");
12368
+ }
12369
+ if (row.stale) {
12370
+ lines.push(`> STALE \u2014 ${staleReason}.${stillRunning || scheduleResult.reason === "already-in-flight" ? "" : " Pass refresh:true to recompute."}`, "");
12226
12371
  }
12227
12372
  const s = verdict.structural;
12228
12373
  lines.push(`Structural completeness: ${s.passed ? "PASS" : "FAIL"}`);
@@ -12249,7 +12394,7 @@ async function handleSpineCheck(story, refresh = false) {
12249
12394
  lines.push("", `One-story judgment [unvalidated, on-demand]: ${verdict.story.passed ? "PASS" : "FAIL"}`);
12250
12395
  if (!verdict.story.passed && verdict.story.failedCriterion) lines.push(`Failed criterion: ${verdict.story.failedCriterion}`);
12251
12396
  } 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.");
12397
+ 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
12398
  } else {
12254
12399
  lines.push("", "One-story judgment: not run this pass \u2014 pass story=true to run it.");
12255
12400
  }
@@ -12262,7 +12407,7 @@ async function handleSpineCheck(story, refresh = false) {
12262
12407
  noSpineEntries: false,
12263
12408
  structuralPassed: s.passed,
12264
12409
  storyPassed: verdict.story?.passed ?? null,
12265
- stale: row.stale,
12410
+ ...freshnessData,
12266
12411
  stillRunning,
12267
12412
  scheduled: scheduleResult.scheduled,
12268
12413
  scheduleReason: scheduleResult.reason
@@ -12351,7 +12496,12 @@ var qualitySpineCheckOutputSchema = z24.object({
12351
12496
  noSpineEntries: z24.boolean().optional(),
12352
12497
  structuralPassed: z24.boolean().optional(),
12353
12498
  storyPassed: z24.boolean().nullable().optional(),
12354
- stale: z24.boolean().optional()
12499
+ stale: z24.boolean().optional(),
12500
+ // TASK-156: a capped membership scan must stay distinguishable from a complete one, and a
12501
+ // malformed verdict needs its own discriminator — both were silently stripped before this.
12502
+ staleMemberIds: z24.array(z24.string()).optional(),
12503
+ membershipTruncated: z24.boolean().optional(),
12504
+ malformed: z24.boolean().optional()
12355
12505
  });
12356
12506
  function registerQualityTools(server) {
12357
12507
  const qualityHandlers = {
@@ -14629,8 +14779,8 @@ async function handleWorkspaceStatus() {
14629
14779
  const { score, totalChecks, passedChecks, checks, gaps, stats, governanceMode } = result2;
14630
14780
  const scoringVersion = result2.scoringVersion ?? "v1";
14631
14781
  const stage = result2.stage ?? "seeded";
14632
- const stageLabel = STAGE_LABELS[stage] ?? stage;
14633
- const stageDescription = STAGE_DESCRIPTIONS[stage] ?? "";
14782
+ const stageLabel = `${STAGE_LABELS[stage] ?? stage}${result2.sampleTruncated ? " (provisional sample)" : ""}`;
14783
+ const stageDescription = result2.sampleTruncated ? READINESS_PROVISIONAL_LINE : STAGE_DESCRIPTIONS[stage] ?? "";
14634
14784
  const scoreBar = "\u2588".repeat(Math.round(score / 10)) + "\u2591".repeat(10 - Math.round(score / 10));
14635
14785
  const lines = [
14636
14786
  `# Brain Status: ${stageLabel}`,
@@ -14639,14 +14789,14 @@ async function handleWorkspaceStatus() {
14639
14789
  `${scoreBar} ${stageLabel} \xB7 ${score}%`,
14640
14790
  `**Governance:** ${governanceMode ?? "open"}${(governanceMode ?? "open") !== "open" ? " (accepts require proposal)" : ""}`,
14641
14791
  "",
14642
- "## Stats",
14643
- `- **Entries:** ${stats.totalEntries} (${stats.activeCount} active, ${stats.draftCount} draft)`,
14792
+ "## Sampled counts",
14793
+ `- **Entries:** ${stats.totalEntries} (${stats.activeCount} accepted/active, ${stats.draftCount} draft)`,
14644
14794
  `- **Relations:** ${stats.totalRelations}`,
14645
14795
  `- **Collections:** ${stats.collectionCount}`,
14646
14796
  `- **Orphaned:** ${stats.orphanedCount} committed entries with no relations`,
14647
14797
  ""
14648
14798
  ];
14649
- if (gaps.length > 0) {
14799
+ if (gaps.length > 0 && !result2.sampleTruncated) {
14650
14800
  lines.push("## Gaps");
14651
14801
  for (const gap of gaps) {
14652
14802
  const action = gap.capabilityGuidance ?? gap.guidance;
@@ -14657,7 +14807,7 @@ async function handleWorkspaceStatus() {
14657
14807
  }
14658
14808
  const passed = checks.filter((c) => c.passed);
14659
14809
  if (passed.length > 0) {
14660
- lines.push("## Passing checks");
14810
+ lines.push(result2.sampleTruncated ? "## Checks observed in the provisional sample" : "## Passing checks");
14661
14811
  for (const check of passed) {
14662
14812
  lines.push(`- [x] ${check.label} (${check.current}/${check.required})`);
14663
14813
  }
@@ -14671,6 +14821,7 @@ async function handleWorkspaceStatus() {
14671
14821
  }
14672
14822
  const statusData = {
14673
14823
  stage,
14824
+ sampleTruncated: result2.sampleTruncated ?? null,
14674
14825
  scoringVersion,
14675
14826
  readinessScore: score,
14676
14827
  activeEntries: stats.activeCount,
@@ -14679,14 +14830,14 @@ async function handleWorkspaceStatus() {
14679
14830
  gaps: gaps.map((g) => ({
14680
14831
  id: g.id,
14681
14832
  label: g.label,
14682
- guidance: g.capabilityGuidance ?? g.guidance
14833
+ guidance: result2.sampleTruncated ? "Unverified gap in sampled entries. Review existing knowledge before adding more." : g.capabilityGuidance ?? g.guidance
14683
14834
  })),
14684
14835
  ...orgHealth && { organisationHealth: orgHealth }
14685
14836
  };
14686
14837
  return {
14687
14838
  content: [{ type: "text", text: lines.join("\n") }],
14688
14839
  structuredContent: success(
14689
- `Brain: ${stageLabel} (${score}%). ${stats.activeCount} active entries, ${gaps.length} gap(s).`,
14840
+ `Brain: ${stageLabel} (${score}%). ${stats.activeCount} accepted/active entries in the sample, ${gaps.length} ${result2.sampleTruncated ? "potential gap(s); absence unverified" : "gap(s)"}.`,
14690
14841
  statusData
14691
14842
  )
14692
14843
  };
@@ -14747,6 +14898,7 @@ var organisationHealthSchema = z29.object({
14747
14898
  });
14748
14899
  var healthStatusOutputSchema = z29.object({
14749
14900
  stage: z29.enum(["blank", "seeded", "grounded", "connected"]).optional().default("seeded"),
14901
+ sampleTruncated: z29.boolean().nullable().optional(),
14750
14902
  scoringVersion: z29.enum(["v1", "v2"]).optional().default("v1"),
14751
14903
  readinessScore: z29.number(),
14752
14904
  activeEntries: z29.number(),
@@ -16941,4 +17093,4 @@ export {
16941
17093
  createProductBrainServer,
16942
17094
  initFeatureFlags
16943
17095
  };
16944
- //# sourceMappingURL=chunk-PTC2REQA.js.map
17096
+ //# sourceMappingURL=chunk-W3MF7EC3.js.map