@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
|
-
//
|
|
2070
|
-
|
|
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 =
|
|
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.
|
|
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
|
-
` + (
|
|
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
|
-
|
|
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
|
|
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
|
|
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:
|
|
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("
|
|
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
|
|
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
|
|
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
|
-
|
|
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 &&
|
|
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.
|
|
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("##
|
|
3698
|
-
lines.push(`${entriesWithOverlaps.length} ${entriesWithOverlaps.length === 1 ? "entry has" : "entries have"}
|
|
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}
|
|
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
|
|
3743
|
-
const
|
|
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
|
|
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':
|
|
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;
|
|
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:
|
|
5395
|
+
content: [{ type: "text", text: emptyMessage }],
|
|
5354
5396
|
structuredContent: success(
|
|
5355
|
-
|
|
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}
|
|
7214
|
-
if (readiness.
|
|
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(
|
|
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
|
|
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
|
-
|
|
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?
|
|
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 (
|
|
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(`
|
|
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
|
-
|
|
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
|
-
"
|
|
9772
|
+
"No accepted user-authored entries were found; drafts may already exist.",
|
|
9709
9773
|
"",
|
|
9710
|
-
"
|
|
9711
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
9732
|
-
{ stage: "blank", readinessScore: readiness?.score ?? 0, oriented, orientationStatus: orientationStatus2, redirectHint: "
|
|
9733
|
-
[{ tool: "orient", description: "
|
|
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 =
|
|
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?
|
|
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, {
|
|
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
|
-
|
|
12213
|
-
|
|
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
|
|
12216
|
-
structuredContent: success(
|
|
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
|
-
}
|
|
12225
|
-
|
|
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).
|
|
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
|
-
|
|
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
|
-
"##
|
|
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-
|
|
17096
|
+
//# sourceMappingURL=chunk-W3MF7EC3.js.map
|