@productbrain/mcp 0.0.1-beta.6 → 0.0.1-beta.7
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-YLVX2CON.js → chunk-6ZSCQINU.js} +5 -6
- package/dist/{chunk-YLVX2CON.js.map → chunk-6ZSCQINU.js.map} +1 -1
- package/dist/index.js +292 -166
- package/dist/index.js.map +1 -1
- package/dist/{smart-capture-QGIC5N47.js → smart-capture-SEINMTTR.js} +2 -2
- package/package.json +1 -1
- /package/dist/{smart-capture-QGIC5N47.js.map → smart-capture-SEINMTTR.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
bootstrap,
|
|
4
4
|
closeAgentSession,
|
|
5
5
|
getAgentSessionId,
|
|
6
|
-
getApiKeyScope,
|
|
7
6
|
getAuditLog,
|
|
8
7
|
getWorkspaceContext,
|
|
9
8
|
getWorkspaceId,
|
|
@@ -21,7 +20,7 @@ import {
|
|
|
21
20
|
shutdownAnalytics,
|
|
22
21
|
startAgentSession,
|
|
23
22
|
trackSessionStarted
|
|
24
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-6ZSCQINU.js";
|
|
25
24
|
|
|
26
25
|
// src/index.ts
|
|
27
26
|
import { readFileSync as readFileSync3 } from "fs";
|
|
@@ -717,6 +716,111 @@ Use \`suggest-links\` to discover potential connections, or \`relate-entries\` t
|
|
|
717
716
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
718
717
|
}
|
|
719
718
|
);
|
|
719
|
+
server2.registerTool(
|
|
720
|
+
"create-collection",
|
|
721
|
+
{
|
|
722
|
+
title: "Create Collection",
|
|
723
|
+
description: "Create a new knowledge collection in the workspace. Collections define the structure for entries (like Notion databases or Capacity boards). Provide a slug, name, and field schema.\n\nUse this when setting up a workspace or when the user wants to track a new type of knowledge. Use `list-collections` first to see what already exists.",
|
|
724
|
+
inputSchema: {
|
|
725
|
+
slug: z.string().describe("URL-safe identifier, e.g. 'glossary', 'tech-debt', 'api-endpoints'"),
|
|
726
|
+
name: z.string().describe("Display name, e.g. 'Glossary', 'Tech Debt', 'API Endpoints'"),
|
|
727
|
+
description: z.string().optional().describe("What this collection is for"),
|
|
728
|
+
icon: z.string().optional().describe("Emoji icon for the collection"),
|
|
729
|
+
fields: z.array(z.object({
|
|
730
|
+
key: z.string().describe("Field key, e.g. 'description', 'severity', 'status'"),
|
|
731
|
+
label: z.string().describe("Display label, e.g. 'Description', 'Severity'"),
|
|
732
|
+
type: z.string().describe("Field type: 'string', 'select', 'array', 'number', 'boolean'"),
|
|
733
|
+
required: z.boolean().optional().describe("Whether this field is required"),
|
|
734
|
+
options: z.array(z.string()).optional().describe("Options for 'select' type fields"),
|
|
735
|
+
searchable: z.boolean().optional().describe("Whether this field is included in full-text search")
|
|
736
|
+
})).describe("Field definitions for the collection schema")
|
|
737
|
+
},
|
|
738
|
+
annotations: { destructiveHint: false }
|
|
739
|
+
},
|
|
740
|
+
async ({ slug, name, description, icon, fields }) => {
|
|
741
|
+
requireWriteAccess();
|
|
742
|
+
try {
|
|
743
|
+
await mcpMutation("chain.createCollection", {
|
|
744
|
+
slug,
|
|
745
|
+
name,
|
|
746
|
+
description,
|
|
747
|
+
icon,
|
|
748
|
+
fields,
|
|
749
|
+
isDefault: false,
|
|
750
|
+
createdBy: getAgentSessionId() ? `agent:${getAgentSessionId()}` : "mcp"
|
|
751
|
+
});
|
|
752
|
+
const fieldList = fields.map((f) => ` - \`${f.key}\` (${f.type}${f.required ? ", required" : ""}${f.searchable ? ", searchable" : ""})`).join("\n");
|
|
753
|
+
return {
|
|
754
|
+
content: [{
|
|
755
|
+
type: "text",
|
|
756
|
+
text: `# Collection Created: ${name}
|
|
757
|
+
|
|
758
|
+
**Slug:** \`${slug}\`
|
|
759
|
+
` + (description ? `**Description:** ${description}
|
|
760
|
+
` : "") + `
|
|
761
|
+
**Fields:**
|
|
762
|
+
${fieldList}
|
|
763
|
+
|
|
764
|
+
You can now capture entries: \`capture collection="${slug}" name="..." description="..."\``
|
|
765
|
+
}]
|
|
766
|
+
};
|
|
767
|
+
} catch (error) {
|
|
768
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
769
|
+
if (msg.includes("already exists")) {
|
|
770
|
+
return {
|
|
771
|
+
content: [{
|
|
772
|
+
type: "text",
|
|
773
|
+
text: `Collection \`${slug}\` already exists. Use \`update-collection\` to modify it, or choose a different slug.`
|
|
774
|
+
}]
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
throw error;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
);
|
|
781
|
+
server2.registerTool(
|
|
782
|
+
"update-collection",
|
|
783
|
+
{
|
|
784
|
+
title: "Update Collection",
|
|
785
|
+
description: "Update an existing collection's name, description, icon, or field schema. Only provide the fields you want to change. Use `list-collections` to see current state.",
|
|
786
|
+
inputSchema: {
|
|
787
|
+
slug: z.string().describe("Collection slug to update, e.g. 'glossary', 'tech-debt'"),
|
|
788
|
+
name: z.string().optional().describe("New display name"),
|
|
789
|
+
description: z.string().optional().describe("New description"),
|
|
790
|
+
icon: z.string().optional().describe("New emoji icon"),
|
|
791
|
+
fields: z.array(z.object({
|
|
792
|
+
key: z.string(),
|
|
793
|
+
label: z.string(),
|
|
794
|
+
type: z.string(),
|
|
795
|
+
required: z.boolean().optional(),
|
|
796
|
+
options: z.array(z.string()).optional(),
|
|
797
|
+
searchable: z.boolean().optional()
|
|
798
|
+
})).optional().describe("Replacement field schema (replaces all fields \u2014 include existing fields you want to keep)")
|
|
799
|
+
},
|
|
800
|
+
annotations: { destructiveHint: false }
|
|
801
|
+
},
|
|
802
|
+
async ({ slug, name, description, icon, fields }) => {
|
|
803
|
+
requireWriteAccess();
|
|
804
|
+
await mcpMutation("chain.updateCollection", {
|
|
805
|
+
slug,
|
|
806
|
+
...name !== void 0 && { name },
|
|
807
|
+
...description !== void 0 && { description },
|
|
808
|
+
...icon !== void 0 && { icon },
|
|
809
|
+
...fields !== void 0 && { fields }
|
|
810
|
+
});
|
|
811
|
+
const changes = [name && "name", description && "description", icon && "icon", fields && "fields"].filter(Boolean).join(", ");
|
|
812
|
+
return {
|
|
813
|
+
content: [{
|
|
814
|
+
type: "text",
|
|
815
|
+
text: `# Collection Updated: \`${slug}\`
|
|
816
|
+
|
|
817
|
+
Changed: ${changes || "no changes"}.
|
|
818
|
+
|
|
819
|
+
Use \`list-collections\` to verify the result.`
|
|
820
|
+
}]
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
);
|
|
720
824
|
server2.registerTool(
|
|
721
825
|
"commit-entry",
|
|
722
826
|
{
|
|
@@ -729,7 +833,7 @@ Use \`suggest-links\` to discover potential connections, or \`relate-entries\` t
|
|
|
729
833
|
},
|
|
730
834
|
async ({ entryId }) => {
|
|
731
835
|
requireWriteAccess();
|
|
732
|
-
const { runContradictionCheck } = await import("./smart-capture-
|
|
836
|
+
const { runContradictionCheck } = await import("./smart-capture-SEINMTTR.js");
|
|
733
837
|
const entry = await mcpQuery("chain.getEntry", { entryId });
|
|
734
838
|
if (!entry) {
|
|
735
839
|
return {
|
|
@@ -876,6 +980,8 @@ var CALL_CATEGORIES = {
|
|
|
876
980
|
"chain.createLabel": "label",
|
|
877
981
|
"chain.updateLabel": "label",
|
|
878
982
|
"chain.deleteLabel": "label",
|
|
983
|
+
"chain.createCollection": "write",
|
|
984
|
+
"chain.updateCollection": "write",
|
|
879
985
|
"chain.listCollections": "meta",
|
|
880
986
|
"chain.getCollection": "meta",
|
|
881
987
|
"chain.listLabels": "meta",
|
|
@@ -1054,7 +1160,7 @@ function registerHealthTools(server2) {
|
|
|
1054
1160
|
"orient",
|
|
1055
1161
|
{
|
|
1056
1162
|
title: "Orient \u2014 Start Here",
|
|
1057
|
-
description: "The single entry point for starting a session. Returns
|
|
1163
|
+
description: "The single entry point for starting a session. Returns workspace context with a single recommended next action for low-readiness workspaces, or a standup-style briefing for established workspaces.\n\nUse this FIRST. One call to orient replaces 3\u20135 individual tool calls.\n\nCompleting orientation unlocks write tools for the active session.",
|
|
1058
1164
|
annotations: { readOnlyHint: true }
|
|
1059
1165
|
},
|
|
1060
1166
|
async () => {
|
|
@@ -1067,15 +1173,13 @@ function registerHealthTools(server2) {
|
|
|
1067
1173
|
errors.push(`Workspace: ${e.message}`);
|
|
1068
1174
|
}
|
|
1069
1175
|
let priorSessions = [];
|
|
1070
|
-
let maxSessions = 3;
|
|
1071
1176
|
if (wsCtx) {
|
|
1072
1177
|
try {
|
|
1073
|
-
priorSessions = await mcpQuery("agent.recentSessions", { limit:
|
|
1178
|
+
priorSessions = await mcpQuery("agent.recentSessions", { limit: 3 });
|
|
1074
1179
|
} catch {
|
|
1075
1180
|
}
|
|
1076
1181
|
}
|
|
1077
1182
|
let constraintEntries = [];
|
|
1078
|
-
let maxConstraints = 8;
|
|
1079
1183
|
try {
|
|
1080
1184
|
const [archEntries, ruleEntries, decisionEntries] = await Promise.all([
|
|
1081
1185
|
mcpQuery("chain.listEntries", { collectionSlug: "architecture" }),
|
|
@@ -1087,7 +1191,7 @@ function registerHealthTools(server2) {
|
|
|
1087
1191
|
...(ruleEntries ?? []).filter((e) => e.status === "Active" || e.status === "active"),
|
|
1088
1192
|
...(decisionEntries ?? []).filter((e) => e.status === "Decided" || e.status === "active")
|
|
1089
1193
|
];
|
|
1090
|
-
constraintEntries = committed.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)).slice(0,
|
|
1194
|
+
constraintEntries = committed.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)).slice(0, 8);
|
|
1091
1195
|
} catch {
|
|
1092
1196
|
}
|
|
1093
1197
|
let openTensions = [];
|
|
@@ -1102,109 +1206,100 @@ function registerHealthTools(server2) {
|
|
|
1102
1206
|
} catch (e) {
|
|
1103
1207
|
errors.push(`Readiness: ${e.message}`);
|
|
1104
1208
|
}
|
|
1105
|
-
const TOKEN_LIMIT = 6e3;
|
|
1106
|
-
const CHAR_PER_TOKEN = 4;
|
|
1107
|
-
const CHAR_LIMIT = TOKEN_LIMIT * CHAR_PER_TOKEN;
|
|
1108
1209
|
const lines = [];
|
|
1109
|
-
|
|
1110
|
-
function addLine(line) {
|
|
1111
|
-
lines.push(line);
|
|
1112
|
-
charCount += line.length + 1;
|
|
1113
|
-
}
|
|
1114
|
-
function addLines(ls) {
|
|
1115
|
-
for (const l of ls) addLine(l);
|
|
1116
|
-
}
|
|
1210
|
+
const isLowReadiness = readiness && readiness.score < 50;
|
|
1117
1211
|
if (wsCtx) {
|
|
1118
|
-
|
|
1119
|
-
|
|
1212
|
+
lines.push(`# ${wsCtx.workspaceName}`);
|
|
1213
|
+
lines.push(`_Workspace \`${wsCtx.workspaceSlug}\` \u2014 Product Brain is healthy._`);
|
|
1120
1214
|
} else {
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
}
|
|
1124
|
-
|
|
1125
|
-
if (
|
|
1126
|
-
const
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
addLine("### Gaps");
|
|
1132
|
-
for (const gap of readiness.gaps) {
|
|
1133
|
-
addLine(`- [ ] **${gap.label}** (${gap.current}/${gap.required}) \u2014 _${gap.guidance}_`);
|
|
1134
|
-
}
|
|
1135
|
-
addLine("");
|
|
1215
|
+
lines.push("# Workspace");
|
|
1216
|
+
lines.push("_Could not resolve workspace._");
|
|
1217
|
+
}
|
|
1218
|
+
lines.push("");
|
|
1219
|
+
if (isLowReadiness && wsCtx?.createdAt) {
|
|
1220
|
+
const ageDays = Math.floor((Date.now() - wsCtx.createdAt) / (1e3 * 60 * 60 * 24));
|
|
1221
|
+
if (ageDays >= 30) {
|
|
1222
|
+
lines.push(`Your workspace has been around for ${ageDays} days but is only ${readiness.score}% ready.`);
|
|
1223
|
+
lines.push("Let's close the gaps \u2014 or if the current structure doesn't fit, we can reshape it.");
|
|
1224
|
+
lines.push("");
|
|
1136
1225
|
}
|
|
1137
1226
|
}
|
|
1138
|
-
if (
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1227
|
+
if (isLowReadiness) {
|
|
1228
|
+
lines.push(`Readiness: ${readiness.score}% (${readiness.passedChecks}/${readiness.totalChecks}).`);
|
|
1229
|
+
lines.push("");
|
|
1230
|
+
const gaps = readiness.gaps ?? [];
|
|
1231
|
+
if (gaps.length > 0) {
|
|
1232
|
+
const gap = gaps[0];
|
|
1233
|
+
const ctaMap = {
|
|
1234
|
+
"strategy-vision": "Tell me what you're building \u2014 your vision, mission, and north star \u2014 and I'll capture it.",
|
|
1235
|
+
"architecture-layers": "Describe your architecture in a few sentences and I'll capture it.",
|
|
1236
|
+
"glossary-foundation": "What are the key terms your team uses? Tell me a few and I'll add them to the glossary.",
|
|
1237
|
+
"decisions-documented": "What's a recent significant decision your team made? I'll document it with the rationale.",
|
|
1238
|
+
"tensions-tracked": "What's a friction point or pain point you're dealing with? I'll capture it as a tension."
|
|
1239
|
+
};
|
|
1240
|
+
const cta = ctaMap[gap.id] ?? `Tell me about your ${gap.label.toLowerCase()} and I'll capture it.`;
|
|
1241
|
+
lines.push("## Recommended next step");
|
|
1242
|
+
lines.push(`**${gap.label}** (${gap.current}/${gap.required})`);
|
|
1243
|
+
lines.push("");
|
|
1244
|
+
lines.push(cta);
|
|
1245
|
+
lines.push("");
|
|
1246
|
+
lines.push('_Everything stays as a draft until you confirm. Say "commit" or "looks good" to promote to the Chain._');
|
|
1247
|
+
lines.push("");
|
|
1248
|
+
const remainingGaps = gaps.length - 1;
|
|
1249
|
+
if (remainingGaps > 0 || openTensions.length > 0) {
|
|
1250
|
+
lines.push(`_${remainingGaps > 0 ? `${remainingGaps} more gap${remainingGaps === 1 ? "" : "s"}` : ""}${remainingGaps > 0 && openTensions.length > 0 ? " and " : ""}${openTensions.length > 0 ? `${openTensions.length} open tension${openTensions.length === 1 ? "" : "s"}` : ""} \u2014 ask "show full status" for details._`);
|
|
1251
|
+
lines.push("");
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
} else if (readiness) {
|
|
1255
|
+
lines.push(`Readiness: ${readiness.score}% (${readiness.passedChecks}/${readiness.totalChecks}).`);
|
|
1256
|
+
lines.push("");
|
|
1257
|
+
const briefingItems = [];
|
|
1258
|
+
if (openTensions.length > 0) {
|
|
1259
|
+
const topTension = openTensions[0];
|
|
1260
|
+
briefingItems.push(`**${openTensions.length} open tension${openTensions.length === 1 ? "" : "s"}** \u2014 top: ${topTension.name}`);
|
|
1261
|
+
}
|
|
1262
|
+
if (priorSessions.length > 0) {
|
|
1263
|
+
const last = priorSessions[0];
|
|
1264
|
+
const date = new Date(last.startedAt).toISOString().split("T")[0];
|
|
1265
|
+
const created = Array.isArray(last.entriesCreated) ? last.entriesCreated.length : last.entriesCreated ?? 0;
|
|
1266
|
+
const modified = Array.isArray(last.entriesModified) ? last.entriesModified.length : last.entriesModified ?? 0;
|
|
1267
|
+
briefingItems.push(`**Last session** (${date}): ${created} created, ${modified} modified`);
|
|
1144
1268
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
if (charCount > CHAR_LIMIT * 0.6) {
|
|
1148
|
-
maxSessions = 2;
|
|
1149
|
-
maxConstraints = 5;
|
|
1150
|
-
priorSessions = priorSessions.slice(0, maxSessions);
|
|
1151
|
-
constraintEntries = constraintEntries.slice(0, maxConstraints);
|
|
1152
|
-
}
|
|
1153
|
-
if (priorSessions.length > 0) {
|
|
1154
|
-
addLine(`## Prior Agent Sessions (last ${priorSessions.length})`);
|
|
1155
|
-
for (const s of priorSessions) {
|
|
1156
|
-
const date = new Date(s.startedAt).toISOString().split("T")[0];
|
|
1157
|
-
const created = Array.isArray(s.entriesCreated) ? s.entriesCreated.length : s.entriesCreated ?? 0;
|
|
1158
|
-
const modified = Array.isArray(s.entriesModified) ? s.entriesModified.length : s.entriesModified ?? 0;
|
|
1159
|
-
const stats = `${created} created, ${modified} modified, ${s.relationsCreated ?? 0} relations`;
|
|
1160
|
-
const gates = (s.gateFailures ?? 0) > 0 ? `, ${s.gateFailures} gate failures` : "";
|
|
1161
|
-
const warns = (s.contradictionWarnings ?? 0) > 0 ? `, ${s.contradictionWarnings} contradiction warnings` : "";
|
|
1162
|
-
addLine(`- **${date}** (${s.status}, by ${s.initiatedBy ?? "unknown"}) \u2014 ${stats}${gates}${warns}`);
|
|
1269
|
+
if (readiness.gaps?.length > 0) {
|
|
1270
|
+
briefingItems.push(`**${readiness.gaps.length} gap${readiness.gaps.length === 1 ? "" : "s"}** remaining`);
|
|
1163
1271
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
if (constraintEntries.length > 0) {
|
|
1167
|
-
addLine(`## Active Constraints (${constraintEntries.length})`);
|
|
1168
|
-
addLine("_Architecture, business rules, and decisions that govern this workspace._");
|
|
1169
|
-
addLine("");
|
|
1170
|
-
for (const c of constraintEntries) {
|
|
1171
|
-
const id = c.entryId ?? "(no ID)";
|
|
1172
|
-
const col = c.collectionSlug ?? "";
|
|
1173
|
-
const desc = c.data?.description ?? c.data?.rationale ?? "";
|
|
1174
|
-
const truncated = desc.length > 100 ? desc.slice(0, 100) + "..." : desc;
|
|
1175
|
-
addLine(`- \`${id}\` **${c.name}** [${col}] \u2014 ${truncated}`);
|
|
1272
|
+
if (constraintEntries.length > 0) {
|
|
1273
|
+
briefingItems.push(`**${constraintEntries.length} active constraint${constraintEntries.length === 1 ? "" : "s"}** (architecture, rules, decisions)`);
|
|
1176
1274
|
}
|
|
1177
|
-
|
|
1275
|
+
if (briefingItems.length > 0) {
|
|
1276
|
+
lines.push("## Briefing");
|
|
1277
|
+
for (const item of briefingItems) {
|
|
1278
|
+
lines.push(`- ${item}`);
|
|
1279
|
+
}
|
|
1280
|
+
lines.push("");
|
|
1281
|
+
}
|
|
1282
|
+
lines.push("What would you like to work on?");
|
|
1283
|
+
lines.push("");
|
|
1178
1284
|
}
|
|
1179
1285
|
if (errors.length > 0) {
|
|
1180
|
-
|
|
1181
|
-
for (const err of errors)
|
|
1182
|
-
|
|
1286
|
+
lines.push("## Errors");
|
|
1287
|
+
for (const err of errors) lines.push(`- ${err}`);
|
|
1288
|
+
lines.push("");
|
|
1183
1289
|
}
|
|
1184
1290
|
if (agentSessionId) {
|
|
1185
1291
|
try {
|
|
1186
1292
|
await mcpCall("agent.markOriented", { sessionId: agentSessionId });
|
|
1187
1293
|
setSessionOriented(true);
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
const readinessInfo = readiness ? `${readiness.score}% readiness.` : "";
|
|
1191
|
-
const tensionInfo = openTensions.length > 0 ? `${openTensions.length} open tensions.` : "No open tensions.";
|
|
1192
|
-
let sessionSummary = "";
|
|
1193
|
-
if (priorSessions.length > 0) {
|
|
1194
|
-
const lastSession = priorSessions[0];
|
|
1195
|
-
const created = Array.isArray(lastSession.entriesCreated) ? lastSession.entriesCreated.length : lastSession.entriesCreated ?? 0;
|
|
1196
|
-
const modified = Array.isArray(lastSession.entriesModified) ? lastSession.entriesModified.length : lastSession.entriesModified ?? 0;
|
|
1197
|
-
sessionSummary = `Last session: ${created} created, ${modified} modified.`;
|
|
1198
|
-
}
|
|
1199
|
-
addLine("---");
|
|
1200
|
-
addLine(`Orientation complete. ${sessionInfo} Write tools now available. ${readinessInfo} ${tensionInfo} ${sessionSummary}`);
|
|
1294
|
+
lines.push("---");
|
|
1295
|
+
lines.push(`Orientation complete. Session ${agentSessionId}. Write tools available.`);
|
|
1201
1296
|
} catch {
|
|
1202
|
-
|
|
1203
|
-
|
|
1297
|
+
lines.push("---");
|
|
1298
|
+
lines.push("_Warning: Could not mark session as oriented. Write tools may be restricted._");
|
|
1204
1299
|
}
|
|
1205
1300
|
} else {
|
|
1206
|
-
|
|
1207
|
-
|
|
1301
|
+
lines.push("---");
|
|
1302
|
+
lines.push("_No active agent session. Call `agent-start` to begin a tracked session._");
|
|
1208
1303
|
}
|
|
1209
1304
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1210
1305
|
}
|
|
@@ -3500,9 +3595,9 @@ function buildPresetMenu(wsCtx) {
|
|
|
3500
3595
|
const lines = [
|
|
3501
3596
|
`# Welcome to ${wsCtx.workspaceName}`,
|
|
3502
3597
|
"",
|
|
3503
|
-
"Your workspace is fresh \u2014 let's
|
|
3598
|
+
"Your workspace is fresh \u2014 let's get it set up together.",
|
|
3504
3599
|
"",
|
|
3505
|
-
"
|
|
3600
|
+
"**Tell me: what are you building?** Describe it in a sentence or two and I'll help you pick the right structure. Or choose a preset to start from:",
|
|
3506
3601
|
""
|
|
3507
3602
|
];
|
|
3508
3603
|
for (const p of presets) {
|
|
@@ -3512,7 +3607,7 @@ function buildPresetMenu(wsCtx) {
|
|
|
3512
3607
|
"",
|
|
3513
3608
|
'Call `start` again with your choice, e.g.: `start preset="software-product"`',
|
|
3514
3609
|
"",
|
|
3515
|
-
"
|
|
3610
|
+
"_These are starting points. You can add, remove, or customize collections at any time using `create-collection` and `update-collection`._"
|
|
3516
3611
|
);
|
|
3517
3612
|
return lines.join("\n");
|
|
3518
3613
|
}
|
|
@@ -3565,46 +3660,57 @@ Available presets: ${listPresets().map((p) => `\`${p.id}\``).join(", ")}`;
|
|
|
3565
3660
|
}
|
|
3566
3661
|
lines.push(
|
|
3567
3662
|
"",
|
|
3568
|
-
"##
|
|
3663
|
+
"## Let's activate your workspace",
|
|
3569
3664
|
"",
|
|
3570
|
-
"
|
|
3571
|
-
""
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
`- \`capture collection="${col.slug}" name="..." description="..."\` \u2014 add to ${col.name}`
|
|
3576
|
-
);
|
|
3577
|
-
}
|
|
3578
|
-
lines.push(
|
|
3665
|
+
"I'll help you load knowledge step by step. Everything stays as a draft until you confirm it.",
|
|
3666
|
+
"",
|
|
3667
|
+
"**First:** Tell me what you're building in one or two sentences. I'll capture it as your product vision.",
|
|
3668
|
+
"",
|
|
3669
|
+
`_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)._`,
|
|
3579
3670
|
"",
|
|
3580
|
-
"
|
|
3671
|
+
"You can also customize your structure anytime: `create-collection`, `update-collection`, or `list-collections`.",
|
|
3581
3672
|
"",
|
|
3582
3673
|
"---",
|
|
3583
|
-
|
|
3674
|
+
"Orientation complete. Write tools are available."
|
|
3584
3675
|
);
|
|
3585
3676
|
return lines.join("\n");
|
|
3586
3677
|
}
|
|
3678
|
+
function computeWorkspaceAge(createdAt) {
|
|
3679
|
+
if (!createdAt) return { ageDays: 0, isNeglected: false };
|
|
3680
|
+
const ageDays = Math.floor((Date.now() - createdAt) / (1e3 * 60 * 60 * 24));
|
|
3681
|
+
return { ageDays, isNeglected: ageDays >= 30 };
|
|
3682
|
+
}
|
|
3683
|
+
function pickNextAction(gaps, openTensions, priorSessions) {
|
|
3684
|
+
if (gaps.length === 0 && openTensions.length === 0) return null;
|
|
3685
|
+
if (gaps.length > 0) {
|
|
3686
|
+
const gap = gaps[0];
|
|
3687
|
+
const ctaMap = {
|
|
3688
|
+
"strategy-vision": "Tell me what you're building \u2014 your vision, mission, and north star \u2014 and I'll capture it.",
|
|
3689
|
+
"architecture-layers": "Describe your architecture in a few sentences and I'll capture it.",
|
|
3690
|
+
"glossary-foundation": "What are the key terms your team uses? Tell me a few and I'll add them to the glossary.",
|
|
3691
|
+
"decisions-documented": "What's a recent significant decision your team made? I'll document it with the rationale.",
|
|
3692
|
+
"tensions-tracked": "What's a friction point or pain point you're dealing with? I'll capture it as a tension."
|
|
3693
|
+
};
|
|
3694
|
+
const cta = ctaMap[gap.id] ?? `Tell me about your ${gap.label.toLowerCase()} and I'll capture it.`;
|
|
3695
|
+
return { action: gap.label, cta };
|
|
3696
|
+
}
|
|
3697
|
+
if (openTensions.length > 0) {
|
|
3698
|
+
const t = openTensions[0];
|
|
3699
|
+
return {
|
|
3700
|
+
action: `Open tension: ${t.name}`,
|
|
3701
|
+
cta: "Want to discuss this tension or capture a decision about it?"
|
|
3702
|
+
};
|
|
3703
|
+
}
|
|
3704
|
+
return null;
|
|
3705
|
+
}
|
|
3587
3706
|
async function buildOrientResponse(wsCtx, agentSessionId, errors) {
|
|
3707
|
+
const wsFullCtx = await getWorkspaceContext();
|
|
3708
|
+
const { ageDays, isNeglected } = computeWorkspaceAge(wsFullCtx.createdAt);
|
|
3588
3709
|
let priorSessions = [];
|
|
3589
3710
|
try {
|
|
3590
3711
|
priorSessions = await mcpQuery("agent.recentSessions", { limit: 3 });
|
|
3591
3712
|
} catch {
|
|
3592
3713
|
}
|
|
3593
|
-
let constraintEntries = [];
|
|
3594
|
-
try {
|
|
3595
|
-
const [archEntries, ruleEntries, decisionEntries] = await Promise.all([
|
|
3596
|
-
mcpQuery("chain.listEntries", { collectionSlug: "architecture" }),
|
|
3597
|
-
mcpQuery("chain.listEntries", { collectionSlug: "business-rules" }),
|
|
3598
|
-
mcpQuery("chain.listEntries", { collectionSlug: "decisions" })
|
|
3599
|
-
]);
|
|
3600
|
-
const committed = [
|
|
3601
|
-
...(archEntries ?? []).filter((e) => e.status === "active" || e.status === "healthy"),
|
|
3602
|
-
...(ruleEntries ?? []).filter((e) => e.status === "Active" || e.status === "active"),
|
|
3603
|
-
...(decisionEntries ?? []).filter((e) => e.status === "Decided" || e.status === "active")
|
|
3604
|
-
];
|
|
3605
|
-
constraintEntries = committed.sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)).slice(0, 8);
|
|
3606
|
-
} catch {
|
|
3607
|
-
}
|
|
3608
3714
|
let openTensions = [];
|
|
3609
3715
|
try {
|
|
3610
3716
|
const tensions = await mcpQuery("chain.listEntries", { collectionSlug: "tensions" });
|
|
@@ -3618,53 +3724,63 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors) {
|
|
|
3618
3724
|
errors.push(`Readiness: ${e.message}`);
|
|
3619
3725
|
}
|
|
3620
3726
|
const lines = [];
|
|
3727
|
+
const isLowReadiness = readiness && readiness.score < 50;
|
|
3728
|
+
const isHighReadiness = readiness && readiness.score >= 50;
|
|
3621
3729
|
lines.push(`# ${wsCtx.workspaceName}`);
|
|
3622
3730
|
lines.push(`_Workspace \`${wsCtx.workspaceSlug}\` \u2014 Product Brain is healthy._`);
|
|
3623
3731
|
lines.push("");
|
|
3624
|
-
if (
|
|
3625
|
-
|
|
3626
|
-
lines.push(
|
|
3627
|
-
lines.push(`${scoreBar} ${readiness.passedChecks}/${readiness.totalChecks} requirements`);
|
|
3732
|
+
if (isLowReadiness && isNeglected) {
|
|
3733
|
+
lines.push(`Your workspace has been around for ${ageDays} days but is only ${readiness.score}% ready.`);
|
|
3734
|
+
lines.push("Let's close the gaps \u2014 or if the current structure doesn't fit, we can reshape it.");
|
|
3628
3735
|
lines.push("");
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3736
|
+
} else if (isLowReadiness) {
|
|
3737
|
+
lines.push(`Readiness: ${readiness.score}%. Let's get your workspace active.`);
|
|
3738
|
+
lines.push("");
|
|
3739
|
+
}
|
|
3740
|
+
if (isLowReadiness) {
|
|
3741
|
+
const nextAction = pickNextAction(readiness.gaps ?? [], openTensions, priorSessions);
|
|
3742
|
+
if (nextAction) {
|
|
3743
|
+
lines.push("## Recommended next step");
|
|
3744
|
+
lines.push(`**${nextAction.action}**`);
|
|
3745
|
+
lines.push("");
|
|
3746
|
+
lines.push(nextAction.cta);
|
|
3634
3747
|
lines.push("");
|
|
3748
|
+
lines.push('_Everything stays as a draft until you confirm. Say "commit" or "looks good" to promote to the Chain._');
|
|
3749
|
+
lines.push("");
|
|
3750
|
+
const remainingGaps = (readiness.gaps ?? []).length - 1;
|
|
3751
|
+
if (remainingGaps > 0 || openTensions.length > 0) {
|
|
3752
|
+
lines.push(`_${remainingGaps > 0 ? `${remainingGaps} more gap${remainingGaps === 1 ? "" : "s"}` : ""}${remainingGaps > 0 && openTensions.length > 0 ? " and " : ""}${openTensions.length > 0 ? `${openTensions.length} open tension${openTensions.length === 1 ? "" : "s"}` : ""} \u2014 ask "show full status" for details._`);
|
|
3753
|
+
lines.push("");
|
|
3754
|
+
}
|
|
3635
3755
|
}
|
|
3636
|
-
}
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
for (const t of openTensions) {
|
|
3640
|
-
const id = t.entryId ?? "(no ID)";
|
|
3641
|
-
const prio = t.data?.priority ?? "";
|
|
3642
|
-
lines.push(`- \`${id}\` ${t.name}${prio ? ` _(${prio})_` : ""}`);
|
|
3756
|
+
} else if (isHighReadiness) {
|
|
3757
|
+
if (readiness) {
|
|
3758
|
+
lines.push(`Readiness: ${readiness.score}% (${readiness.passedChecks}/${readiness.totalChecks}).`);
|
|
3643
3759
|
}
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
for (const s of priorSessions) {
|
|
3649
|
-
const date = new Date(s.startedAt).toISOString().split("T")[0];
|
|
3650
|
-
const created = Array.isArray(s.entriesCreated) ? s.entriesCreated.length : s.entriesCreated ?? 0;
|
|
3651
|
-
const modified = Array.isArray(s.entriesModified) ? s.entriesModified.length : s.entriesModified ?? 0;
|
|
3652
|
-
const stats = `${created} created, ${modified} modified, ${s.relationsCreated ?? 0} relations`;
|
|
3653
|
-
lines.push(`- **${date}** (${s.status}, by ${s.initiatedBy ?? "unknown"}) \u2014 ${stats}`);
|
|
3760
|
+
const briefingItems = [];
|
|
3761
|
+
if (openTensions.length > 0) {
|
|
3762
|
+
const topTension = openTensions[0];
|
|
3763
|
+
briefingItems.push(`**${openTensions.length} open tension${openTensions.length === 1 ? "" : "s"}** \u2014 top: ${topTension.name}`);
|
|
3654
3764
|
}
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
lines.push(
|
|
3765
|
+
if (priorSessions.length > 0) {
|
|
3766
|
+
const last = priorSessions[0];
|
|
3767
|
+
const date = new Date(last.startedAt).toISOString().split("T")[0];
|
|
3768
|
+
const created = Array.isArray(last.entriesCreated) ? last.entriesCreated.length : last.entriesCreated ?? 0;
|
|
3769
|
+
const modified = Array.isArray(last.entriesModified) ? last.entriesModified.length : last.entriesModified ?? 0;
|
|
3770
|
+
briefingItems.push(`**Last session** (${date}): ${created} created, ${modified} modified`);
|
|
3771
|
+
}
|
|
3772
|
+
if (readiness?.gaps?.length > 0) {
|
|
3773
|
+
briefingItems.push(`**${readiness.gaps.length} gap${readiness.gaps.length === 1 ? "" : "s"}** remaining`);
|
|
3774
|
+
}
|
|
3775
|
+
if (briefingItems.length > 0) {
|
|
3776
|
+
lines.push("");
|
|
3777
|
+
lines.push("## Briefing");
|
|
3778
|
+
for (const item of briefingItems) {
|
|
3779
|
+
lines.push(`- ${item}`);
|
|
3780
|
+
}
|
|
3781
|
+
lines.push("");
|
|
3667
3782
|
}
|
|
3783
|
+
lines.push("What would you like to work on?");
|
|
3668
3784
|
lines.push("");
|
|
3669
3785
|
}
|
|
3670
3786
|
if (errors.length > 0) {
|
|
@@ -3676,11 +3792,9 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors) {
|
|
|
3676
3792
|
try {
|
|
3677
3793
|
await mcpCall("agent.markOriented", { sessionId: agentSessionId });
|
|
3678
3794
|
setSessionOriented(true);
|
|
3679
|
-
const readinessInfo = readiness ? `${readiness.score}% readiness.` : "";
|
|
3680
|
-
const tensionInfo = openTensions.length > 0 ? `${openTensions.length} open tensions.` : "No open tensions.";
|
|
3681
3795
|
lines.push("---");
|
|
3682
3796
|
lines.push(
|
|
3683
|
-
`Orientation complete. Session ${agentSessionId}. Write tools
|
|
3797
|
+
`Orientation complete. Session ${agentSessionId}. Write tools available.`
|
|
3684
3798
|
);
|
|
3685
3799
|
} catch {
|
|
3686
3800
|
lines.push("---");
|
|
@@ -4223,14 +4337,26 @@ var server = new McpServer2(
|
|
|
4223
4337
|
" 4. Drill in: use `get-entry` for full details \u2014 data, labels, relations, history.",
|
|
4224
4338
|
" 5. Context: use `gather-context` with an entryId or a task description.",
|
|
4225
4339
|
" 6. Capture: use `capture` to create entries \u2014 auto-links and scores in one call.",
|
|
4226
|
-
" 7. Commit: use `commit-entry` to promote drafts to SSOT.",
|
|
4340
|
+
" 7. Commit: use `commit-entry` to promote drafts to SSOT \u2014 only when the user confirms.",
|
|
4227
4341
|
" 8. Connect: use `suggest-links` then `relate-entries` to build the graph.",
|
|
4228
4342
|
" 9. Close: call `agent-close` when done \u2014 records session activity.",
|
|
4229
4343
|
"",
|
|
4230
4344
|
"Write tools (capture, update-entry, relate-entries, commit-entry) require:",
|
|
4231
4345
|
" - An active agent session (call agent-start)",
|
|
4232
4346
|
" - Completed orientation (call orient)",
|
|
4233
|
-
" - A readwrite API key scope"
|
|
4347
|
+
" - A readwrite API key scope",
|
|
4348
|
+
"",
|
|
4349
|
+
"Commit-on-confirm: always capture as draft first and show the user what was captured.",
|
|
4350
|
+
"Only call `commit-entry` when the user explicitly confirms (e.g. 'commit', 'looks good', 'yes').",
|
|
4351
|
+
"This builds trust \u2014 the Chain (main) is SSOT; nothing goes there without user consent.",
|
|
4352
|
+
"",
|
|
4353
|
+
"Workspace setup: use `create-collection` and `update-collection` to shape the workspace",
|
|
4354
|
+
"structure with the user. Ask what they need to track; presets are starting points, not fixed.",
|
|
4355
|
+
"",
|
|
4356
|
+
"Personalization: if you have context about the user from memory (prior work, recent",
|
|
4357
|
+
"conversations, team context), use it to personalize recommendations. For example,",
|
|
4358
|
+
"'Based on your recent pitch reviews, the gap most likely to matter is X.'",
|
|
4359
|
+
"The orient/start output gives you the workspace state; your memory fills in the human context."
|
|
4234
4360
|
].join("\n")
|
|
4235
4361
|
}
|
|
4236
4362
|
);
|