@productbrain/mcp 0.0.1-beta.11434 → 0.0.1-beta.11441
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.
- package/dist/{chunk-KU3IBGI4.js → chunk-CY37RSBU.js} +189 -79
- package/dist/chunk-CY37RSBU.js.map +1 -0
- package/dist/{chunk-QUM6WNMF.js → chunk-LELPZ5YE.js} +8 -7
- package/dist/chunk-LELPZ5YE.js.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/http.js +2 -2
- package/dist/index.js +2 -2
- package/dist/{setup-QJ434LAR.js → setup-UJJCEW4H.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-KU3IBGI4.js.map +0 -1
- package/dist/chunk-QUM6WNMF.js.map +0 -1
- /package/dist/{setup-QJ434LAR.js.map → setup-UJJCEW4H.js.map} +0 -0
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
runWithToolContext,
|
|
35
35
|
setSessionOriented,
|
|
36
36
|
startAgentSession
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-LELPZ5YE.js";
|
|
38
38
|
|
|
39
39
|
// src/server.ts
|
|
40
40
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -190,6 +190,81 @@ function orientationRefusalResult(err, lines) {
|
|
|
190
190
|
}
|
|
191
191
|
var ORIENT_REQUIRED_NOTE = "workflow checkpoints and organisation-structure writes (membership, ownership, parent) still require an oriented session.";
|
|
192
192
|
|
|
193
|
+
// src/lib/spineRungs.ts
|
|
194
|
+
var SPINE_RUNG_SEQUENCE = [
|
|
195
|
+
{ relationType: "validates", targetKey: "key_result", gapIfMissing: "key_result_missing" },
|
|
196
|
+
{ relationType: "part_of", targetKey: "objective", gapIfMissing: "objective_missing" },
|
|
197
|
+
{ relationType: "part_of", targetKey: "strategy", gapIfMissing: "strategy_missing" },
|
|
198
|
+
{ relationType: "part_of", targetKey: "vision", gapIfMissing: "vision_missing" },
|
|
199
|
+
{ relationType: "part_of", targetKey: "purpose", gapIfMissing: "purpose_missing" }
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
// src/lib/checklist.ts
|
|
203
|
+
var FIRST_SPINE_SUBJECT = "work_package";
|
|
204
|
+
var kindWord = (kind) => kind.replace(/_/g, " ");
|
|
205
|
+
var withArticle = (word) => `${/^[aeiou]/.test(word) ? "an" : "a"} ${word}`;
|
|
206
|
+
var spineSubject = (kind) => {
|
|
207
|
+
const words = withArticle(kindWord(kind));
|
|
208
|
+
return words[0].toUpperCase() + words.slice(1);
|
|
209
|
+
};
|
|
210
|
+
function rungLine(rung) {
|
|
211
|
+
const index = SPINE_RUNG_SEQUENCE.findIndex((spec) => spec.targetKey === rung);
|
|
212
|
+
if (index < 0) return null;
|
|
213
|
+
const { relationType: relationType2, targetKey } = SPINE_RUNG_SEQUENCE[index];
|
|
214
|
+
const parent = `an accepted ${kindWord(targetKey)}`;
|
|
215
|
+
const subject = spineSubject(index === 0 ? FIRST_SPINE_SUBJECT : SPINE_RUNG_SEQUENCE[index - 1].targetKey);
|
|
216
|
+
return relationType2 === "part_of" ? `${subject} hangs on ${parent} with ${relationType2}.` : `${subject} ${relationType2} ${parent}.`;
|
|
217
|
+
}
|
|
218
|
+
var SPINE_RUNG_LINES = SPINE_RUNG_SEQUENCE.map((spec) => rungLine(spec.targetKey));
|
|
219
|
+
var SPINE_LINKS_LINES = [
|
|
220
|
+
`Spine links run child to parent, each ${SPINE_RUNG_SEQUENCE[SPINE_RUNG_SEQUENCE.length - 1].relationType} the level above.`,
|
|
221
|
+
`${spineSubject(FIRST_SPINE_SUBJECT)} ${SPINE_RUNG_SEQUENCE[0].relationType} ${withArticle(kindWord(SPINE_RUNG_SEQUENCE[0].targetKey))}; informs joins no levels.`,
|
|
222
|
+
"Before accepting a spine entry, preflight it: it names what blocks."
|
|
223
|
+
];
|
|
224
|
+
var CHECKLIST_LINES = {
|
|
225
|
+
purpose: {
|
|
226
|
+
stranger_test: "Explain it so a stranger knows why this company exists",
|
|
227
|
+
specificity: "Say what would be missing from the world without this company",
|
|
228
|
+
not_a_platitude: "Say something a reasonable company would not also claim",
|
|
229
|
+
beneficiary_named: "Name who it is for: people, a community or part of the world, specific enough to say who it is not for",
|
|
230
|
+
durable_not_dated: "Leave out dates and planning cycles; those belong on goals"
|
|
231
|
+
},
|
|
232
|
+
vision: {
|
|
233
|
+
specificity: "Say what will be different, in words only this product could use",
|
|
234
|
+
beneficiary: "Name who it is for: people, a community or part of the world, specific enough to say who it is not for",
|
|
235
|
+
falsifiability: "Choose a direction a reasonable rival would reject",
|
|
236
|
+
stranger_test: "Explain it so a stranger knows what will be different, and for whom"
|
|
237
|
+
},
|
|
238
|
+
strategy: {
|
|
239
|
+
coherent_narrative: "Connect the diagnosis, the policy and the first actions",
|
|
240
|
+
decisiveness: "Say what you will not do",
|
|
241
|
+
time_bounded: "Name a horizon: a date, a duration or a milestone"
|
|
242
|
+
},
|
|
243
|
+
objective: {
|
|
244
|
+
outcome_not_activity: "State what will be true once it is met, not the work to do",
|
|
245
|
+
serves_strategy: "Say what must be true for the strategy to succeed",
|
|
246
|
+
observable: "Make it something a reader could see has happened"
|
|
247
|
+
},
|
|
248
|
+
key_result: {
|
|
249
|
+
measurable: "Name a measure and the target value or state",
|
|
250
|
+
bounded: "Say by when, or how often, the measure is read",
|
|
251
|
+
moves_the_objective: "Pick a result that shows the objective is happening, not that work was done"
|
|
252
|
+
},
|
|
253
|
+
/** One line per spine rung, keyed by the rung it reaches: rungLine, in SPINE_RUNG_SEQUENCE order (TASK-1026). */
|
|
254
|
+
strategy_link: Object.fromEntries(SPINE_RUNG_SEQUENCE.map((spec) => [spec.targetKey, rungLine(spec.targetKey)]))
|
|
255
|
+
};
|
|
256
|
+
var MARK = { met: "[x]", not_yet: "[ ]", judging: "[~]" };
|
|
257
|
+
function renderChecklist(canonicalKey, criteria, judging) {
|
|
258
|
+
const lines = CHECKLIST_LINES[canonicalKey];
|
|
259
|
+
if (!lines) return null;
|
|
260
|
+
return criteria.filter((c) => lines[c.id] !== void 0).map((c) => {
|
|
261
|
+
const state = !c.passed ? "not_yet" : judging ? "judging" : "met";
|
|
262
|
+
const still = state === "judging" ? " (still judging)" : "";
|
|
263
|
+
const missing = state === "not_yet" && c.missing ? ` \u2014 missing: ${c.missing}` : "";
|
|
264
|
+
return `${MARK[state]} ${lines[c.id]}${still}${missing}`;
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
193
268
|
// src/lib/createEntryPreviewResult.ts
|
|
194
269
|
async function resolvePreviewPreflight(result2, links) {
|
|
195
270
|
if (!links?.length) return result2.preflight;
|
|
@@ -729,57 +804,6 @@ function buildCaptureForRealNextAction(payload) {
|
|
|
729
804
|
}];
|
|
730
805
|
}
|
|
731
806
|
|
|
732
|
-
// src/lib/checklist.ts
|
|
733
|
-
var CHECKLIST_LINES = {
|
|
734
|
-
purpose: {
|
|
735
|
-
stranger_test: "Explain it so a stranger knows why this company exists",
|
|
736
|
-
specificity: "Say what would be missing from the world without this company",
|
|
737
|
-
not_a_platitude: "Say something a reasonable company would not also claim",
|
|
738
|
-
beneficiary_named: "Name who it is for: people, a community or part of the world, specific enough to say who it is not for",
|
|
739
|
-
durable_not_dated: "Leave out dates and planning cycles; those belong on goals"
|
|
740
|
-
},
|
|
741
|
-
vision: {
|
|
742
|
-
specificity: "Say what will be different, in words only this product could use",
|
|
743
|
-
beneficiary: "Name who it is for: people, a community or part of the world, specific enough to say who it is not for",
|
|
744
|
-
falsifiability: "Choose a direction a reasonable rival would reject",
|
|
745
|
-
stranger_test: "Explain it so a stranger knows what will be different, and for whom"
|
|
746
|
-
},
|
|
747
|
-
strategy: {
|
|
748
|
-
coherent_narrative: "Connect the diagnosis, the policy and the first actions",
|
|
749
|
-
decisiveness: "Say what you will not do",
|
|
750
|
-
time_bounded: "Name a horizon: a date, a duration or a milestone"
|
|
751
|
-
},
|
|
752
|
-
objective: {
|
|
753
|
-
outcome_not_activity: "State what will be true once it is met, not the work to do",
|
|
754
|
-
serves_strategy: "Say what must be true for the strategy to succeed",
|
|
755
|
-
observable: "Make it something a reader could see has happened"
|
|
756
|
-
},
|
|
757
|
-
key_result: {
|
|
758
|
-
measurable: "Name a measure and the target value or state",
|
|
759
|
-
bounded: "Say by when, or how often, the measure is read",
|
|
760
|
-
moves_the_objective: "Pick a result that shows the objective is happening, not that work was done"
|
|
761
|
-
},
|
|
762
|
-
/** One line per spine rung, keyed by the rung it reaches (kernel/strategy/spineTrace.ts order). */
|
|
763
|
-
strategy_link: {
|
|
764
|
-
key_result: "Link this work to an accepted key result it validates",
|
|
765
|
-
objective: "Put the key result under an accepted objective",
|
|
766
|
-
strategy: "Put the objective under an accepted strategy",
|
|
767
|
-
vision: "Put the strategy under an accepted vision",
|
|
768
|
-
purpose: "Put the vision under an accepted purpose"
|
|
769
|
-
}
|
|
770
|
-
};
|
|
771
|
-
var MARK = { met: "[x]", not_yet: "[ ]", judging: "[~]" };
|
|
772
|
-
function renderChecklist(canonicalKey, criteria, judging) {
|
|
773
|
-
const lines = CHECKLIST_LINES[canonicalKey];
|
|
774
|
-
if (!lines) return null;
|
|
775
|
-
return criteria.filter((c) => lines[c.id] !== void 0).map((c) => {
|
|
776
|
-
const state = !c.passed ? "not_yet" : judging ? "judging" : "met";
|
|
777
|
-
const still = state === "judging" ? " (still judging)" : "";
|
|
778
|
-
const missing = state === "not_yet" && c.missing ? ` \u2014 missing: ${c.missing}` : "";
|
|
779
|
-
return `${MARK[state]} ${lines[c.id]}${still}${missing}`;
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
|
|
783
807
|
// src/lib/rubricFormatting.ts
|
|
784
808
|
function verdictQualityPhrase(result2) {
|
|
785
809
|
const v = result2?.verdict;
|
|
@@ -911,8 +935,8 @@ var RELATION_BATCH_MAX = 25;
|
|
|
911
935
|
|
|
912
936
|
// src/tools/smart-capture.ts
|
|
913
937
|
var CAPTURE_LINK_OUTCOMES = ["written", "refused"];
|
|
914
|
-
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.";
|
|
915
938
|
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.";
|
|
939
|
+
var LINK_TYPE_DESCRIPTION = `Relation type. The spine links: ${SPINE_RUNG_LINES.join(" ")}`;
|
|
916
940
|
var captureSchema = z5.object({
|
|
917
941
|
collection: z5.string().optional().describe("Collection slug, e.g. 'tensions', 'rules', 'glossary', 'decisions'. Optional \u2014 the kernel classifies when omitted."),
|
|
918
942
|
name: z5.string().describe("Display name \u2014 be specific (e.g. 'Convex adjacency list won't scale for graph traversal')"),
|
|
@@ -924,7 +948,7 @@ var captureSchema = z5.object({
|
|
|
924
948
|
// TASK-380: mirrors whyPolicy.ts
|
|
925
949
|
links: z5.array(z5.object({
|
|
926
950
|
to: z5.string().describe("Target entry ID (e.g. '<PREFIX>-<n>')"),
|
|
927
|
-
type: z5.string().describe(
|
|
951
|
+
type: z5.string().describe(LINK_TYPE_DESCRIPTION)
|
|
928
952
|
})).optional().describe("Links written with the entry in one transaction; each returns written or refused with a reason; a refused link fails nothing."),
|
|
929
953
|
sourceRef: z5.string().optional().describe("URI or path of the source document backing this entry (e.g. 'meeting-2026-03-28.md', 'import://batch-5'). Stored as top-level entry field, not in data."),
|
|
930
954
|
sourceExcerpt: z5.string().optional().describe("Verbatim excerpt from the source that backs this entry's claims. Stored as top-level entry field, not in data."),
|
|
@@ -981,7 +1005,7 @@ var captureSuccessOutputSchema = z5.object({
|
|
|
981
1005
|
required: z5.boolean().optional()
|
|
982
1006
|
})).optional(),
|
|
983
1007
|
// TASK-160: the server's birth-floor verdict must survive the strict output contract.
|
|
984
|
-
spineFloor: z5.object({ relationType: z5.string(), parentKind: z5.string(), exempt: z5.boolean() }).optional(),
|
|
1008
|
+
spineFloor: z5.object({ relationType: z5.string(), parentKind: z5.string(), exempt: z5.boolean(), linkWritten: z5.boolean().optional() }).optional(),
|
|
985
1009
|
// WP-751 M3a-1b (TASK-189, DEC-1700): `coherencyRefusal` and `contradictionAdvisory` are deleted with the
|
|
986
1010
|
// client-side auto-commit attempt that alone populated them (its gate refusal, the commit's advisory).
|
|
987
1011
|
replayed: z5.literal(true).optional(),
|
|
@@ -1008,7 +1032,7 @@ var captureCompoundSchema = z5.object({
|
|
|
1008
1032
|
why: z5.string().max(ENTRY_BODY_MAX_CHARS).optional().describe("Why this entry exists."),
|
|
1009
1033
|
links: z5.array(z5.object({
|
|
1010
1034
|
to: z5.string().max(200).describe("Target entry ID (e.g. '<PREFIX>-<n>')"),
|
|
1011
|
-
type: z5.string().max(200).describe(
|
|
1035
|
+
type: z5.string().max(200).describe(LINK_TYPE_DESCRIPTION)
|
|
1012
1036
|
})).max(RELATION_BATCH_MAX).optional().describe("Links written with the entry in one transaction; each returns written or refused with a reason; a refused link fails nothing."),
|
|
1013
1037
|
sourceRef: z5.string().max(2e3).optional().describe("URI or path of the source document backing this entry."),
|
|
1014
1038
|
sourceExcerpt: z5.string().max(SOURCE_EXCERPT_MAX_CHARS).optional().describe("Verbatim excerpt from the source backing this entry's claims."),
|
|
@@ -1260,10 +1284,9 @@ Use \`context action=get\` to inspect the existing entry, or \`entries action=up
|
|
|
1260
1284
|
lines.push(`## Accepted: ${finalEntryId}`);
|
|
1261
1285
|
lines.push(`**${name}** added to your knowledge base.`);
|
|
1262
1286
|
} else if (spineFloor) {
|
|
1263
|
-
|
|
1264
|
-
lines.push("## Saved as draft");
|
|
1265
|
-
lines.push(
|
|
1266
|
-
lines.push(`Next: \`entries action=commit entryId="${finalEntryId}"\`.`);
|
|
1287
|
+
const { relationType: relationType2, parentKind, exempt, linkWritten } = spineFloor, id = finalEntryId, to = `<${parentKind.toUpperCase().replace(/_/g, "-")}-ID>`;
|
|
1288
|
+
lines.push("", "## Saved as draft", `**${name}** ${exempt ? SPINE_FLOOR_EXEMPT_SENTENCE : `kept as a draft. ${rungLine(parentKind)}`}`);
|
|
1289
|
+
lines.push(exempt || linkWritten ? `Next: \`entries action=commit entryId="${id}"\`.` : `Next: \`relations action=create from="${id}" type="${relationType2}" to="${to}"\`.`);
|
|
1267
1290
|
}
|
|
1268
1291
|
if (groundingCaveat) lines.push("", groundingCaveat);
|
|
1269
1292
|
const named = groundingReport.duplicates.slice(0, 3).map((d) => `${d.entryId} "${d.name}"`).join(", ");
|
|
@@ -1310,7 +1333,7 @@ Use \`context action=get\` to inspect the existing entry, or \`entries action=up
|
|
|
1310
1333
|
next.push({ tool: "context", description: "See its connections", parameters: { action: "find", entryId: finalEntryId } });
|
|
1311
1334
|
} else {
|
|
1312
1335
|
next.push({ tool: "context", description: "Review the draft alternative", parameters: { action: "get", entryId: finalEntryId } });
|
|
1313
|
-
next.push({ tool: "entries", description: "Accept onto Chain", parameters: { action: "commit", entryId: finalEntryId } });
|
|
1336
|
+
next.push(spineFloor && !spineFloor.exempt && !spineFloor.linkWritten ? { tool: "relations", description: "Write the spine link it owes", parameters: { action: "create", from: finalEntryId, type: spineFloor.relationType } } : { tool: "entries", description: "Accept onto Chain", parameters: { action: "commit", entryId: finalEntryId } });
|
|
1314
1337
|
}
|
|
1315
1338
|
const qualityPhrase = verdictQualityPhrase(verdictResult);
|
|
1316
1339
|
const summary = finalStatus === "committed" ? `Captured and accepted ${finalEntryId} (${name}) onto ${landedCollection}. ${qualityPhrase}` : `Captured ${finalEntryId} (${name}) as draft in ${landedCollection}. ${qualityPhrase}`;
|
|
@@ -3019,11 +3042,91 @@ ${footer}` }],
|
|
|
3019
3042
|
|
|
3020
3043
|
// src/tools/relations.ts
|
|
3021
3044
|
import { z as z13 } from "zod/v3";
|
|
3045
|
+
|
|
3046
|
+
// src/lib/relationTypes.ts
|
|
3047
|
+
var ALLOWED_RELATION_TYPES_LIST = [
|
|
3048
|
+
// Core semantic types
|
|
3049
|
+
"informs",
|
|
3050
|
+
"governs",
|
|
3051
|
+
"surfaces_tension_in",
|
|
3052
|
+
"defines_term_for",
|
|
3053
|
+
"belongs_to",
|
|
3054
|
+
"references",
|
|
3055
|
+
"related_to",
|
|
3056
|
+
"fills_slot",
|
|
3057
|
+
"commits_to",
|
|
3058
|
+
"informed_by",
|
|
3059
|
+
"depends_on",
|
|
3060
|
+
"conflicts_with",
|
|
3061
|
+
"confused_with",
|
|
3062
|
+
// WP-449: Glossary Foundation — negative assertion for agents (spec §4.5)
|
|
3063
|
+
"do_not_confuse_with",
|
|
3064
|
+
"replaces",
|
|
3065
|
+
"part_of",
|
|
3066
|
+
"constrains",
|
|
3067
|
+
// Epistemic types — evidence linking
|
|
3068
|
+
"validates",
|
|
3069
|
+
"invalidates",
|
|
3070
|
+
// Extended types
|
|
3071
|
+
"governed_by",
|
|
3072
|
+
"alternative_to",
|
|
3073
|
+
"has_proposal",
|
|
3074
|
+
"requests_promotion_of",
|
|
3075
|
+
"resolves",
|
|
3076
|
+
// WP-228 Slice 0: Playing Field relation types
|
|
3077
|
+
"experiences",
|
|
3078
|
+
"addresses",
|
|
3079
|
+
// Tier 1 Hierarchy: DEC-401 (experiments prereqs), DEC-402 (risk views)
|
|
3080
|
+
"threatens",
|
|
3081
|
+
"tests",
|
|
3082
|
+
"produces",
|
|
3083
|
+
// WP-274: Capability tier relation types
|
|
3084
|
+
"realizes",
|
|
3085
|
+
"requires",
|
|
3086
|
+
"invests_in",
|
|
3087
|
+
"enables",
|
|
3088
|
+
// PARKED (park note, 2026-08-23) — these 5 verbs are untouched by a separate
|
|
3089
|
+
// governance-verb-ownership review: no delete, no lock, no whitelist, no picker change,
|
|
3090
|
+
// and no relation of this note to that review. Live source-canonicalKey row counts, from
|
|
3091
|
+
// a read-only pass over an uncapped, exhaustive census of a real workspace (measured
|
|
3092
|
+
// 2026-08-23): follows = 11 rows (decision 2, milestone 5, work_package 4);
|
|
3093
|
+
// extracted_from = 4 rows (decision 1, insight 1, tension 1, work_package 1);
|
|
3094
|
+
// part_of_workstream = 0 rows; stated_by = 0 rows; discussed_in = 0 rows. Whether any of
|
|
3095
|
+
// these should be retired, merged, or promoted is a founder call, deliberately deferred —
|
|
3096
|
+
// not decided here, and not decided by silence either. Re-verify the counts before acting
|
|
3097
|
+
// on them; they will drift.
|
|
3098
|
+
// WP-344 S0a: Evolve-First Ingestion relation types
|
|
3099
|
+
"follows",
|
|
3100
|
+
// sequential/derived — new entry follows/continues from another
|
|
3101
|
+
"extracted_from",
|
|
3102
|
+
// provenance — this entry was extracted from a source document
|
|
3103
|
+
// WP-353 S0: scope-propagated commit relation types (DEC-A captured as exploring per RULE-187)
|
|
3104
|
+
"part_of_workstream",
|
|
3105
|
+
// entry belongs to a workstream anchor confirmed at scope step
|
|
3106
|
+
"stated_by",
|
|
3107
|
+
// entry was stated by a speaker in the source document
|
|
3108
|
+
"discussed_in",
|
|
3109
|
+
// entry was discussed within a particular meeting/doc context
|
|
3110
|
+
// Specification types — WP-363 E2: precision alternatives to governs for decision→governance relations
|
|
3111
|
+
"specializes",
|
|
3112
|
+
// DEC narrows or refines a broader STD/PRI — more specific application of the parent
|
|
3113
|
+
"implements",
|
|
3114
|
+
// DEC/WP realizes a BR/STD in concrete operational form
|
|
3115
|
+
// WP-641 S2a (WP-647): belongs_to vocabulary split — the strategy-scope successor.
|
|
3116
|
+
// The other two successors (authority_domain, domain_routed_to) and `blocks` are no longer
|
|
3117
|
+
// legal to write: a new workspace carries none of them (DEC-1845, TASK-331, TASK-425).
|
|
3118
|
+
"anchored_to"
|
|
3119
|
+
// strategy-scope anchor: a raw strategy entry belongs_to a product (DEC-1523)
|
|
3120
|
+
];
|
|
3121
|
+
var ALLOWED_RELATION_TYPES = new Set(ALLOWED_RELATION_TYPES_LIST);
|
|
3122
|
+
|
|
3123
|
+
// src/tools/relations.ts
|
|
3022
3124
|
var RELATIONS_ACTIONS = ["create", "batch-create", "delete"];
|
|
3125
|
+
var relationType = z13.enum(ALLOWED_RELATION_TYPES_LIST);
|
|
3023
3126
|
var relationItemSchema = z13.object({
|
|
3024
3127
|
from: z13.string().max(200),
|
|
3025
3128
|
to: z13.string().max(200),
|
|
3026
|
-
type:
|
|
3129
|
+
type: relationType
|
|
3027
3130
|
});
|
|
3028
3131
|
var relationsSchema = z13.object({
|
|
3029
3132
|
action: z13.enum(RELATIONS_ACTIONS).describe(
|
|
@@ -3031,16 +3134,17 @@ var relationsSchema = z13.object({
|
|
|
3031
3134
|
),
|
|
3032
3135
|
from: z13.string().max(200).optional().describe("For 'create'/'delete': source entry ID."),
|
|
3033
3136
|
to: z13.string().max(200).optional().describe("For 'create'/'delete': target entry ID."),
|
|
3034
|
-
|
|
3137
|
+
// TASK-1026: the batch item's type is served as a $ref to this one, so the description holds for both; a second enum instance costs 590 bytes of the 64 KB budget (DEC-1981).
|
|
3138
|
+
type: relationType.optional().describe("Relation type, for create, delete and each batch-create item."),
|
|
3035
3139
|
relations: z13.array(relationItemSchema).min(1).max(RELATION_BATCH_MAX).optional().describe("For 'batch-create': array of {from, to, type}."),
|
|
3036
3140
|
// WP-316 S3: Preview gate — dry-run mode for action=create.
|
|
3037
|
-
preview: z13.boolean().optional().describe("For 'create': if true,
|
|
3141
|
+
preview: z13.boolean().optional().describe("For 'create': if true, checks the relation and writes nothing.")
|
|
3038
3142
|
});
|
|
3039
3143
|
var relationsCreateVariant = z13.object({
|
|
3040
3144
|
action: z13.literal("create"),
|
|
3041
3145
|
from: z13.string().max(200),
|
|
3042
3146
|
to: z13.string().max(200),
|
|
3043
|
-
type:
|
|
3147
|
+
type: relationType,
|
|
3044
3148
|
preview: z13.boolean().optional()
|
|
3045
3149
|
});
|
|
3046
3150
|
var relationsBatchCreateVariant = z13.object({
|
|
@@ -3051,7 +3155,7 @@ var relationsDeleteVariant = z13.object({
|
|
|
3051
3155
|
action: z13.literal("delete"),
|
|
3052
3156
|
from: z13.string().max(200),
|
|
3053
3157
|
to: z13.string().max(200),
|
|
3054
|
-
type:
|
|
3158
|
+
type: relationType
|
|
3055
3159
|
});
|
|
3056
3160
|
var relationsActionUnion = z13.discriminatedUnion("action", [
|
|
3057
3161
|
relationsCreateVariant,
|
|
@@ -3073,7 +3177,9 @@ function registerRelationsTools(server) {
|
|
|
3073
3177
|
"relations",
|
|
3074
3178
|
{
|
|
3075
3179
|
title: "Relations",
|
|
3076
|
-
description:
|
|
3180
|
+
description: `Create and delete relations between entries, each written only on request with exactly the verb you send. Read relations with the read-only \`context action=find\`.
|
|
3181
|
+
|
|
3182
|
+
The spine links, child to parent: ${SPINE_RUNG_LINES.join(" ")}`,
|
|
3077
3183
|
inputSchema: relationsSchema,
|
|
3078
3184
|
// DEC-1894 / DEC-74: find lives on the read-only `context` tool; every action left here writes.
|
|
3079
3185
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
@@ -4909,27 +5015,27 @@ async function handleBuild(server, entryId, maxHops) {
|
|
|
4909
5015
|
)
|
|
4910
5016
|
};
|
|
4911
5017
|
}
|
|
4912
|
-
async function handleCrossCut(
|
|
5018
|
+
async function handleCrossCut(relationType2) {
|
|
4913
5019
|
const result2 = await kernelQuery("chain.structuralAggregation", {
|
|
4914
|
-
relationType
|
|
5020
|
+
relationType: relationType2
|
|
4915
5021
|
});
|
|
4916
5022
|
const groupKeys = Object.keys(result2.groups);
|
|
4917
5023
|
if (result2.totalCount === 0) {
|
|
4918
5024
|
return {
|
|
4919
5025
|
content: [{
|
|
4920
5026
|
type: "text",
|
|
4921
|
-
text: `# Cross-Cut: ${
|
|
5027
|
+
text: `# Cross-Cut: ${relationType2}
|
|
4922
5028
|
|
|
4923
|
-
No relations of type '${
|
|
5029
|
+
No relations of type '${relationType2}' found in this workspace.`
|
|
4924
5030
|
}],
|
|
4925
5031
|
structuredContent: success(
|
|
4926
|
-
`No '${
|
|
5032
|
+
`No '${relationType2}' relations found.`,
|
|
4927
5033
|
{ groups: {}, totalCount: 0, truncated: false }
|
|
4928
5034
|
)
|
|
4929
5035
|
};
|
|
4930
5036
|
}
|
|
4931
5037
|
const lines = [
|
|
4932
|
-
`# Cross-Cut: ${
|
|
5038
|
+
`# Cross-Cut: ${relationType2}`,
|
|
4933
5039
|
`_${result2.totalCount} relations across ${groupKeys.length} source collection${groupKeys.length === 1 ? "" : "s"}${result2.truncated ? " (TRUNCATED \u2014 more relations exist)" : ""}_`,
|
|
4934
5040
|
""
|
|
4935
5041
|
];
|
|
@@ -4948,18 +5054,18 @@ No relations of type '${relationType}' found in this workspace.`
|
|
|
4948
5054
|
return {
|
|
4949
5055
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
4950
5056
|
structuredContent: success(
|
|
4951
|
-
`${result2.totalCount} '${
|
|
5057
|
+
`${result2.totalCount} '${relationType2}' relations across ${groupKeys.length} collections.${result2.truncated ? " Truncated." : ""}`,
|
|
4952
5058
|
result2
|
|
4953
5059
|
)
|
|
4954
5060
|
};
|
|
4955
5061
|
}
|
|
4956
|
-
async function handleChainWalk(entryId, direction, maxHops,
|
|
5062
|
+
async function handleChainWalk(entryId, direction, maxHops, relationType2) {
|
|
4957
5063
|
const clampedHops = Math.max(1, Math.min(4, maxHops));
|
|
4958
5064
|
const result2 = await kernelQuery("chain.deepChainWalk", {
|
|
4959
5065
|
entryId,
|
|
4960
5066
|
direction,
|
|
4961
5067
|
maxHops: clampedHops,
|
|
4962
|
-
...
|
|
5068
|
+
...relationType2 ? { relationType: relationType2 } : {}
|
|
4963
5069
|
});
|
|
4964
5070
|
if (result2.error) {
|
|
4965
5071
|
return notFoundResult(entryId, result2.error);
|
|
@@ -4970,7 +5076,7 @@ async function handleChainWalk(entryId, direction, maxHops, relationType) {
|
|
|
4970
5076
|
type: "text",
|
|
4971
5077
|
text: `# Chain Walk: ${entryId}
|
|
4972
5078
|
|
|
4973
|
-
_No ${direction} relations found${
|
|
5079
|
+
_No ${direction} relations found${relationType2 ? ` of type '${relationType2}'` : ""}._
|
|
4974
5080
|
|
|
4975
5081
|
Try \`context action=chain entryId="${entryId}" direction="${direction === "outgoing" ? "incoming" : "outgoing"}"\` or remove the relationType filter.`
|
|
4976
5082
|
}],
|
|
@@ -4982,7 +5088,7 @@ Try \`context action=chain entryId="${entryId}" direction="${direction === "outg
|
|
|
4982
5088
|
}
|
|
4983
5089
|
const lines = [
|
|
4984
5090
|
`# Chain Walk: ${entryId}`,
|
|
4985
|
-
`**Direction:** ${direction} | **Depth:** ${clampedHops}${
|
|
5091
|
+
`**Direction:** ${direction} | **Depth:** ${clampedHops}${relationType2 ? ` | **Type filter:** ${relationType2}` : ""}`,
|
|
4986
5092
|
`**Nodes:** ${result2.totalNodes}${result2.truncated ? " (TRUNCATED at 100)" : ""} | **Edges:** ${result2.edges.length} | **Cycles:** ${result2.cycles.length}`,
|
|
4987
5093
|
""
|
|
4988
5094
|
];
|
|
@@ -10218,6 +10324,7 @@ function formatNoticeLines(notices) {
|
|
|
10218
10324
|
}
|
|
10219
10325
|
|
|
10220
10326
|
// src/tools/session.ts
|
|
10327
|
+
var START_PREFLIGHT_NEXT = 'Next: context action=preflight collection=<kind> links=[{"type":"part_of","entryId":"<PARENT-ID>"}]';
|
|
10221
10328
|
var SESSION_ACTIONS = [
|
|
10222
10329
|
"start",
|
|
10223
10330
|
"close",
|
|
@@ -10352,6 +10459,7 @@ async function handleStart2(data) {
|
|
|
10352
10459
|
surface: "mcp",
|
|
10353
10460
|
sessionStart: true
|
|
10354
10461
|
}));
|
|
10462
|
+
lines.push("", ...SPINE_LINKS_LINES, ...result2.briefing?.firstReply ? [] : [START_PREFLIGHT_NEXT]);
|
|
10355
10463
|
const summary = `Session ${result2.sessionId} active. Workspace ${result2.workspaceName}. Pass sessionId="${result2.sessionId}" on every write. Orient with a task first; workflow checkpoints and organisation-structure writes still require it.`;
|
|
10356
10464
|
return {
|
|
10357
10465
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
@@ -12030,6 +12138,8 @@ var INSTRUCTIONS = [
|
|
|
12030
12138
|
"Pass the `sessionId` that `session action=start` returned on every write: the connection does not remember it.",
|
|
12031
12139
|
"Use `capture` to create an entry and read its reply: it lands as a draft, except a question, which is born open and needs no `entries action=commit`.",
|
|
12032
12140
|
"Link entries with `relations action=create` \u2014 a relation is written only when you ask, with exactly the verb you send.",
|
|
12141
|
+
...SPINE_LINKS_LINES,
|
|
12142
|
+
// TASK-1022 (WP-862): the spine grammar session start prints, a backup for the clients that load instructions
|
|
12033
12143
|
"Finish with `session action=wrapup-review`, then `session action=close`.",
|
|
12034
12144
|
"If the work falls outside the active work packages, name the gap and ask the user before designing it."
|
|
12035
12145
|
].join("\n");
|
|
@@ -12102,4 +12212,4 @@ export {
|
|
|
12102
12212
|
SERVER_VERSION,
|
|
12103
12213
|
createProductBrainServer
|
|
12104
12214
|
};
|
|
12105
|
-
//# sourceMappingURL=chunk-
|
|
12215
|
+
//# sourceMappingURL=chunk-CY37RSBU.js.map
|