@productbrain/mcp 0.0.1-beta.37 → 0.0.1-beta.39

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.
@@ -25,7 +25,7 @@ import {
25
25
  startAgentSession,
26
26
  trackWriteTool,
27
27
  translateStaleToolNames
28
- } from "./chunk-Z2FGGJHI.js";
28
+ } from "./chunk-7VJP2IMS.js";
29
29
  import {
30
30
  trackQualityCheck,
31
31
  trackQualityVerdict
@@ -36,10 +36,32 @@ import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js
36
36
 
37
37
  // src/tools/knowledge.ts
38
38
  import { z } from "zod";
39
+ var LEGACY_WORKFLOW_STATUSES = /* @__PURE__ */ new Set([
40
+ "open",
41
+ "pending",
42
+ "decided",
43
+ "proposed",
44
+ "accepted",
45
+ "needs-amendment",
46
+ "withdrawn",
47
+ "review",
48
+ "in-progress",
49
+ "conflict",
50
+ "processing",
51
+ "closed",
52
+ "shaped",
53
+ "bet",
54
+ "building",
55
+ "shipped"
56
+ ]);
39
57
  var updateEntrySchema = z.object({
40
58
  entryId: z.string().describe("Entry ID to update, e.g. 'T-SUPPLIER', 'BR-001'"),
41
59
  name: z.string().optional().describe("New display name"),
42
- status: z.string().optional().describe("New status: draft | active | verified | deprecated"),
60
+ status: z.union([
61
+ z.enum(["draft", "active", "deprecated", "archived"]),
62
+ z.enum(["open", "pending", "decided", "proposed", "accepted", "needs-amendment", "withdrawn", "review", "in-progress", "conflict", "processing", "closed", "shaped", "bet", "building", "shipped"])
63
+ ]).optional().describe("Lifecycle status: draft | active | deprecated | archived. **Workflow values (open, pending, decided\u2026) are deprecated here \u2014 use `workflowStatus` instead. Passing a workflow value as `status` will be auto-routed with a warning until 2026-09-03, then hard-errored.**"),
64
+ workflowStatus: z.enum(["open", "pending", "proposed", "accepted", "needs-amendment", "withdrawn", "review", "in-progress", "decided", "conflict", "processing", "closed"]).optional().describe("Collection workflow state (e.g. 'open', 'pending', 'decided', 'conflict'). Validated against collection's allowed values."),
43
65
  data: z.record(z.unknown()).optional().describe("Fields to update (merged with existing data)"),
44
66
  order: z.number().optional().describe("New sort order"),
45
67
  canonicalKey: z.string().optional().describe("Semantic type (e.g. 'decision', 'tension'). Only changeable on draft/uncommitted entries."),
@@ -61,12 +83,23 @@ function registerKnowledgeTools(server) {
61
83
  inputSchema: updateEntrySchema,
62
84
  annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false }
63
85
  },
64
- async ({ entryId, name, status, data, order, canonicalKey, autoPublish, changeNote }) => {
86
+ async ({ entryId, name, status: rawStatus, workflowStatus: rawWorkflowStatus, data, order, canonicalKey, autoPublish, changeNote }) => {
65
87
  requireWriteAccess();
88
+ let status = rawStatus;
89
+ let workflowStatus = rawWorkflowStatus;
90
+ let deprecationWarning;
91
+ if (rawStatus && LEGACY_WORKFLOW_STATUSES.has(rawStatus)) {
92
+ if (!workflowStatus) {
93
+ workflowStatus = rawStatus;
94
+ }
95
+ status = void 0;
96
+ deprecationWarning = `\u26A0\uFE0F Deprecation: \`status: '${rawStatus}'\` \u2014 use \`workflowStatus: '${rawStatus}'\` instead. Support ends ~2026-09-03.`;
97
+ }
66
98
  const id = await mcpMutation("chain.updateEntry", {
67
99
  entryId,
68
100
  name,
69
101
  status,
102
+ workflowStatus,
70
103
  data,
71
104
  order,
72
105
  canonicalKey,
@@ -79,13 +112,21 @@ function registerKnowledgeTools(server) {
79
112
  const updated = await mcpQuery("chain.getEntry", { entryId });
80
113
  const entryStatus = updated?.status ?? "draft";
81
114
  const versionMode = entryStatus === "active" ? "published" : `saved as ${entryStatus}`;
115
+ const responseLines = [
116
+ `# Entry Updated`,
117
+ ``,
118
+ `**${entryId}** \u2014 ${versionMode}.`,
119
+ ``,
120
+ `Internal ID: ${id}`,
121
+ `**Workspace:** ${wsCtx.workspaceSlug} (${wsCtx.workspaceId})`
122
+ ];
123
+ if (deprecationWarning) {
124
+ responseLines.push("");
125
+ responseLines.push(deprecationWarning);
126
+ }
82
127
  return {
83
- content: [{ type: "text", text: `# Entry Updated
84
-
85
- **${entryId}** \u2014 ${versionMode}.
86
-
87
- Internal ID: ${id}
88
- **Workspace:** ${wsCtx.workspaceSlug} (${wsCtx.workspaceId})` }]
128
+ content: [{ type: "text", text: responseLines.join("\n") }],
129
+ structuredContent: { entryId: id, versionMode }
89
130
  };
90
131
  }
91
132
  );
@@ -125,7 +166,7 @@ ${formatted}` }]
125
166
  },
126
167
  async ({ entryId }) => {
127
168
  requireWriteAccess();
128
- const { runContradictionCheck } = await import("./smart-capture-BIKEZC6P.js");
169
+ const { runContradictionCheck } = await import("./smart-capture-E53YEHHO.js");
129
170
  const entry = await mcpQuery("chain.getEntry", { entryId });
130
171
  if (!entry) {
131
172
  return {
@@ -225,7 +266,7 @@ var entriesSchema = z2.object({
225
266
  entryId: z2.string().optional().describe("Entry ID for get action, e.g. 'T-SUPPLIER', 'BR-001'"),
226
267
  entryIds: z2.array(z2.string()).min(1).max(20).optional().describe("Entry IDs for batch action, e.g. ['TYPE-strategy', 'STR-jljeg7']"),
227
268
  collection: z2.string().optional().describe("Collection slug for list/search, e.g. 'glossary', 'tracking-events', 'business-rules'"),
228
- status: z2.string().optional().describe("Filter: draft | active | verified | deprecated"),
269
+ status: z2.string().optional().describe("Filter: draft | active | deprecated | archived"),
229
270
  tag: z2.string().optional().describe("Filter by internal tag for list"),
230
271
  label: z2.string().optional().describe("Filter by label slug for list \u2014 matches entries across all collections"),
231
272
  query: z2.string().optional().describe("Search text for search action (min 2 characters)")
@@ -334,7 +375,7 @@ async function handleGet(entryId) {
334
375
  "",
335
376
  `# ${e.entryId ? `${e.entryId}: ` : ""}${e.name}`,
336
377
  "",
337
- `**Status:** ${e.status}`,
378
+ `**Status:** ${e.status}${e.workflowStatus ? ` / ${e.workflowStatus}` : ""}`,
338
379
  `**Type:** ${e.canonicalKey ?? "untyped"}`
339
380
  ];
340
381
  if (e.data && typeof e.data === "object") {
@@ -371,7 +412,8 @@ async function handleGet(entryId) {
371
412
  name: String(e.name ?? ""),
372
413
  collection: String(e.canonicalKey ?? ""),
373
414
  status: String(e.status ?? ""),
374
- entries: [{ entryId: e.entryId, name: e.name, status: e.status, collectionName: String(e.collectionName ?? e.canonicalKey ?? "\u2014") }],
415
+ ...e.workflowStatus ? { workflowStatus: String(e.workflowStatus) } : {},
416
+ entries: [{ entryId: e.entryId, name: e.name, status: e.status, ...e.workflowStatus ? { workflowStatus: e.workflowStatus } : {}, collectionName: String(e.collectionName ?? e.canonicalKey ?? "\u2014") }],
375
417
  ...e.data && typeof e.data === "object" ? { data: e.data } : {},
376
418
  ...Array.isArray(e.relations) && e.relations.length > 0 ? {
377
419
  relations: e.relations.map((r) => ({
@@ -404,7 +446,7 @@ async function handleBatch(entryIds) {
404
446
  const entry = r.entry;
405
447
  lines.push(`## ${entry.entryId ? `${entry.entryId}: ` : ""}${entry.name}`);
406
448
  lines.push("");
407
- lines.push(`**Status:** ${entry.status}`);
449
+ lines.push(`**Status:** ${entry.status}${entry.workflowStatus ? ` / ${entry.workflowStatus}` : ""}`);
408
450
  lines.push(`**Type:** ${entry.canonicalKey ?? "untyped"}`);
409
451
  if (entry.data && typeof entry.data === "object") {
410
452
  lines.push("");
@@ -444,6 +486,7 @@ async function handleBatch(entryIds) {
444
486
  name: String(entry.name ?? ""),
445
487
  collection: String(entry.canonicalKey ?? ""),
446
488
  status: String(entry.status ?? ""),
489
+ ...entry.workflowStatus ? { workflowStatus: String(entry.workflowStatus) } : {},
447
490
  ...entry.data && typeof entry.data === "object" ? { data: entry.data } : {}
448
491
  };
449
492
  });
@@ -471,8 +514,9 @@ async function handleList(collection, status, tag, label) {
471
514
  }
472
515
  const formatted = entries.map((e) => {
473
516
  const id = e.entryId ? `**${e.entryId}:** ` : "";
517
+ const statusDisplay = e.workflowStatus ? `${e.status} / ${e.workflowStatus}` : e.status;
474
518
  const dataPreview = e.data ? Object.entries(e.data).slice(0, 4).map(([k, v]) => ` ${k}: ${typeof v === "string" ? v.substring(0, 120) : JSON.stringify(v)}`).join("\n") : "";
475
- return `- ${id}${e.name} \`${e.status}\`${dataPreview ? `
519
+ return `- ${id}${e.name} \`${statusDisplay}\`${dataPreview ? `
476
520
  ${dataPreview}` : ""}`;
477
521
  }).join("\n\n");
478
522
  const scope = collection ? ` in \`${collection}\`` : "";
@@ -480,7 +524,8 @@ ${dataPreview}` : ""}`;
480
524
  entryId: String(e.entryId ?? ""),
481
525
  name: String(e.name ?? ""),
482
526
  collection: String(e.canonicalKey ?? collection ?? ""),
483
- status: String(e.status ?? "")
527
+ status: String(e.status ?? ""),
528
+ ...e.workflowStatus ? { workflowStatus: e.workflowStatus } : {}
484
529
  }));
485
530
  return {
486
531
  content: [{ type: "text", text: `## List Result
@@ -523,13 +568,14 @@ async function handleSearch(server, query, collection, status) {
523
568
  const collSummary = [...countsBySlug.entries()].sort((a, b) => b[1] - a[1]).map(([slug, count]) => `${count} ${slug}`).join(", ");
524
569
  const formatted = results.map((e) => {
525
570
  const id = e.entryId ? `**${e.entryId}:** ` : "";
571
+ const statusDisplay = e.workflowStatus ? `${e.status} / ${e.workflowStatus}` : e.status;
526
572
  const col = collMap.get(e.collectionId ?? "");
527
573
  const colTag = col ? ` [${col.slug}]` : "";
528
574
  const typeTag = e.canonicalKey ? ` (${e.canonicalKey})` : "";
529
575
  const desc = extractPreview(e.data, 150);
530
576
  const preview = desc ? `
531
577
  ${desc}` : "";
532
- return `- ${id}${e.name} \`${e.status}\`${colTag}${typeTag}${preview}`;
578
+ return `- ${id}${e.name} \`${statusDisplay}\`${colTag}${typeTag}${preview}`;
533
579
  }).join("\n");
534
580
  const header = `## Search Result
535
581
 
@@ -542,6 +588,7 @@ async function handleSearch(server, query, collection, status) {
542
588
  name: String(e.name ?? ""),
543
589
  collection: collMap.get(e.collectionId ?? "")?.slug ?? "unknown",
544
590
  status: String(e.status ?? ""),
591
+ ...e.workflowStatus ? { workflowStatus: e.workflowStatus } : {},
545
592
  ...e.score != null ? { score: e.score } : {}
546
593
  }));
547
594
  const entriesForView = structuredResults.map((e) => ({
@@ -991,11 +1038,14 @@ function annotateStaleNames(text) {
991
1038
  var CONTEXT_ACTIONS = ["gather", "build"];
992
1039
  var contextSchema = z5.object({
993
1040
  action: z5.enum(CONTEXT_ACTIONS).describe(
994
- "'gather': assemble knowledge context (entry graph, task auto-load, or graph mode). 'build': structured build spec for an entry."
1041
+ "'gather': assemble knowledge context (entry graph, task auto-load, journey mode, or graph mode). 'build': structured build spec for an entry."
995
1042
  ),
996
1043
  entryId: z5.string().optional().describe("Entry ID for graph traversal or build, e.g. 'FEAT-001', 'GT-019'"),
1044
+ mapEntryId: z5.string().optional().describe(
1045
+ "Journey map entry ID for journey-aware context (gather only). Returns context organised by journey stage. Takes precedence over entryId when both are supplied. Example: 'MAP-1'."
1046
+ ),
997
1047
  task: z5.string().optional().describe("Natural-language task description for auto-loading relevant context (gather only)"),
998
- mode: z5.enum(["search", "graph"]).default("search").optional().describe("For gather: 'search' (default) or 'graph' (enhanced with provenance paths)"),
1048
+ mode: z5.enum(["search", "graph"]).default("search").optional().describe("For gather: 'search' (default) or 'graph' (enhanced with provenance paths). Ignored when mapEntryId is provided."),
999
1049
  maxHops: z5.number().min(1).max(3).default(2).describe("Relation traversal depth (1=direct only, 2=default, 3=wide net)"),
1000
1050
  maxResults: z5.number().min(1).max(25).default(10).optional().describe("Max entries to return in gather task mode (default 10)")
1001
1051
  });
@@ -1015,9 +1065,12 @@ function registerContextTools(server) {
1015
1065
  const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
1016
1066
  return { content: [{ type: "text", text: `Invalid arguments: ${issues}` }] };
1017
1067
  }
1018
- const { action, entryId, task, mode, maxHops, maxResults } = parsed.data;
1068
+ const { action, entryId, mapEntryId, task, mode, maxHops, maxResults } = parsed.data;
1019
1069
  return runWithToolContext({ tool: "context", action }, async () => {
1020
1070
  if (action === "gather") {
1071
+ if (mapEntryId) {
1072
+ return handleJourneyGather(server, mapEntryId, maxHops ?? 2, maxResults ?? 20);
1073
+ }
1021
1074
  return handleGather(server, entryId, task, mode ?? "search", maxHops ?? 2, maxResults ?? 10);
1022
1075
  }
1023
1076
  if (action === "build") {
@@ -1233,6 +1286,80 @@ Use \`graph action=suggest\` to discover potential connections, or \`relations a
1233
1286
  structuredContent: { nodes, edges }
1234
1287
  };
1235
1288
  }
1289
+ async function handleJourneyGather(server, mapEntryId, maxHops, maxResults) {
1290
+ await server.sendLoggingMessage({
1291
+ level: "info",
1292
+ data: `Loading journey context for map ${mapEntryId}...`,
1293
+ logger: "product-brain"
1294
+ });
1295
+ const result = await mcpQuery("chain.journeyAwareGatherContext", {
1296
+ mapEntryId,
1297
+ maxHops,
1298
+ maxResults
1299
+ });
1300
+ if (!result) {
1301
+ return { content: [{ type: "text", text: `Journey map \`${mapEntryId}\` not found or has no slot data. Verify the map entry ID.` }] };
1302
+ }
1303
+ if (result.stages.length === 0 || result.seedCount === 0) {
1304
+ return {
1305
+ content: [{
1306
+ type: "text",
1307
+ text: `# Journey Context: ${result.map.name}
1308
+
1309
+ _No ingredients found in this journey map._
1310
+
1311
+ Add journey steps via MCP: \`map-slot action=add mapEntryId="${mapEntryId}" slotId="..." ingredientEntryId="..."\``
1312
+ }]
1313
+ };
1314
+ }
1315
+ const lines = [
1316
+ `# Journey Context: ${result.map.name}`,
1317
+ `**Template:** ${result.map.templateName} | **Map ID:** ${result.map.entryId}`,
1318
+ `**Steps scanned:** ${result.seedCount}${result.truncated ? ` (capped at 10 \u2014 ${result.stages.flatMap((s) => s.ingredients).length} total ingredients)` : ""}`,
1319
+ `**Related knowledge:** ${result.totalFound} entries`,
1320
+ "",
1321
+ "## Journey Stages"
1322
+ ];
1323
+ for (const stage of result.stages) {
1324
+ const ingredientList = stage.ingredients.length === 0 ? "_empty_" : stage.ingredients.map((i) => `${i.entryId}: ${i.name}`).join(", ");
1325
+ lines.push(`- **${stage.label}**: ${ingredientList}`);
1326
+ }
1327
+ if (result.context.length > 0) {
1328
+ lines.push("");
1329
+ lines.push("## Governance & Intelligence");
1330
+ lines.push(`_Entries from the knowledge graph connected to journey steps:_`);
1331
+ lines.push("");
1332
+ const byCollection = /* @__PURE__ */ new Map();
1333
+ for (const entry of result.context) {
1334
+ const key = entry.collectionName;
1335
+ if (!byCollection.has(key)) byCollection.set(key, []);
1336
+ byCollection.get(key).push(entry);
1337
+ }
1338
+ for (const [collName, entries] of byCollection) {
1339
+ lines.push(`### ${collName} (${entries.length})`);
1340
+ for (const e of entries) {
1341
+ const arrow = e.relationDirection === "outgoing" ? "\u2192" : "\u2190";
1342
+ const id = e.entryId ? `**${e.entryId}:** ` : "";
1343
+ const scoreLabel = typeof e.score === "number" ? ` ${e.score}` : "";
1344
+ lines.push(`- ${arrow} ${id}${e.name}${scoreLabel}`);
1345
+ }
1346
+ lines.push("");
1347
+ }
1348
+ }
1349
+ lines.push(`_Use \`entries action=get\` for full details. Link governance to steps: \`relations action=create\`._`);
1350
+ return {
1351
+ content: [{ type: "text", text: lines.join("\n") }],
1352
+ structuredContent: {
1353
+ map: result.map,
1354
+ stages: result.stages,
1355
+ context: result.context.map((e) => ({
1356
+ entryId: e.entryId ?? "",
1357
+ name: e.name,
1358
+ collectionName: e.collectionName
1359
+ }))
1360
+ }
1361
+ };
1362
+ }
1236
1363
  async function handleBuild(server, entryId, maxHops) {
1237
1364
  requireActiveSession();
1238
1365
  await server.sendLoggingMessage({
@@ -1569,7 +1696,7 @@ function registerLabelTools(server) {
1569
1696
  }
1570
1697
 
1571
1698
  // src/tools/health.ts
1572
- import { z as z19 } from "zod";
1699
+ import { z as z20 } from "zod";
1573
1700
 
1574
1701
  // src/tools/session.ts
1575
1702
  import { z as z9 } from "zod";
@@ -2991,17 +3118,11 @@ function scoreElements(ctx) {
2991
3118
  } else {
2992
3119
  missing.push("Identify solution elements \u2014 breadboard-level pieces, each independently describable.");
2993
3120
  }
2994
- if (text.length > 20) {
2995
- const hasNoImpl = !/\b(implement|code|function|class|import|export|module)\b/i.test(text) || /breadboard|concept|capability|value/i.test(text);
2996
- if (hasNoImpl) {
2997
- satisfied.push("Abstraction level appropriate");
2998
- score += 2;
2999
- } else {
3000
- missing.push("Elements should be at breadboard level \u2014 capabilities, not implementation details.");
3001
- }
3002
- }
3003
3121
  const totalElements = Math.max(ctx.elementCount, textDescribed);
3004
- if (totalElements >= 2) {
3122
+ if (totalElements >= 3) {
3123
+ satisfied.push("Multiple independently describable pieces");
3124
+ score += 3;
3125
+ } else if (totalElements >= 2) {
3005
3126
  satisfied.push("Multiple independently describable pieces");
3006
3127
  score += 2;
3007
3128
  }
@@ -3099,9 +3220,8 @@ function scoreBoundaries(ctx) {
3099
3220
  const missing = [];
3100
3221
  const satisfied = [];
3101
3222
  let score = 0;
3102
- const noGoSignals = countMatches(text, NOGO_SIGNALS);
3103
- const wontStatements = (text.match(/\bwon'?t\b|\bwill not\b/gi) ?? []).length;
3104
- const totalNoGos = Math.max(ctx.noGoCount, noGoSignals, wontStatements);
3223
+ const textNoGos = Math.max(countMatches(text, NOGO_SIGNALS), (text.match(/\bwon'?t\b|\bwill not\b/gi) ?? []).length);
3224
+ const totalNoGos = Math.min(ctx.noGoCount + textNoGos, 10);
3105
3225
  if (totalNoGos >= 3) {
3106
3226
  satisfied.push(`${totalNoGos} explicit no-gos declared`);
3107
3227
  score += 5;
@@ -3181,8 +3301,8 @@ function suggestCaptures(ctx, activeDimension) {
3181
3301
  if (text.length < 30) return suggestions;
3182
3302
  if (activeDimension === "problem_clarity" || activeDimension === "appetite") {
3183
3303
  if (countMatches(text, WORKAROUND_SIGNALS) > 0 && ctx.riskCount === 0) {
3184
- const match = text.match(/(?:workaround|currently|today|right now)[^.]*\./i);
3185
- if (match) {
3304
+ const match = text.match(/(?:workaround|currently|today|right now)[^.]{0,120}\./i);
3305
+ if (match && isBalanced(match[0])) {
3186
3306
  suggestions.push({
3187
3307
  type: "tension",
3188
3308
  name: extractPhrase(match[0], 60),
@@ -3195,7 +3315,7 @@ function suggestCaptures(ctx, activeDimension) {
3195
3315
  if (activeDimension === "elements") {
3196
3316
  if (ctx.elementCount === 0 && /(?:service|component|module|layer|store|engine|kernel)/i.test(text)) {
3197
3317
  const match = text.match(/(?:a |the )?(\w[\w\s]{2,30}(?:service|component|module|layer|store|engine|kernel))/i);
3198
- if (match) {
3318
+ if (match && isBalanced(match[1])) {
3199
3319
  suggestions.push({
3200
3320
  type: "element",
3201
3321
  name: capitalize(match[1].trim()),
@@ -3207,8 +3327,8 @@ function suggestCaptures(ctx, activeDimension) {
3207
3327
  }
3208
3328
  if (activeDimension === "risks") {
3209
3329
  if (ctx.riskCount === 0 && countMatches(text, RISK_SIGNALS) >= 2) {
3210
- const match = text.match(/(?:risk|rabbit hole|unknown|might|could fail)[^.]*\./i);
3211
- if (match) {
3330
+ const match = text.match(/(?:risk|rabbit hole|unknown|might|could fail)[^.]{0,120}\./i);
3331
+ if (match && isBalanced(match[0])) {
3212
3332
  suggestions.push({
3213
3333
  type: "risk",
3214
3334
  name: extractPhrase(match[0], 60),
@@ -3219,8 +3339,8 @@ function suggestCaptures(ctx, activeDimension) {
3219
3339
  }
3220
3340
  }
3221
3341
  if (/(?:we(?:'ll| will) (?:use|go with|pick|choose)|decided to|decision:|instead of)/i.test(text)) {
3222
- const match = text.match(/(?:we(?:'ll| will) (?:use|go with|pick|choose)|decided to|decision:)[^.]*\./i);
3223
- if (match) {
3342
+ const match = text.match(/(?:we(?:'ll| will) (?:use|go with|pick|choose)|decided to|decision:)[^.]{0,120}\./i);
3343
+ if (match && isBalanced(match[0])) {
3224
3344
  suggestions.push({
3225
3345
  type: "decision",
3226
3346
  name: extractPhrase(match[0], 60),
@@ -3414,7 +3534,8 @@ function registerFacilitateTools(server) {
3414
3534
  );
3415
3535
  }
3416
3536
  function buildStudioUrl(workspaceSlug, entryId) {
3417
- return `https://productbrain.app/${workspaceSlug}/studio/entries/${entryId}`;
3537
+ const appUrl = process.env.PRODUCTBRAIN_APP_URL ?? "https://productbrain.io";
3538
+ return `${appUrl}/${workspaceSlug}/studio/entries/${entryId}`;
3418
3539
  }
3419
3540
  function buildDirective(scorecard, phase, betEntryId, captureReady, activeDimension, nextDimension) {
3420
3541
  const b = betEntryId;
@@ -4538,7 +4659,7 @@ function registerVerifyTools(server) {
4538
4659
  }
4539
4660
 
4540
4661
  // src/tools/start.ts
4541
- import { z as z14 } from "zod";
4662
+ import { z as z15 } from "zod";
4542
4663
 
4543
4664
  // src/presets/collections.ts
4544
4665
  var GOVERNANCE_CORE = [
@@ -4675,13 +4796,12 @@ async function queryPlannedWork() {
4675
4796
  if (entry.stratum === "system") continue;
4676
4797
  const collection = entry.collectionSlug ?? entry.collection ?? "unknown";
4677
4798
  if (entry.status === "draft") {
4678
- if (collection === "tensions") {
4679
- result.openTensions.push({ name: entry.name, entryId: entry.entryId ?? entry._id });
4680
- } else {
4681
- result.uncommittedDrafts.push({ name: entry.name, collection });
4682
- }
4799
+ result.uncommittedDrafts.push({ name: entry.name, collection });
4683
4800
  }
4684
- if (entry.status === "in-progress" || entry.status === "in_progress") {
4801
+ if (collection === "tensions" && entry.workflowStatus === "open") {
4802
+ result.openTensions.push({ name: entry.name, entryId: entry.entryId ?? entry._id });
4803
+ }
4804
+ if (entry.workflowStatus === "in-progress") {
4685
4805
  result.inProgressEntries.push({
4686
4806
  name: entry.name,
4687
4807
  collection,
@@ -4878,9 +4998,120 @@ function buildOperatingProtocol(governanceOrStandards, task) {
4878
4998
  return lines;
4879
4999
  }
4880
5000
 
5001
+ // src/tools/start-interview.ts
5002
+ import { z as z14 } from "zod";
5003
+ var interviewExtractionSchema = z14.object({
5004
+ vision: z14.string().min(10).describe("What they're building \u2014 concise product purpose statement"),
5005
+ audience: z14.string().optional().describe("Who it's for \u2014 primary user or customer segment"),
5006
+ techStack: z14.array(z14.string()).optional().describe("Key technologies, frameworks, or platforms (e.g. ['SvelteKit', 'Convex', 'PostgreSQL'])"),
5007
+ keyTerms: z14.array(z14.string()).optional().describe("Domain-specific terms that belong in the glossary"),
5008
+ keyDecisions: z14.array(z14.string()).optional().describe("Recent significant decisions made (each as a concise statement)"),
5009
+ tensions: z14.array(z14.string()).optional().describe("Pain points or friction the product is solving")
5010
+ });
5011
+ function extractionToBatchEntries(extracted) {
5012
+ const entries = [];
5013
+ if (extracted.vision) {
5014
+ entries.push({
5015
+ collection: "strategy",
5016
+ name: "Product Vision",
5017
+ description: extracted.vision
5018
+ });
5019
+ }
5020
+ if (extracted.audience) {
5021
+ const audienceName = extracted.audience.split(/\s+/).slice(0, 6).join(" ");
5022
+ entries.push({
5023
+ collection: "audiences",
5024
+ name: audienceName,
5025
+ description: extracted.audience
5026
+ });
5027
+ }
5028
+ if (extracted.techStack?.length) {
5029
+ entries.push({
5030
+ collection: "architecture",
5031
+ name: "Tech Stack",
5032
+ description: `Technology choices: ${extracted.techStack.join(", ")}`
5033
+ });
5034
+ for (const tech of extracted.techStack.slice(0, 3)) {
5035
+ entries.push({ collection: "glossary", name: tech, description: `${tech} \u2014 part of the tech stack` });
5036
+ }
5037
+ }
5038
+ if (extracted.keyTerms?.length) {
5039
+ for (const term of extracted.keyTerms) {
5040
+ const alreadyAdded = entries.some(
5041
+ (e) => e.collection === "glossary" && e.name.toLowerCase() === term.toLowerCase()
5042
+ );
5043
+ if (!alreadyAdded) {
5044
+ entries.push({ collection: "glossary", name: term, description: `Core domain term: ${term}` });
5045
+ }
5046
+ }
5047
+ }
5048
+ if (extracted.keyDecisions?.length) {
5049
+ for (const decision of extracted.keyDecisions) {
5050
+ entries.push({
5051
+ collection: "decisions",
5052
+ name: decision.slice(0, 80),
5053
+ description: decision
5054
+ });
5055
+ }
5056
+ }
5057
+ if (extracted.tensions?.length) {
5058
+ for (const tension of extracted.tensions) {
5059
+ entries.push({
5060
+ collection: "tensions",
5061
+ name: tension.slice(0, 80),
5062
+ description: tension
5063
+ });
5064
+ }
5065
+ }
5066
+ return entries;
5067
+ }
5068
+ function getInterviewInstructions(workspaceName) {
5069
+ return {
5070
+ systemPrompt: `You are activating the **${workspaceName}** Product Brain. Ask 1\u20132 focused questions, then extract structured knowledge and feed it to batch-capture. Everything lands as a draft \u2014 the user confirms before anything is committed.`,
5071
+ question1: `**Q1 \u2014 What are you building and for whom?** Describe your product in 1\u20132 sentences and who it's for. (This becomes your Product Vision and primary Audience.)`,
5072
+ question2: `**Q2 (optional) \u2014 What's your tech stack or key domain terms?** Name a few core technologies or terms your team uses. (These become Architecture + Glossary entries.)`,
5073
+ extractionGuidance: `After the user answers, extract:
5074
+ - vision: the core product purpose (required)
5075
+ - audience: who it's for (optional)
5076
+ - techStack: technologies mentioned (optional, array)
5077
+ - keyTerms: domain terms (optional, array)
5078
+ - keyDecisions: any decisions stated (optional, array)
5079
+ - tensions: any pain points stated (optional, array)
5080
+
5081
+ Map to batch-capture entries:
5082
+ - vision \u2192 strategy ("Product Vision")
5083
+ - audience \u2192 audiences (1 entry)
5084
+ - techStack \u2192 architecture ("Tech Stack") + top 3 terms \u2192 glossary
5085
+ - keyTerms \u2192 glossary (1 per term)
5086
+ - keyDecisions \u2192 decisions (1 per decision)
5087
+ - tensions \u2192 tensions (1 per tension)`,
5088
+ captureInstructions: `Call batch-capture with the extracted entries. Keep descriptions concise (1\u20132 sentences each). Prefer 8 good entries over 15 mediocre ones \u2014 quality over volume. If batch-capture returns failedEntries, tell the user and retry those individually.`,
5089
+ qualityNote: `After capturing, call graph action=suggest on 2\u20133 key entries to build the graph. Present the drafts grouped by collection. Ask: "Commit all, or review first?" Only call commit-entry when the user confirms.`,
5090
+ scanOffer: `After the interview capture, offer the Project Scan: "I can also scan your project files (README.md, package.json, .cursorrules) to extract more knowledge automatically. Want me to? It takes about 2 minutes and stays as drafts."`
5091
+ };
5092
+ }
5093
+ function buildInterviewResponse(workspaceName) {
5094
+ const instructions = getInterviewInstructions(workspaceName);
5095
+ return [
5096
+ "## Let's activate your workspace",
5097
+ "",
5098
+ instructions.systemPrompt,
5099
+ "",
5100
+ "I'll ask you 1\u20132 questions. Your answers become the first entries in your knowledge graph \u2014 all as drafts until you confirm.",
5101
+ "",
5102
+ `**${instructions.question1}**`,
5103
+ "",
5104
+ "_Take your time \u2014 a sentence or two is enough. I'll extract the structure from your answer._",
5105
+ "",
5106
+ `> ${instructions.scanOffer}`,
5107
+ "",
5108
+ '_Everything stays as a draft until you say "commit" or "looks good"._'
5109
+ ].join("\n");
5110
+ }
5111
+
4881
5112
  // src/tools/start.ts
4882
- var startSchema = z14.object({
4883
- preset: z14.string().optional().describe(
5113
+ var startSchema = z15.object({
5114
+ preset: z15.string().optional().describe(
4884
5115
  "Collection preset ID to seed (e.g. 'software-product', 'content-business', 'agency', 'saas-api', 'general'). Only used for fresh workspaces. If omitted on a fresh workspace, returns the preset menu."
4885
5116
  )
4886
5117
  });
@@ -5002,15 +5233,9 @@ Available presets: ${listPresets().map((p) => `\`${p.id}\``).join(", ")}`;
5002
5233
  }
5003
5234
  lines.push(
5004
5235
  "",
5005
- "## Let's activate your workspace",
5006
- "",
5007
- "I'll help you load knowledge step by step. Everything stays as a draft until you confirm it.",
5008
- "",
5009
- "**First:** Tell me what you're building in one or two sentences. I'll capture it as your product vision.",
5236
+ buildInterviewResponse(wsCtx.workspaceName),
5010
5237
  "",
5011
- `_After that, we'll add a few key terms to your glossary and any important decisions. Each entry is a draft \u2014 say "commit" or "looks good" when you're happy with it, and I'll promote it to the Chain (SSOT)._`,
5012
- "",
5013
- "You can also customize your structure anytime: `collections action=create`, `collections action=update`, or `collections action=list`.",
5238
+ "_You can also customize your structure anytime: `collections action=create`, `collections action=update`, or `collections action=list`._",
5014
5239
  "",
5015
5240
  "---",
5016
5241
  "Orientation complete. Write tools are available."
@@ -5026,14 +5251,7 @@ function pickNextAction(gaps, openTensions, priorSessions) {
5026
5251
  if (gaps.length === 0 && openTensions.length === 0) return null;
5027
5252
  if (gaps.length > 0) {
5028
5253
  const gap = gaps[0];
5029
- const ctaMap = {
5030
- "strategy-vision": "Tell me what you're building \u2014 your vision, mission, and north star \u2014 and I'll capture it.",
5031
- "architecture-layers": "Describe your architecture in a few sentences and I'll capture it.",
5032
- "glossary-foundation": "What are the key terms your team uses? Tell me a few and I'll add them to the glossary.",
5033
- "decisions-documented": "What's a recent significant decision your team made? I'll document it with the rationale.",
5034
- "tensions-tracked": "What's a friction point or pain point you're dealing with? I'll capture it as a tension."
5035
- };
5036
- const cta = ctaMap[gap.id] ?? `Tell me about your ${gap.label.toLowerCase()} and I'll capture it.`;
5254
+ const cta = gap.capabilityGuidance ?? gap.guidance ?? `Tell me about your ${gap.label.toLowerCase()} and I'll capture it.`;
5037
5255
  return { action: gap.label, cta };
5038
5256
  }
5039
5257
  if (openTensions.length > 0) {
@@ -5059,7 +5277,7 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors) {
5059
5277
  let openTensions = [];
5060
5278
  try {
5061
5279
  const tensions = await mcpQuery("chain.listEntries", { collectionSlug: "tensions" });
5062
- openTensions = (tensions ?? []).filter((e) => e.status === "draft");
5280
+ openTensions = (tensions ?? []).filter((e) => e.workflowStatus === "open");
5063
5281
  } catch {
5064
5282
  }
5065
5283
  let readiness = null;
@@ -5069,17 +5287,19 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors) {
5069
5287
  errors.push(`Readiness: ${e.message}`);
5070
5288
  }
5071
5289
  const lines = [];
5072
- const isLowReadiness = readiness && readiness.score < 50;
5073
- const isHighReadiness = readiness && readiness.score >= 50;
5290
+ const isLowReadiness = readiness !== null && readiness.score < 50;
5291
+ const isHighReadiness = readiness !== null && readiness.score >= 50;
5292
+ const stage = readiness?.stage ?? null;
5074
5293
  lines.push(`# ${wsCtx.workspaceName}`);
5075
5294
  lines.push(`_Workspace \`${wsCtx.workspaceSlug}\` \u2014 Product Brain is healthy._`);
5076
5295
  lines.push("");
5077
5296
  if (isLowReadiness && isNeglected) {
5078
- lines.push(`Your workspace has been around for ${ageDays} days but is only ${readiness.score}% ready.`);
5297
+ const stageNote = stage ? ` and is still at the **${stage}** stage` : "";
5298
+ lines.push(`Your workspace has been around for ${ageDays} days${stageNote}.`);
5079
5299
  lines.push("Let's close the gaps \u2014 or if the current structure doesn't fit, we can reshape it.");
5080
5300
  lines.push("");
5081
5301
  } else if (isLowReadiness) {
5082
- lines.push(`Readiness: ${readiness.score}%. Let's get your workspace active.`);
5302
+ if (stage) lines.push(`**Brain stage: ${stage}.** Let's get your workspace active.`);
5083
5303
  lines.push("");
5084
5304
  }
5085
5305
  if (isLowReadiness) {
@@ -5103,7 +5323,7 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors) {
5103
5323
  lines.push("");
5104
5324
  } else if (isHighReadiness) {
5105
5325
  if (readiness) {
5106
- lines.push(`Readiness: ${readiness.score}% (${readiness.passedChecks}/${readiness.totalChecks}).`);
5326
+ lines.push(`**Brain stage: ${stage}.**`);
5107
5327
  }
5108
5328
  let wsPrinciples = [];
5109
5329
  let wsStandards = [];
@@ -5118,7 +5338,7 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors) {
5118
5338
  const govEntries = [];
5119
5339
  for (const slug of govSlugs) {
5120
5340
  const entries = await mcpQuery("chain.listEntries", { collectionSlug: slug });
5121
- const active = (entries ?? []).filter((e) => e.status === "active" || e.status === "verified");
5341
+ const active = (entries ?? []).filter((e) => e.status === "active");
5122
5342
  if (active.length > 0) {
5123
5343
  govEntries.push({ slug, entries: active });
5124
5344
  if (slug === "principles") wsPrinciples = active;
@@ -5196,6 +5416,34 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors) {
5196
5416
  lines.push(...formatRecoveryBlock(recoveryBlock));
5197
5417
  }
5198
5418
  }
5419
+ try {
5420
+ const allEntries = await mcpQuery("chain.listEntries", {});
5421
+ const committed = (allEntries ?? []).filter(
5422
+ (e) => e.status === "active"
5423
+ );
5424
+ const committedCollections = new Set(committed.map((e) => e.collectionId ?? e.collection));
5425
+ if (committed.length >= 10 && committedCollections.size >= 3) {
5426
+ lines.push("");
5427
+ lines.push("## Your workspace is activated");
5428
+ lines.push(
5429
+ `**${committed.length} committed entries** across **${committedCollections.size} collections** \u2014 your knowledge graph is alive.`
5430
+ );
5431
+ lines.push("");
5432
+ lines.push(
5433
+ `**Try it:** Ask me anything about your product \u2014 architecture, decisions, tensions \u2014 and I'll pull context from the graph.`
5434
+ );
5435
+ lines.push("");
5436
+ if (wsCtx.workspaceSlug) {
5437
+ lines.push(`**View in Studio:** \`/w/${wsCtx.workspaceSlug}/studio\``);
5438
+ lines.push("");
5439
+ }
5440
+ if (committed.length >= 10 && committedCollections.size >= 3) {
5441
+ lines.push(`_Tip: Connect entries with \`graph action=suggest\` to unlock deeper context._`);
5442
+ lines.push("");
5443
+ }
5444
+ }
5445
+ } catch {
5446
+ }
5199
5447
  lines.push("What would you like to work on?");
5200
5448
  lines.push("");
5201
5449
  }
@@ -5224,9 +5472,9 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors) {
5224
5472
  }
5225
5473
 
5226
5474
  // src/tools/usage.ts
5227
- import { z as z15 } from "zod";
5228
- var usageSummarySchema = z15.object({
5229
- periodDays: z15.number().min(1).max(90).optional().describe("Number of days to look back (default 30, max 90)")
5475
+ import { z as z16 } from "zod";
5476
+ var usageSummarySchema = z16.object({
5477
+ periodDays: z16.number().min(1).max(90).optional().describe("Number of days to look back (default 30, max 90)")
5230
5478
  });
5231
5479
  function registerUsageTools(server) {
5232
5480
  server.registerTool(
@@ -5289,43 +5537,43 @@ function registerUsageTools(server) {
5289
5537
  }
5290
5538
 
5291
5539
  // src/tools/gitchain.ts
5292
- import { z as z16 } from "zod";
5293
- var chainSchema = z16.object({
5294
- action: z16.enum(["create", "get", "list", "edit"]).describe("Action: create a process, get process details, list all processes, or edit a process link"),
5295
- chainEntryId: z16.string().optional().describe("Chain entry ID (required for get/edit)"),
5296
- title: z16.string().optional().describe("Process title (required for create)"),
5297
- chainTypeId: z16.string().optional().default("strategy-coherence").describe("Process template slug for create: 'strategy-coherence', 'idm-proposal', or any custom template slug"),
5298
- description: z16.string().optional().describe("Description (for create)"),
5299
- linkId: z16.string().optional().describe("Link to edit (for edit action): problem, insight, choice, action, outcome"),
5300
- content: z16.string().optional().describe("New content for the link (for edit action)"),
5301
- status: z16.string().optional().describe("Filter by status for list: 'draft' or 'active'"),
5302
- author: z16.string().optional().describe("Who is performing the action. Defaults to 'mcp'.")
5540
+ import { z as z17 } from "zod";
5541
+ var chainSchema = z17.object({
5542
+ action: z17.enum(["create", "get", "list", "edit"]).describe("Action: create a process, get process details, list all processes, or edit a process link"),
5543
+ chainEntryId: z17.string().optional().describe("Chain entry ID (required for get/edit)"),
5544
+ title: z17.string().optional().describe("Process title (required for create)"),
5545
+ chainTypeId: z17.string().optional().default("strategy-coherence").describe("Process template slug for create: 'strategy-coherence', 'idm-proposal', or any custom template slug"),
5546
+ description: z17.string().optional().describe("Description (for create)"),
5547
+ linkId: z17.string().optional().describe("Link to edit (for edit action): problem, insight, choice, action, outcome"),
5548
+ content: z17.string().optional().describe("New content for the link (for edit action)"),
5549
+ status: z17.string().optional().describe("Filter by status for list: 'draft' or 'active'"),
5550
+ author: z17.string().optional().describe("Who is performing the action. Defaults to 'mcp'.")
5303
5551
  });
5304
- var chainVersionSchema = z16.object({
5305
- action: z16.enum(["commit", "list", "diff", "revert", "history"]).describe("Action: commit a snapshot, list commits, diff two versions, revert to a version, or view history"),
5306
- chainEntryId: z16.string().describe("The chain's entry ID"),
5307
- commitMessage: z16.string().optional().describe("Commit message (required for commit). Convention: type(link): description"),
5308
- versionA: z16.number().optional().describe("Earlier version for diff"),
5309
- versionB: z16.number().optional().describe("Later version for diff"),
5310
- toVersion: z16.number().optional().describe("Version number to revert to"),
5311
- author: z16.string().optional().describe("Who is performing the action. Defaults to 'mcp'.")
5552
+ var chainVersionSchema = z17.object({
5553
+ action: z17.enum(["commit", "list", "diff", "revert", "history"]).describe("Action: commit a snapshot, list commits, diff two versions, revert to a version, or view history"),
5554
+ chainEntryId: z17.string().describe("The chain's entry ID"),
5555
+ commitMessage: z17.string().optional().describe("Commit message (required for commit). Convention: type(link): description"),
5556
+ versionA: z17.number().optional().describe("Earlier version for diff"),
5557
+ versionB: z17.number().optional().describe("Later version for diff"),
5558
+ toVersion: z17.number().optional().describe("Version number to revert to"),
5559
+ author: z17.string().optional().describe("Who is performing the action. Defaults to 'mcp'.")
5312
5560
  });
5313
- var chainBranchSchema = z16.object({
5314
- action: z16.enum(["create", "list", "merge", "conflicts"]).describe("Action: create a branch, list branches, merge a branch, or check for conflicts"),
5315
- chainEntryId: z16.string().describe("The chain's entry ID"),
5316
- branchName: z16.string().optional().describe("Branch name (required for merge/conflicts, optional for create)"),
5317
- strategy: z16.enum(["merge_commit", "squash"]).optional().describe("Merge strategy: 'merge_commit' (default) or 'squash'"),
5318
- author: z16.string().optional().describe("Who is performing the action. Defaults to 'mcp'.")
5561
+ var chainBranchSchema = z17.object({
5562
+ action: z17.enum(["create", "list", "merge", "conflicts"]).describe("Action: create a branch, list branches, merge a branch, or check for conflicts"),
5563
+ chainEntryId: z17.string().describe("The chain's entry ID"),
5564
+ branchName: z17.string().optional().describe("Branch name (required for merge/conflicts, optional for create)"),
5565
+ strategy: z17.enum(["merge_commit", "squash"]).optional().describe("Merge strategy: 'merge_commit' (default) or 'squash'"),
5566
+ author: z17.string().optional().describe("Who is performing the action. Defaults to 'mcp'.")
5319
5567
  });
5320
- var chainReviewSchema = z16.object({
5321
- action: z16.enum(["gate", "comment", "resolve-comment", "list-comments"]).describe("Action: run coherence gate, add a comment, resolve a comment, or list comments"),
5322
- chainEntryId: z16.string().describe("The chain's entry ID"),
5323
- commitMessage: z16.string().optional().describe("Commit message to lint (for gate action)"),
5324
- versionNumber: z16.number().optional().describe("Version to comment on or list comments for"),
5325
- linkId: z16.string().optional().describe("Link this comment targets (optional for comment)"),
5326
- body: z16.string().optional().describe("Comment text (required for comment action)"),
5327
- commentId: z16.string().optional().describe("Comment ID (required for resolve-comment)"),
5328
- author: z16.string().optional().describe("Who is performing the action. Defaults to 'mcp'.")
5568
+ var chainReviewSchema = z17.object({
5569
+ action: z17.enum(["gate", "comment", "resolve-comment", "list-comments"]).describe("Action: run coherence gate, add a comment, resolve a comment, or list comments"),
5570
+ chainEntryId: z17.string().describe("The chain's entry ID"),
5571
+ commitMessage: z17.string().optional().describe("Commit message to lint (for gate action)"),
5572
+ versionNumber: z17.number().optional().describe("Version to comment on or list comments for"),
5573
+ linkId: z17.string().optional().describe("Link this comment targets (optional for comment)"),
5574
+ body: z17.string().optional().describe("Comment text (required for comment action)"),
5575
+ commentId: z17.string().optional().describe("Comment ID (required for resolve-comment)"),
5576
+ author: z17.string().optional().describe("Who is performing the action. Defaults to 'mcp'.")
5329
5577
  });
5330
5578
  function linkSummary(links) {
5331
5579
  return Object.entries(links).map(([id, content]) => {
@@ -5708,38 +5956,38 @@ ${formatted}`
5708
5956
  }
5709
5957
 
5710
5958
  // src/tools/maps.ts
5711
- import { z as z17 } from "zod";
5712
- var createAudienceMapSetSchema = z17.object({
5713
- audienceEntryId: z17.string().describe("Entry ID of the audience (e.g. STR-fb7hje)")
5959
+ import { z as z18 } from "zod";
5960
+ var createAudienceMapSetSchema = z18.object({
5961
+ audienceEntryId: z18.string().describe("Entry ID of the audience (e.g. STR-fb7hje)")
5714
5962
  });
5715
- var mapSchema = z17.object({
5716
- action: z17.enum(["create", "get", "list"]).describe("Action: create a map, get map details, or list all maps"),
5717
- mapEntryId: z17.string().optional().describe("Map entry ID (for get)"),
5718
- title: z17.string().optional().describe("Map title (for create)"),
5719
- templateId: z17.string().optional().default("lean-canvas").describe("Template slug for create: 'lean-canvas' or any composed template"),
5720
- description: z17.string().optional().describe("Description (for create)"),
5721
- slotIds: z17.array(z17.string()).optional().describe("Slot IDs to initialize (for create; auto-populated from template if omitted)"),
5722
- status: z17.string().optional().describe("Filter by status for list")
5963
+ var mapSchema = z18.object({
5964
+ action: z18.enum(["create", "get", "list"]).describe("Action: create a map, get map details, or list all maps"),
5965
+ mapEntryId: z18.string().optional().describe("Map entry ID (for get)"),
5966
+ title: z18.string().optional().describe("Map title (for create)"),
5967
+ templateId: z18.string().optional().default("lean-canvas").describe("Template slug for create: 'lean-canvas' or any composed template"),
5968
+ description: z18.string().optional().describe("Description (for create)"),
5969
+ slotIds: z18.array(z18.string()).optional().describe("Slot IDs to initialize (for create; auto-populated from template if omitted)"),
5970
+ status: z18.string().optional().describe("Filter by status for list")
5723
5971
  });
5724
- var mapSlotSchema = z17.object({
5725
- action: z17.enum(["add", "remove", "replace", "list"]).describe("Action: add/remove/replace an ingredient in a slot, or list slot contents"),
5726
- mapEntryId: z17.string().describe("Map entry ID"),
5727
- slotId: z17.string().optional().describe("Slot ID (e.g. 'problem', 'customer-segments')"),
5728
- ingredientEntryId: z17.string().optional().describe("Ingredient entry ID to add/remove"),
5729
- newIngredientEntryId: z17.string().optional().describe("New ingredient entry ID (for replace)"),
5730
- label: z17.string().optional().describe("Display label override"),
5731
- author: z17.string().optional().describe("Who is performing the action")
5972
+ var mapSlotSchema = z18.object({
5973
+ action: z18.enum(["add", "remove", "replace", "list"]).describe("Action: add/remove/replace an ingredient in a slot, or list slot contents"),
5974
+ mapEntryId: z18.string().describe("Map entry ID"),
5975
+ slotId: z18.string().optional().describe("Slot ID (e.g. 'problem', 'customer-segments')"),
5976
+ ingredientEntryId: z18.string().optional().describe("Ingredient entry ID to add/remove"),
5977
+ newIngredientEntryId: z18.string().optional().describe("New ingredient entry ID (for replace)"),
5978
+ label: z18.string().optional().describe("Display label override"),
5979
+ author: z18.string().optional().describe("Who is performing the action")
5732
5980
  });
5733
- var mapVersionSchema = z17.object({
5734
- action: z17.enum(["commit", "list", "history"]).describe("Action: commit the map, list commits, or view commit history"),
5735
- mapEntryId: z17.string().describe("Map entry ID"),
5736
- commitMessage: z17.string().optional().describe("Commit message (for commit action)"),
5737
- author: z17.string().optional().describe("Who is committing")
5981
+ var mapVersionSchema = z18.object({
5982
+ action: z18.enum(["commit", "list", "history"]).describe("Action: commit the map, list commits, or view commit history"),
5983
+ mapEntryId: z18.string().describe("Map entry ID"),
5984
+ commitMessage: z18.string().optional().describe("Commit message (for commit action)"),
5985
+ author: z18.string().optional().describe("Who is committing")
5738
5986
  });
5739
- var mapSuggestSchema = z17.object({
5740
- mapEntryId: z17.string().describe("Map entry ID to suggest ingredients for"),
5741
- slotId: z17.string().optional().describe("Specific slot to find ingredients for (or all empty slots)"),
5742
- query: z17.string().optional().describe("Optional search query to narrow ingredient suggestions")
5987
+ var mapSuggestSchema = z18.object({
5988
+ mapEntryId: z18.string().describe("Map entry ID to suggest ingredients for"),
5989
+ slotId: z18.string().optional().describe("Specific slot to find ingredients for (or all empty slots)"),
5990
+ query: z18.string().optional().describe("Optional search query to narrow ingredient suggestions")
5743
5991
  });
5744
5992
  function slotSummary(slots) {
5745
5993
  return Object.entries(slots).map(([id, refs]) => {
@@ -6165,7 +6413,7 @@ Use \`map-slot action=add mapEntryId="${mapEntryId}" slotId="..." ingredientEntr
6165
6413
  // src/tools/architecture.ts
6166
6414
  import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
6167
6415
  import { resolve as resolve2, relative, dirname, normalize } from "path";
6168
- import { z as z18 } from "zod";
6416
+ import { z as z19 } from "zod";
6169
6417
  var COLLECTION_SLUG = "architecture";
6170
6418
  var COLLECTION_FIELDS = [
6171
6419
  { key: "archType", label: "Architecture Type", type: "select", required: true, options: ["template", "layer", "node", "flow"], searchable: true },
@@ -6322,14 +6570,14 @@ var SEED_FLOWS = [
6322
6570
  { entryId: "ARCH-flow-knowledge-trust", name: "Knowledge Trust Flow", order: 3, data: { archType: "flow", sourceNode: "ARCH-node-mcp", targetNode: "ARCH-node-glossary", description: "MCP verify tool checks entries against codebase \u2192 file existence, schema references validated \u2192 trust scores updated", color: "#22c55e" } },
6323
6571
  { entryId: "ARCH-flow-analytics", name: "Analytics Flow", order: 4, data: { archType: "flow", sourceNode: "ARCH-node-command-center", targetNode: "ARCH-node-posthog", description: "Feature views and actions tracked \u2192 workspace-scoped events \u2192 PostHog group analytics \u2192 Command Center metrics", color: "#ec4899" } }
6324
6572
  ];
6325
- var architectureSchema = z18.object({
6326
- action: z18.enum(["show", "explore", "flow"]).describe("Action: show full map, explore a layer, or visualize a flow"),
6327
- template: z18.string().optional().describe("Template entry ID to filter by (for show)"),
6328
- layer: z18.string().optional().describe("Layer name or entry ID (for explore), e.g. 'Core' or 'ARCH-layer-core'"),
6329
- flow: z18.string().optional().describe("Flow name or entry ID (for flow), e.g. 'Smart Capture Flow'")
6573
+ var architectureSchema = z19.object({
6574
+ action: z19.enum(["show", "explore", "flow"]).describe("Action: show full map, explore a layer, or visualize a flow"),
6575
+ template: z19.string().optional().describe("Template entry ID to filter by (for show)"),
6576
+ layer: z19.string().optional().describe("Layer name or entry ID (for explore), e.g. 'Core' or 'ARCH-layer-core'"),
6577
+ flow: z19.string().optional().describe("Flow name or entry ID (for flow), e.g. 'Smart Capture Flow'")
6330
6578
  });
6331
- var architectureAdminSchema = z18.object({
6332
- action: z18.enum(["seed", "check"]).describe("Action: seed default architecture data, or check dependency health")
6579
+ var architectureAdminSchema = z19.object({
6580
+ action: z19.enum(["seed", "check"]).describe("Action: seed default architecture data, or check dependency health")
6333
6581
  });
6334
6582
  function registerArchitectureTools(server) {
6335
6583
  server.registerTool(
@@ -6925,15 +7173,32 @@ async function handleWhoami() {
6925
7173
  }
6926
7174
  };
6927
7175
  }
7176
+ var STAGE_LABELS = {
7177
+ blank: "Blank",
7178
+ seeded: "Seeded",
7179
+ grounded: "Grounded",
7180
+ connected: "Connected"
7181
+ };
7182
+ var STAGE_DESCRIPTIONS = {
7183
+ blank: "No knowledge captured yet.",
7184
+ seeded: "Early knowledge is in place \u2014 keep building.",
7185
+ grounded: "Solid foundations \u2014 a few gaps remain.",
7186
+ connected: "Well-connected knowledge graph \u2014 your Brain is useful."
7187
+ };
6928
7188
  async function handleWorkspaceStatus() {
6929
7189
  const result = await mcpQuery("chain.workspaceReadiness");
6930
7190
  const { score, totalChecks, passedChecks, checks, gaps, stats, governanceMode } = result;
7191
+ const scoringVersion = result.scoringVersion ?? "v1";
7192
+ const stage = result.stage ?? "seeded";
7193
+ const stageLabel = STAGE_LABELS[stage] ?? stage;
7194
+ const stageDescription = STAGE_DESCRIPTIONS[stage] ?? "";
6931
7195
  const scoreBar = "\u2588".repeat(Math.round(score / 10)) + "\u2591".repeat(10 - Math.round(score / 10));
6932
7196
  const lines = [
6933
- `# Workspace Readiness: ${score}%`,
6934
- `${scoreBar} ${passedChecks}/${totalChecks} requirements met`,
6935
- `
6936
- **Governance:** ${governanceMode ?? "open"}${(governanceMode ?? "open") !== "open" ? " (commits require proposal)" : ""}`,
7197
+ `# Brain Status: ${stageLabel}`,
7198
+ `_${stageDescription}_`,
7199
+ "",
7200
+ `${scoreBar} ${stageLabel} \xB7 ${score}%`,
7201
+ `**Governance:** ${governanceMode ?? "open"}${(governanceMode ?? "open") !== "open" ? " (commits require proposal)" : ""}`,
6937
7202
  "",
6938
7203
  "## Stats",
6939
7204
  `- **Entries:** ${stats.totalEntries} (${stats.activeCount} active, ${stats.draftCount} draft)`,
@@ -6943,28 +7208,35 @@ async function handleWorkspaceStatus() {
6943
7208
  ""
6944
7209
  ];
6945
7210
  if (gaps.length > 0) {
6946
- lines.push("## Gaps (action required)");
7211
+ lines.push("## Gaps");
6947
7212
  for (const gap of gaps) {
6948
- lines.push(`- [ ] **${gap.label}** \u2014 ${gap.description}`);
6949
- lines.push(` ${gap.current}/${gap.required} | _${gap.guidance}_`);
7213
+ const action = gap.capabilityGuidance ?? gap.guidance;
7214
+ lines.push(`- [ ] **${gap.label}**`);
7215
+ lines.push(` _${action}_`);
6950
7216
  }
6951
7217
  lines.push("");
6952
7218
  }
6953
7219
  const passed = checks.filter((c) => c.passed);
6954
7220
  if (passed.length > 0) {
6955
- lines.push("## Passed");
7221
+ lines.push("## Passing checks");
6956
7222
  for (const check of passed) {
6957
- lines.push(`- [x] **${check.label}** (${check.current}/${check.required})`);
7223
+ lines.push(`- [x] ${check.label} (${check.current}/${check.required})`);
6958
7224
  }
6959
7225
  }
6960
7226
  return {
6961
7227
  content: [{ type: "text", text: lines.join("\n") }],
6962
7228
  structuredContent: {
7229
+ stage,
7230
+ scoringVersion,
6963
7231
  readinessScore: score,
6964
7232
  activeEntries: stats.activeCount,
6965
7233
  totalRelations: stats.totalRelations,
6966
7234
  orphanedEntries: stats.orphanedCount,
6967
- gaps: gaps.map((g) => ({ label: g.label, guidance: g.guidance }))
7235
+ gaps: gaps.map((g) => ({
7236
+ id: g.id,
7237
+ label: g.label,
7238
+ guidance: g.capabilityGuidance ?? g.guidance
7239
+ }))
6968
7240
  }
6969
7241
  };
6970
7242
  }
@@ -7004,45 +7276,48 @@ ${logLines.join("\n")}` }],
7004
7276
  };
7005
7277
  }
7006
7278
  var HEALTH_ACTIONS = ["check", "whoami", "status", "audit", "self-test"];
7007
- var healthSchema = z19.object({
7008
- action: z19.enum(HEALTH_ACTIONS).describe(
7279
+ var healthSchema = z20.object({
7280
+ action: z20.enum(HEALTH_ACTIONS).describe(
7009
7281
  "'check': connectivity and workspace stats. 'whoami': session identity. 'status': workspace readiness. 'audit': session audit log. 'self-test': validate all tool schemas."
7010
7282
  ),
7011
- limit: z19.number().min(1).max(50).default(20).optional().describe("For audit: how many recent calls to show (max 50)")
7283
+ limit: z20.number().min(1).max(50).default(20).optional().describe("For audit: how many recent calls to show (max 50)")
7012
7284
  });
7013
- var orientSchema = z19.object({
7014
- mode: z19.enum(["full", "brief"]).optional().default("full").describe("full = full context (default). brief = compact summary for mid-session re-orientation."),
7015
- task: z19.string().optional().describe("Natural-language task description for task-scoped context. When provided, orient returns scored, relevant entries for the task.")
7285
+ var orientSchema = z20.object({
7286
+ mode: z20.enum(["full", "brief"]).optional().default("full").describe("full = full context (default). brief = compact summary for mid-session re-orientation."),
7287
+ task: z20.string().optional().describe("Natural-language task description for task-scoped context. When provided, orient returns scored, relevant entries for the task.")
7016
7288
  });
7017
- var healthCheckOutputSchema = z19.object({
7018
- healthy: z19.boolean(),
7019
- collections: z19.number(),
7020
- entries: z19.number(),
7021
- latencyMs: z19.number(),
7022
- workspace: z19.string()
7289
+ var healthCheckOutputSchema = z20.object({
7290
+ healthy: z20.boolean(),
7291
+ collections: z20.number(),
7292
+ entries: z20.number(),
7293
+ latencyMs: z20.number(),
7294
+ workspace: z20.string()
7023
7295
  });
7024
- var healthStatusOutputSchema = z19.object({
7025
- readinessScore: z19.number(),
7026
- activeEntries: z19.number(),
7027
- totalRelations: z19.number(),
7028
- orphanedEntries: z19.number(),
7029
- gaps: z19.array(z19.object({ label: z19.string(), guidance: z19.string() }))
7296
+ var healthStatusOutputSchema = z20.object({
7297
+ // Optional with defaults — older backends that don't return these fields won't fail.
7298
+ stage: z20.enum(["blank", "seeded", "grounded", "connected"]).optional().default("seeded"),
7299
+ scoringVersion: z20.enum(["v1", "v2"]).optional().default("v1"),
7300
+ readinessScore: z20.number(),
7301
+ activeEntries: z20.number(),
7302
+ totalRelations: z20.number(),
7303
+ orphanedEntries: z20.number(),
7304
+ gaps: z20.array(z20.object({ id: z20.string(), label: z20.string(), guidance: z20.string() }))
7030
7305
  });
7031
- var healthAuditOutputSchema = z19.object({
7032
- totalCalls: z19.number(),
7033
- calls: z19.array(z19.object({
7034
- tool: z19.string(),
7035
- action: z19.string().optional(),
7036
- timestamp: z19.string(),
7037
- durationMs: z19.number().optional()
7306
+ var healthAuditOutputSchema = z20.object({
7307
+ totalCalls: z20.number(),
7308
+ calls: z20.array(z20.object({
7309
+ tool: z20.string(),
7310
+ action: z20.string().optional(),
7311
+ timestamp: z20.string(),
7312
+ durationMs: z20.number().optional()
7038
7313
  }))
7039
7314
  });
7040
- var healthWhoamiOutputSchema = z19.object({
7041
- workspaceId: z19.string(),
7042
- workspaceName: z19.string(),
7043
- scope: z19.string(),
7044
- sessionId: z19.union([z19.string(), z19.null()]),
7045
- oriented: z19.boolean()
7315
+ var healthWhoamiOutputSchema = z20.object({
7316
+ workspaceId: z20.string(),
7317
+ workspaceName: z20.string(),
7318
+ scope: z20.string(),
7319
+ sessionId: z20.union([z20.string(), z20.null()]),
7320
+ oriented: z20.boolean()
7046
7321
  });
7047
7322
  var ALL_TOOL_SCHEMAS = [
7048
7323
  { name: "entries", schema: entriesSchema },
@@ -7078,14 +7353,14 @@ var ALL_TOOL_SCHEMAS = [
7078
7353
  { name: "architecture-admin", schema: architectureAdminSchema },
7079
7354
  { name: "facilitate", schema: facilitateSchema }
7080
7355
  ];
7081
- var selfTestOutputSchema = z19.object({
7082
- passed: z19.number(),
7083
- failed: z19.number(),
7084
- total: z19.number(),
7085
- results: z19.array(z19.object({
7086
- tool: z19.string(),
7087
- valid: z19.boolean(),
7088
- error: z19.string().optional()
7356
+ var selfTestOutputSchema = z20.object({
7357
+ passed: z20.number(),
7358
+ failed: z20.number(),
7359
+ total: z20.number(),
7360
+ results: z20.array(z20.object({
7361
+ tool: z20.string(),
7362
+ valid: z20.boolean(),
7363
+ error: z20.string().optional()
7089
7364
  }))
7090
7365
  });
7091
7366
  function handleSelfTest() {
@@ -7203,7 +7478,7 @@ function registerHealthTools(server) {
7203
7478
  let openTensions = [];
7204
7479
  try {
7205
7480
  const tensions = await mcpQuery("chain.listEntries", { collectionSlug: "tensions" });
7206
- openTensions = (tensions ?? []).filter((e) => e.status === "draft");
7481
+ openTensions = (tensions ?? []).filter((e) => e.workflowStatus === "open");
7207
7482
  } catch {
7208
7483
  }
7209
7484
  let readiness = null;
@@ -7223,7 +7498,8 @@ function registerHealthTools(server) {
7223
7498
  }
7224
7499
  lines.push("");
7225
7500
  if (mode === "brief") {
7226
- lines.push(`Readiness: ${readiness?.score ?? "?"}%`);
7501
+ const briefStage = readiness?.stage ?? (readiness?.score != null ? readiness.score < 50 ? "seeded" : "grounded" : "unknown");
7502
+ lines.push(`Brain stage: ${briefStage}`);
7227
7503
  if (orientEntries?.strategicContext) {
7228
7504
  const sc = orientEntries.strategicContext;
7229
7505
  if (sc.vision) lines.push(`Vision: ${sc.vision}`);
@@ -7283,30 +7559,24 @@ function registerHealthTools(server) {
7283
7559
  }
7284
7560
  return { content: [{ type: "text", text: lines.join("\n") }] };
7285
7561
  }
7562
+ const orientStage = readiness?.stage ?? "seeded";
7286
7563
  if (isLowReadiness && wsCtx?.createdAt) {
7287
7564
  const ageDays = Math.floor((Date.now() - wsCtx.createdAt) / (1e3 * 60 * 60 * 24));
7288
7565
  if (ageDays >= 30) {
7289
- lines.push(`Your workspace has been around for ${ageDays} days but is only ${readiness.score}% ready.`);
7566
+ lines.push(`Your workspace has been around for ${ageDays} days and is still at the **${orientStage}** stage.`);
7290
7567
  lines.push("Let's close the gaps \u2014 or if the current structure doesn't fit, we can reshape it.");
7291
7568
  lines.push("");
7292
7569
  }
7293
7570
  }
7294
7571
  if (isLowReadiness) {
7295
- lines.push(`Readiness: ${readiness.score}% (${readiness.passedChecks}/${readiness.totalChecks}).`);
7572
+ lines.push(`**Brain stage: ${orientStage}.**`);
7296
7573
  lines.push("");
7297
7574
  const gaps = readiness.gaps ?? [];
7298
7575
  if (gaps.length > 0) {
7299
7576
  const gap = gaps[0];
7300
- const ctaMap = {
7301
- "strategy-vision": "Tell me what you're building \u2014 your vision, mission, and north star \u2014 and I'll capture it.",
7302
- "architecture-layers": "Describe your architecture in a few sentences and I'll capture it.",
7303
- "glossary-foundation": "What are the key terms your team uses? Tell me a few and I'll add them to the glossary.",
7304
- "decisions-documented": "What's a recent significant decision your team made? I'll document it with the rationale.",
7305
- "tensions-tracked": "What's a friction point or pain point you're dealing with? I'll capture it as a tension."
7306
- };
7307
- const cta = ctaMap[gap.id] ?? `Tell me about your ${gap.label.toLowerCase()} and I'll capture it.`;
7577
+ const cta = gap.capabilityGuidance ?? gap.guidance ?? `Tell me about your ${gap.label.toLowerCase()} and I'll capture it.`;
7308
7578
  lines.push("## Recommended next step");
7309
- lines.push(`**${gap.label}** (${gap.current}/${gap.required})`);
7579
+ lines.push(`**${gap.label}**`);
7310
7580
  lines.push("");
7311
7581
  lines.push(cta);
7312
7582
  lines.push("");
@@ -7322,7 +7592,7 @@ function registerHealthTools(server) {
7322
7592
  lines.push("_Use `collections action=create` to add it, or ask me to propose collections for your domain._");
7323
7593
  lines.push("");
7324
7594
  } else if (readiness) {
7325
- lines.push(`Readiness: ${readiness.score}% (${readiness.passedChecks}/${readiness.totalChecks}).`);
7595
+ lines.push(`**Brain stage: ${orientStage}.**`);
7326
7596
  lines.push("");
7327
7597
  if (orientEntries?.strategicContext) {
7328
7598
  const sc = orientEntries.strategicContext;
@@ -7929,12 +8199,12 @@ ${entry.labels.map((l) => `- ${l.name ?? l.slug}`).join("\n")}`);
7929
8199
  }
7930
8200
 
7931
8201
  // src/prompts/index.ts
7932
- import { z as z20 } from "zod";
8202
+ import { z as z21 } from "zod";
7933
8203
  function registerPrompts(server) {
7934
8204
  server.prompt(
7935
8205
  "review-against-rules",
7936
8206
  "Review code or a design decision against all business rules for a given domain. Fetches the rules and asks you to do a structured compliance review.",
7937
- { domain: z20.string().describe("Business rule domain (e.g. 'Identity & Access', 'Governance & Decision-Making')") },
8207
+ { domain: z21.string().describe("Business rule domain (e.g. 'Identity & Access', 'Governance & Decision-Making')") },
7938
8208
  async ({ domain }) => {
7939
8209
  const entries = await mcpQuery("chain.listEntries", { collectionSlug: "business-rules" });
7940
8210
  const rules = entries.filter((e) => e.data?.domain === domain);
@@ -7987,7 +8257,7 @@ Provide a structured review with a compliance status for each rule (COMPLIANT /
7987
8257
  server.prompt(
7988
8258
  "name-check",
7989
8259
  "Check variable names, field names, or API names against the glossary for terminology alignment. Flags drift from canonical terms.",
7990
- { names: z20.string().describe("Comma-separated list of names to check (e.g. 'vendor_id, compliance_level, formulator_type')") },
8260
+ { names: z21.string().describe("Comma-separated list of names to check (e.g. 'vendor_id, compliance_level, formulator_type')") },
7991
8261
  async ({ names }) => {
7992
8262
  const terms = await mcpQuery("chain.listEntries", { collectionSlug: "glossary" });
7993
8263
  const glossaryContext = terms.map(
@@ -8023,7 +8293,7 @@ Format as a table: Name | Status | Canonical Form | Action Needed`
8023
8293
  server.prompt(
8024
8294
  "draft-decision-record",
8025
8295
  "Draft a structured decision record from a description of what was decided. Includes context from recent decisions and relevant rules.",
8026
- { context: z20.string().describe("Description of the decision (e.g. 'We decided to use MRSL v3.1 as the conformance baseline because...')") },
8296
+ { context: z21.string().describe("Description of the decision (e.g. 'We decided to use MRSL v3.1 as the conformance baseline because...')") },
8027
8297
  async ({ context }) => {
8028
8298
  const recentDecisions = await mcpQuery("chain.listEntries", { collectionSlug: "decisions" });
8029
8299
  const sorted = [...recentDecisions].sort((a, b) => (b.data?.date ?? "") > (a.data?.date ?? "") ? 1 : -1).slice(0, 5);
@@ -8061,8 +8331,8 @@ After drafting, I can log it using the capture tool with collection "decisions".
8061
8331
  "draft-rule-from-context",
8062
8332
  "Draft a new business rule from an observation or discovery made while coding. Fetches existing rules for the domain to ensure consistency.",
8063
8333
  {
8064
- observation: z20.string().describe("What you observed or discovered (e.g. 'Suppliers can have multiple org types in Gateway')"),
8065
- domain: z20.string().describe("Which domain this rule belongs to (e.g. 'Governance & Decision-Making')")
8334
+ observation: z21.string().describe("What you observed or discovered (e.g. 'Suppliers can have multiple org types in Gateway')"),
8335
+ domain: z21.string().describe("Which domain this rule belongs to (e.g. 'Governance & Decision-Making')")
8066
8336
  },
8067
8337
  async ({ observation, domain }) => {
8068
8338
  const allRules = await mcpQuery("chain.listEntries", { collectionSlug: "business-rules" });
@@ -8103,10 +8373,10 @@ Make sure the rule is consistent with existing rules and doesn't contradict them
8103
8373
  "run-workflow",
8104
8374
  "Launch a Chainwork workflow (retro, shape, IDM) in Facilitator Mode. Returns the full workflow definition, facilitation instructions, and round structure. The agent enters Facilitator Mode and guides the participant through each round.",
8105
8375
  {
8106
- workflow: z20.string().describe(
8376
+ workflow: z21.string().describe(
8107
8377
  "Workflow ID to run. Available: " + listWorkflows().map((w) => `'${w.id}' (${w.name})`).join(", ")
8108
8378
  ),
8109
- context: z20.string().optional().describe(
8379
+ context: z21.string().optional().describe(
8110
8380
  "Optional context from the participant (e.g., 'retro on last sprint', 'shape the Chainwork API bet')"
8111
8381
  )
8112
8382
  },
@@ -8210,7 +8480,7 @@ Description field: ${wf.kbOutputTemplate.descriptionField}
8210
8480
  "shape-a-bet",
8211
8481
  "Launch a coached shaping session powered by the facilitate tool. Dynamically loads workspace context, governance constraints, and coaching rubrics. Use when the user wants to shape a bet, define a pitch, or scope work.",
8212
8482
  {
8213
- idea: z20.string().describe("Brief description of the idea or feature to shape (e.g. 'Improve the glossary editing flow')")
8483
+ idea: z21.string().describe("Brief description of the idea or feature to shape (e.g. 'Improve the glossary editing flow')")
8214
8484
  },
8215
8485
  async ({ idea }) => {
8216
8486
  let strategicContext = "";
@@ -8248,7 +8518,7 @@ Description field: ${wf.kbOutputTemplate.descriptionField}
8248
8518
  "chain.listEntries",
8249
8519
  { collectionSlug: "tensions" }
8250
8520
  );
8251
- const open = (tensions ?? []).filter((t) => t.status === "draft" || t.status === "active");
8521
+ const open = (tensions ?? []).filter((t) => t.workflowStatus === "open");
8252
8522
  if (open.length > 0) {
8253
8523
  tensionsContext = open.slice(0, 10).map((t) => `- \`${t.entryId ?? "?"}\` ${t.name}`).join("\n");
8254
8524
  }
@@ -8362,9 +8632,9 @@ Walk away mid-session and everything captured so far exists as drafts. \`facilit
8362
8632
  "capture-and-connect",
8363
8633
  "Guided workflow: capture a knowledge entry, discover graph connections, create relations, and prepare for commit. Encodes the capture \u2192 suggest \u2192 batch-create \u2192 commit choreography as a step-by-step guide.",
8364
8634
  {
8365
- collection: z20.string().describe("Collection slug (e.g. 'glossary', 'business-rules', 'decisions', 'tensions')"),
8366
- name: z20.string().describe("Entry name"),
8367
- description: z20.string().describe("Entry description")
8635
+ collection: z21.string().describe("Collection slug (e.g. 'glossary', 'business-rules', 'decisions', 'tensions')"),
8636
+ name: z21.string().describe("Entry name"),
8637
+ description: z21.string().describe("Entry description")
8368
8638
  },
8369
8639
  async ({ collection, name, description }) => {
8370
8640
  const collections = await mcpQuery("chain.listCollections");
@@ -8418,7 +8688,7 @@ Only call \`commit-entry\` when the user explicitly confirms.
8418
8688
  "deep-dive",
8419
8689
  "Explore everything the Chain knows about a topic or entry. Assembles entry details, graph context, related business rules, and glossary terms into a comprehensive briefing.",
8420
8690
  {
8421
- topic: z20.string().describe("Entry ID (e.g. 'BR-001') or topic to explore (e.g. 'authentication')")
8691
+ topic: z21.string().describe("Entry ID (e.g. 'BR-001') or topic to explore (e.g. 'authentication')")
8422
8692
  },
8423
8693
  async ({ topic }) => {
8424
8694
  const isEntryId = /^[A-Z]+-\d+$/i.test(topic) || /^[A-Z]+-[a-z0-9]+$/i.test(topic);
@@ -8501,7 +8771,7 @@ ${contextInstructions}`
8501
8771
  "pre-commit-check",
8502
8772
  "Run a readiness check before committing an entry to the Chain. Validates quality score, required relations, and business rule compliance.",
8503
8773
  {
8504
- entryId: z20.string().describe("Entry ID to check (e.g. 'GT-019', 'DEC-005')")
8774
+ entryId: z21.string().describe("Entry ID to check (e.g. 'GT-019', 'DEC-005')")
8505
8775
  },
8506
8776
  async ({ entryId }) => {
8507
8777
  return {
@@ -8555,6 +8825,123 @@ If ready, ask the user to confirm. If not, suggest specific improvements.
8555
8825
  };
8556
8826
  }
8557
8827
  );
8828
+ server.prompt(
8829
+ "project-scan",
8830
+ "Scan local project files to extract structured knowledge for Product Brain. The IDE agent reads README.md, package.json, .cursorrules, folder structure, and recent git history, then extracts vision, tech stack, key terms, and decisions into batch-capture entries. All entries land as drafts \u2014 the user reviews and commits.",
8831
+ {
8832
+ workspaceContext: z21.string().optional().describe("Optional context about the workspace preset and existing collections (paste from start/orient output)")
8833
+ },
8834
+ async ({ workspaceContext }) => {
8835
+ let collectionsContext = "";
8836
+ try {
8837
+ const cols = await mcpQuery("chain.listCollections");
8838
+ if (cols?.length) {
8839
+ const colList = cols.map((c) => `- \`${c.slug}\`: ${c.name}`).join("\n");
8840
+ collectionsContext = `
8841
+ ## Available Collections
8842
+ ${colList}
8843
+ `;
8844
+ }
8845
+ } catch {
8846
+ }
8847
+ const schemaFields = Object.entries(interviewExtractionSchema.shape).map(([key, field]) => `- \`${key}\`: ${field.description ?? ""}`).join("\n");
8848
+ const exampleExtraction = {
8849
+ vision: "A task management app for solo developers who work with AI coding assistants",
8850
+ audience: "Solo developers using Cursor, Claude Code, or similar AI IDEs",
8851
+ techStack: ["SvelteKit", "Convex", "TypeScript"],
8852
+ keyTerms: ["Chain", "Capture", "Orientation"],
8853
+ keyDecisions: ["Use Convex for real-time backend to avoid managing infrastructure"],
8854
+ tensions: ["Activation requires skill \u2014 users need to know what to capture"]
8855
+ };
8856
+ const exampleEntries = extractionToBatchEntries(exampleExtraction);
8857
+ const exampleOutput = JSON.stringify({ entries: exampleEntries.slice(0, 4) }, null, 2);
8858
+ return {
8859
+ messages: [{
8860
+ role: "user",
8861
+ content: {
8862
+ type: "text",
8863
+ text: `# Project Scan \u2014 Agent-Side Knowledge Extraction
8864
+
8865
+ You are scanning the user's local project to extract structured knowledge for Product Brain.
8866
+ PB runs remotely \u2014 you do the file reading and extraction, then call \`batch-capture\`.
8867
+
8868
+ ` + (workspaceContext ? `## Workspace Context
8869
+ ${workspaceContext}
8870
+
8871
+ ` : "") + collectionsContext + `## Step 1: Read Project Files
8872
+
8873
+ Read these files in this order (skip gracefully if not present):
8874
+ 1. \`README.md\` or \`readme.md\` \u2014 product description, purpose, setup
8875
+ 2. \`package.json\` \u2014 name, description, dependencies (framework, database, auth)
8876
+ 3. \`tsconfig.json\` or \`tsconfig.app.json\` \u2014 compilation target, paths
8877
+ 4. Top-level folder structure (list directories only, max 2 levels deep)
8878
+ 5. \`.cursorrules\` or \`CLAUDE.md\` or \`AGENTS.md\` \u2014 project conventions, constraints
8879
+ 6. \`git log --oneline -20\` \u2014 recent commit messages for decisions/tensions
8880
+
8881
+ **ICP stack only (V1):** README, package.json, tsconfig, folder structure, .cursorrules/CLAUDE.md, git log.
8882
+ Do not attempt to read Python, Rust, Go, or mobile-specific files in this pass.
8883
+
8884
+ ## Step 2: Extract Structured Data
8885
+
8886
+ After reading, extract the following schema:
8887
+
8888
+ \`\`\`
8889
+ ${schemaFields}
8890
+ \`\`\`
8891
+
8892
+ **Quality rules (RH2 mitigation):**
8893
+ - Prefer 8 good entries over 20 mediocre ones
8894
+ - Only include terms you have direct evidence for from the files
8895
+ - Skip fields you cannot confidently fill \u2014 empty arrays > hallucinated data
8896
+ - Keep descriptions concise: 1\u20132 sentences max
8897
+
8898
+ ## Step 3: Validate Before Capture
8899
+
8900
+ Before calling batch-capture, check your extraction:
8901
+ - vision is present and at least 10 characters
8902
+ - each entry has a non-empty name and description
8903
+ - no duplicate names within the same collection
8904
+ - if techStack is present, at least one glossary entry per technology
8905
+
8906
+ If validation fails for any entry, drop it rather than sending malformed data.
8907
+
8908
+ ## Step 4: Map to batch-capture
8909
+
8910
+ Map your extracted data to entries using these rules:
8911
+ - \`vision\` \u2192 strategy collection, name: "Product Vision"
8912
+ - \`audience\` \u2192 audiences collection
8913
+ - \`techStack\` \u2192 architecture collection ("Tech Stack") + top 3 items \u2192 glossary
8914
+ - \`keyTerms\` \u2192 glossary collection (1 entry per term)
8915
+ - \`keyDecisions\` \u2192 decisions collection (1 entry per decision)
8916
+ - \`tensions\` \u2192 tensions collection (1 entry per tension)
8917
+
8918
+ **Example output:**
8919
+ \`\`\`json
8920
+ ${exampleOutput}
8921
+ \`\`\`
8922
+
8923
+ ## Step 5: Call batch-capture
8924
+
8925
+ \`\`\`
8926
+ batch-capture entries=[...your mapped entries...]
8927
+ \`\`\`
8928
+
8929
+ If \`failed > 0\` in the response, inspect \`failedEntries\` and retry those individually.
8930
+
8931
+ ## Step 6: Connect + Review
8932
+
8933
+ After capture:
8934
+ 1. Call \`graph action=suggest\` on 2\u20133 key entries (vision, architecture, a decision)
8935
+ 2. Present entries grouped by collection
8936
+ 3. Ask: "Commit all, or would you like to review first?"
8937
+ 4. Only call \`commit-entry\` when the user confirms
8938
+
8939
+ **Begin with Step 1 now.** Read the files, then report what you found before extracting.`
8940
+ }
8941
+ }]
8942
+ };
8943
+ }
8944
+ );
8558
8945
  }
8559
8946
 
8560
8947
  // src/server.ts
@@ -8669,4 +9056,4 @@ export {
8669
9056
  SERVER_VERSION,
8670
9057
  createProductBrainServer
8671
9058
  };
8672
- //# sourceMappingURL=chunk-7CD66MUS.js.map
9059
+ //# sourceMappingURL=chunk-XCKGFYDP.js.map