@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.
- package/dist/{chunk-Z2FGGJHI.js → chunk-7VJP2IMS.js} +23 -8
- package/dist/chunk-7VJP2IMS.js.map +1 -0
- package/dist/{chunk-7CD66MUS.js → chunk-XCKGFYDP.js} +613 -226
- package/dist/chunk-XCKGFYDP.js.map +1 -0
- package/dist/http.js +23 -5
- package/dist/http.js.map +1 -1
- package/dist/index.js +41 -18
- package/dist/index.js.map +1 -1
- package/dist/{smart-capture-BIKEZC6P.js → smart-capture-E53YEHHO.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-7CD66MUS.js.map +0 -1
- package/dist/chunk-Z2FGGJHI.js.map +0 -1
- /package/dist/{smart-capture-BIKEZC6P.js.map → smart-capture-E53YEHHO.js.map} +0 -0
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
startAgentSession,
|
|
26
26
|
trackWriteTool,
|
|
27
27
|
translateStaleToolNames
|
|
28
|
-
} from "./chunk-
|
|
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.
|
|
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:
|
|
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-
|
|
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 |
|
|
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
|
-
|
|
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} \`${
|
|
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} \`${
|
|
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
|
|
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 >=
|
|
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
|
|
3103
|
-
const
|
|
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)[^.]
|
|
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)[^.]
|
|
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:)[^.]
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
4883
|
-
preset:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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"
|
|
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
|
|
5228
|
-
var usageSummarySchema =
|
|
5229
|
-
periodDays:
|
|
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
|
|
5293
|
-
var chainSchema =
|
|
5294
|
-
action:
|
|
5295
|
-
chainEntryId:
|
|
5296
|
-
title:
|
|
5297
|
-
chainTypeId:
|
|
5298
|
-
description:
|
|
5299
|
-
linkId:
|
|
5300
|
-
content:
|
|
5301
|
-
status:
|
|
5302
|
-
author:
|
|
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 =
|
|
5305
|
-
action:
|
|
5306
|
-
chainEntryId:
|
|
5307
|
-
commitMessage:
|
|
5308
|
-
versionA:
|
|
5309
|
-
versionB:
|
|
5310
|
-
toVersion:
|
|
5311
|
-
author:
|
|
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 =
|
|
5314
|
-
action:
|
|
5315
|
-
chainEntryId:
|
|
5316
|
-
branchName:
|
|
5317
|
-
strategy:
|
|
5318
|
-
author:
|
|
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 =
|
|
5321
|
-
action:
|
|
5322
|
-
chainEntryId:
|
|
5323
|
-
commitMessage:
|
|
5324
|
-
versionNumber:
|
|
5325
|
-
linkId:
|
|
5326
|
-
body:
|
|
5327
|
-
commentId:
|
|
5328
|
-
author:
|
|
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
|
|
5712
|
-
var createAudienceMapSetSchema =
|
|
5713
|
-
audienceEntryId:
|
|
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 =
|
|
5716
|
-
action:
|
|
5717
|
-
mapEntryId:
|
|
5718
|
-
title:
|
|
5719
|
-
templateId:
|
|
5720
|
-
description:
|
|
5721
|
-
slotIds:
|
|
5722
|
-
status:
|
|
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 =
|
|
5725
|
-
action:
|
|
5726
|
-
mapEntryId:
|
|
5727
|
-
slotId:
|
|
5728
|
-
ingredientEntryId:
|
|
5729
|
-
newIngredientEntryId:
|
|
5730
|
-
label:
|
|
5731
|
-
author:
|
|
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 =
|
|
5734
|
-
action:
|
|
5735
|
-
mapEntryId:
|
|
5736
|
-
commitMessage:
|
|
5737
|
-
author:
|
|
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 =
|
|
5740
|
-
mapEntryId:
|
|
5741
|
-
slotId:
|
|
5742
|
-
query:
|
|
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
|
|
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 =
|
|
6326
|
-
action:
|
|
6327
|
-
template:
|
|
6328
|
-
layer:
|
|
6329
|
-
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 =
|
|
6332
|
-
action:
|
|
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
|
-
`#
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
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
|
|
7211
|
+
lines.push("## Gaps");
|
|
6947
7212
|
for (const gap of gaps) {
|
|
6948
|
-
|
|
6949
|
-
lines.push(
|
|
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("##
|
|
7221
|
+
lines.push("## Passing checks");
|
|
6956
7222
|
for (const check of passed) {
|
|
6957
|
-
lines.push(`- [x]
|
|
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) => ({
|
|
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 =
|
|
7008
|
-
action:
|
|
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:
|
|
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 =
|
|
7014
|
-
mode:
|
|
7015
|
-
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 =
|
|
7018
|
-
healthy:
|
|
7019
|
-
collections:
|
|
7020
|
-
entries:
|
|
7021
|
-
latencyMs:
|
|
7022
|
-
workspace:
|
|
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 =
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
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 =
|
|
7032
|
-
totalCalls:
|
|
7033
|
-
calls:
|
|
7034
|
-
tool:
|
|
7035
|
-
action:
|
|
7036
|
-
timestamp:
|
|
7037
|
-
durationMs:
|
|
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 =
|
|
7041
|
-
workspaceId:
|
|
7042
|
-
workspaceName:
|
|
7043
|
-
scope:
|
|
7044
|
-
sessionId:
|
|
7045
|
-
oriented:
|
|
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 =
|
|
7082
|
-
passed:
|
|
7083
|
-
failed:
|
|
7084
|
-
total:
|
|
7085
|
-
results:
|
|
7086
|
-
tool:
|
|
7087
|
-
valid:
|
|
7088
|
-
error:
|
|
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.
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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}
|
|
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(
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
8065
|
-
domain:
|
|
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:
|
|
8376
|
+
workflow: z21.string().describe(
|
|
8107
8377
|
"Workflow ID to run. Available: " + listWorkflows().map((w) => `'${w.id}' (${w.name})`).join(", ")
|
|
8108
8378
|
),
|
|
8109
|
-
context:
|
|
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:
|
|
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.
|
|
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:
|
|
8366
|
-
name:
|
|
8367
|
-
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:
|
|
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:
|
|
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-
|
|
9059
|
+
//# sourceMappingURL=chunk-XCKGFYDP.js.map
|