@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.
@@ -34,7 +34,7 @@ import {
34
34
  runWithToolContext,
35
35
  setSessionOriented,
36
36
  startAgentSession
37
- } from "./chunk-QUM6WNMF.js";
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("Relation type (e.g. 'governs', 'related_to', 'informs')")
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("Relation type (e.g. 'governs', 'related_to', 'informs')")
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
- lines.push("");
1264
- lines.push("## Saved as draft");
1265
- lines.push(`**${name}** ${spineFloor.exempt ? SPINE_FLOOR_EXEMPT_SENTENCE : SPINE_FLOOR_SENTENCE}`);
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: z13.string().max(200)
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
- type: z13.string().max(200).optional().describe("For 'create'/'delete': relation type."),
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, validates the relation without writing. Returns what would happen. Default false.")
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: z13.string().max(200),
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: z13.string().max(200)
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: "Create and delete relations between entries. A relation is written only when you ask for it, with exactly the verb you send. Read an entry's relations with the read-only `context action=find`.\n\n- **create**: Link two entries with a typed relation.\n- **batch-create**: Create multiple relations in one call (validates every item before writing any).\n- **delete**: Remove a relation by from, to, and type.",
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(relationType) {
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: ${relationType}
5027
+ text: `# Cross-Cut: ${relationType2}
4922
5028
 
4923
- No relations of type '${relationType}' found in this workspace.`
5029
+ No relations of type '${relationType2}' found in this workspace.`
4924
5030
  }],
4925
5031
  structuredContent: success(
4926
- `No '${relationType}' relations found.`,
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: ${relationType}`,
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} '${relationType}' relations across ${groupKeys.length} collections.${result2.truncated ? " Truncated." : ""}`,
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, relationType) {
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
- ...relationType ? { relationType } : {}
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${relationType ? ` of type '${relationType}'` : ""}._
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}${relationType ? ` | **Type filter:** ${relationType}` : ""}`,
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-KU3IBGI4.js.map
12215
+ //# sourceMappingURL=chunk-CY37RSBU.js.map