@productbrain/mcp 0.0.1-beta.62 → 0.0.1-beta.63
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-4M3SUZHX.js → chunk-DM5DZC7B.js} +103 -18
- package/dist/chunk-DM5DZC7B.js.map +1 -0
- package/dist/{chunk-MH5MYP76.js → chunk-GKMXGRJW.js} +217 -53
- package/dist/chunk-GKMXGRJW.js.map +1 -0
- package/dist/{chunk-MRIO53BY.js → chunk-RQXM3TCI.js} +19 -1
- package/dist/chunk-RQXM3TCI.js.map +1 -0
- package/dist/cli/index.js +1 -1
- package/dist/http.js +3 -3
- package/dist/index.js +3 -3
- package/dist/{setup-LSCFKMW7.js → setup-GQ3LQS2L.js} +9 -7
- package/dist/setup-GQ3LQS2L.js.map +1 -0
- package/dist/{smart-capture-NXLUISUD.js → smart-capture-NYOKGAFS.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-4M3SUZHX.js.map +0 -1
- package/dist/chunk-MH5MYP76.js.map +0 -1
- package/dist/chunk-MRIO53BY.js.map +0 -1
- package/dist/setup-LSCFKMW7.js.map +0 -1
- /package/dist/{smart-capture-NXLUISUD.js.map → smart-capture-NYOKGAFS.js.map} +0 -0
|
@@ -38,11 +38,12 @@ import {
|
|
|
38
38
|
unknownAction,
|
|
39
39
|
validationResult,
|
|
40
40
|
withEnvelope
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-DM5DZC7B.js";
|
|
42
42
|
import {
|
|
43
|
+
trackKnowledgeGap,
|
|
43
44
|
trackQualityCheck,
|
|
44
45
|
trackQualityVerdict
|
|
45
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-RQXM3TCI.js";
|
|
46
47
|
|
|
47
48
|
// src/server.ts
|
|
48
49
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -232,7 +233,7 @@ ${formatted}` }],
|
|
|
232
233
|
},
|
|
233
234
|
withEnvelope(async ({ entryId }) => {
|
|
234
235
|
requireWriteAccess();
|
|
235
|
-
const { runContradictionCheck } = await import("./smart-capture-
|
|
236
|
+
const { runContradictionCheck } = await import("./smart-capture-NYOKGAFS.js");
|
|
236
237
|
const entry = await mcpQuery("chain.getEntry", { entryId });
|
|
237
238
|
if (!entry) {
|
|
238
239
|
return notFoundResult(entryId, `Entry '${entryId}' not found. Try search to find the right ID.`);
|
|
@@ -352,6 +353,43 @@ ${formatted}` }],
|
|
|
352
353
|
|
|
353
354
|
// src/tools/entries.ts
|
|
354
355
|
import { z as z2 } from "zod";
|
|
356
|
+
|
|
357
|
+
// src/gap-store.ts
|
|
358
|
+
var SESSION_GAPS = [];
|
|
359
|
+
var DEDUP_WINDOW_MS = 6e4;
|
|
360
|
+
function isDuplicate(gap) {
|
|
361
|
+
const cutoff = gap.timestamp - DEDUP_WINDOW_MS;
|
|
362
|
+
return SESSION_GAPS.some(
|
|
363
|
+
(existing) => existing.query === gap.query && existing.tool === gap.tool && existing.action === gap.action && existing.timestamp >= cutoff
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
function recordGap(gap) {
|
|
367
|
+
const timestamped = { ...gap, timestamp: Date.now() };
|
|
368
|
+
if (isDuplicate(timestamped)) return false;
|
|
369
|
+
SESSION_GAPS.push(timestamped);
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
function getSessionGaps() {
|
|
373
|
+
return SESSION_GAPS;
|
|
374
|
+
}
|
|
375
|
+
function getTopGaps(limit = 5) {
|
|
376
|
+
const counts = /* @__PURE__ */ new Map();
|
|
377
|
+
for (const gap of SESSION_GAPS) {
|
|
378
|
+
const key = gap.query.toLowerCase().trim();
|
|
379
|
+
const existing = counts.get(key);
|
|
380
|
+
if (existing) {
|
|
381
|
+
existing.count++;
|
|
382
|
+
} else {
|
|
383
|
+
counts.set(key, { count: 1, gapType: gap.gapType });
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return [...counts.entries()].map(([query, { count, gapType }]) => ({ query, count, gapType })).sort((a, b) => b.count - a.count).slice(0, limit);
|
|
387
|
+
}
|
|
388
|
+
function clearSessionGaps() {
|
|
389
|
+
SESSION_GAPS.length = 0;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/tools/entries.ts
|
|
355
393
|
function sanitizeEntryData(data) {
|
|
356
394
|
if (!data || typeof data !== "object") return void 0;
|
|
357
395
|
const filtered = Object.fromEntries(
|
|
@@ -664,15 +702,23 @@ async function handleSearch(server, query, collection, status) {
|
|
|
664
702
|
mcpQuery("chain.listCollections")
|
|
665
703
|
]);
|
|
666
704
|
if (results.length === 0) {
|
|
705
|
+
const recorded = recordGap({ query, tool: "entries", action: "search", gapType: "search_zero", collectionScope: collection });
|
|
706
|
+
if (recorded) {
|
|
707
|
+
getWorkspaceId().then((wsId) => trackKnowledgeGap(wsId, { query, tool: "entries", action: "search", gap_type: "search_zero", collection_scope: collection })).catch(() => {
|
|
708
|
+
});
|
|
709
|
+
}
|
|
667
710
|
return {
|
|
668
711
|
content: [{
|
|
669
712
|
type: "text",
|
|
670
|
-
text: `
|
|
713
|
+
text: `"${query}" is not yet on the Chain${scope}. Try a broader search or use \`collections action=list\` to browse available knowledge.`
|
|
671
714
|
}],
|
|
672
715
|
structuredContent: success(
|
|
673
|
-
`
|
|
716
|
+
`'${query}' is not yet on the Chain${scope}.`,
|
|
674
717
|
{ entries: [], total: 0, query },
|
|
675
|
-
[
|
|
718
|
+
[
|
|
719
|
+
{ tool: "entries", description: "Try a broader search", parameters: { action: "search", query: query.split(" ")[0] } },
|
|
720
|
+
{ tool: "collections", description: "Browse available collections", parameters: { action: "list" } }
|
|
721
|
+
]
|
|
676
722
|
)
|
|
677
723
|
};
|
|
678
724
|
}
|
|
@@ -1253,6 +1299,11 @@ async function handleGather(server, entryId, task, mode, maxHops, maxResults) {
|
|
|
1253
1299
|
return notFoundResult(entryId, `Entry '${entryId}' not found. Try search to find the right ID.`);
|
|
1254
1300
|
}
|
|
1255
1301
|
if (result2.context.length === 0) {
|
|
1302
|
+
const recorded = recordGap({ query: result2.root.entryId, tool: "context", action: "gather", gapType: "context_graph_empty" });
|
|
1303
|
+
if (recorded) {
|
|
1304
|
+
getWorkspaceId().then((wsId) => trackKnowledgeGap(wsId, { query: result2.root.entryId, tool: "context", action: "gather", gap_type: "context_graph_empty" })).catch(() => {
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1256
1307
|
return {
|
|
1257
1308
|
content: [{
|
|
1258
1309
|
type: "text",
|
|
@@ -1330,6 +1381,12 @@ Use \`graph action=suggest\` to discover potential connections.`
|
|
|
1330
1381
|
maxResults
|
|
1331
1382
|
});
|
|
1332
1383
|
if (!result2 || result2.totalFound === 0) {
|
|
1384
|
+
const taskQuery = task.slice(0, 200);
|
|
1385
|
+
const recorded = recordGap({ query: taskQuery, tool: "context", action: "gather", gapType: "context_task_empty" });
|
|
1386
|
+
if (recorded) {
|
|
1387
|
+
getWorkspaceId().then((wsId) => trackKnowledgeGap(wsId, { query: taskQuery, tool: "context", action: "gather", gap_type: "context_task_empty" })).catch(() => {
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1333
1390
|
return {
|
|
1334
1391
|
content: [{
|
|
1335
1392
|
type: "text",
|
|
@@ -1337,15 +1394,15 @@ Use \`graph action=suggest\` to discover potential connections.`
|
|
|
1337
1394
|
|
|
1338
1395
|
**Confidence:** None
|
|
1339
1396
|
|
|
1340
|
-
|
|
1397
|
+
The Chain doesn't cover this area yet. If you discover relevant knowledge during this task, use \`capture\` with a name and description to add it.
|
|
1341
1398
|
|
|
1342
|
-
|
|
1399
|
+
_This helps future sessions start with better context._`
|
|
1343
1400
|
}],
|
|
1344
1401
|
structuredContent: failure(
|
|
1345
1402
|
"NOT_FOUND",
|
|
1346
|
-
"
|
|
1347
|
-
"
|
|
1348
|
-
[{ tool: "
|
|
1403
|
+
"The Chain doesn't cover this area yet.",
|
|
1404
|
+
"If you discover relevant knowledge during this task, capture it with a name and description.",
|
|
1405
|
+
[{ tool: "entries", description: "Search for related terms", parameters: { action: "search", query: task.split(" ").slice(0, 3).join(" ") } }]
|
|
1349
1406
|
)
|
|
1350
1407
|
};
|
|
1351
1408
|
}
|
|
@@ -1398,6 +1455,11 @@ _Consider capturing domain knowledge discovered during this task via \`capture\`
|
|
|
1398
1455
|
return notFoundResult(entryId, `Entry '${entryId}' not found. Try search to find the right ID.`);
|
|
1399
1456
|
}
|
|
1400
1457
|
if (result.related.length === 0) {
|
|
1458
|
+
const recorded = recordGap({ query: result.root.entryId, tool: "context", action: "gather", gapType: "context_entry_isolated" });
|
|
1459
|
+
if (recorded) {
|
|
1460
|
+
getWorkspaceId().then((wsId) => trackKnowledgeGap(wsId, { query: result.root.entryId, tool: "context", action: "gather", gap_type: "context_entry_isolated" })).catch(() => {
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1401
1463
|
return {
|
|
1402
1464
|
content: [{
|
|
1403
1465
|
type: "text",
|
|
@@ -2031,6 +2093,18 @@ async function runWrapupReview() {
|
|
|
2031
2093
|
}
|
|
2032
2094
|
lines.push("");
|
|
2033
2095
|
}
|
|
2096
|
+
const topGaps = getTopGaps(5);
|
|
2097
|
+
if (topGaps.length > 0) {
|
|
2098
|
+
lines.push("### Learning Opportunities", "");
|
|
2099
|
+
lines.push(`This session, agents looked for ${topGaps.length} topic${topGaps.length === 1 ? "" : "s"} not yet on the Chain:`, "");
|
|
2100
|
+
for (const gap of topGaps) {
|
|
2101
|
+
const freq = gap.count > 1 ? ` _(${gap.count}x)_` : "";
|
|
2102
|
+
lines.push(`- **${gap.query}** \u2014 not yet captured${freq}`);
|
|
2103
|
+
}
|
|
2104
|
+
lines.push("");
|
|
2105
|
+
lines.push("_Capturing these will make future sessions more effective._");
|
|
2106
|
+
lines.push("");
|
|
2107
|
+
}
|
|
2034
2108
|
if (data.drafts.length > 0) {
|
|
2035
2109
|
const draftIds = data.drafts.map((d) => `\`${d.entryId}\``).join(", ");
|
|
2036
2110
|
lines.push("### Actions", "");
|
|
@@ -2038,7 +2112,8 @@ async function runWrapupReview() {
|
|
|
2038
2112
|
lines.push("- **Commit all:** call `session-wrapup` with action `commit-all`");
|
|
2039
2113
|
lines.push("- **Skip:** call `session action=close` \u2014 drafts remain for next session's orient recovery.");
|
|
2040
2114
|
}
|
|
2041
|
-
|
|
2115
|
+
const gapCount = getSessionGaps().length;
|
|
2116
|
+
return { text: lines.join("\n"), data, suggestions, gapCount };
|
|
2042
2117
|
}
|
|
2043
2118
|
async function runWrapupCommitAll(data, cachedSuggestions) {
|
|
2044
2119
|
requireWriteAccess();
|
|
@@ -2163,7 +2238,7 @@ function registerWrapupTools(server) {
|
|
|
2163
2238
|
}
|
|
2164
2239
|
);
|
|
2165
2240
|
}
|
|
2166
|
-
const { text, data, suggestions, failureCode } = await runWrapupReview();
|
|
2241
|
+
const { text, data, suggestions, failureCode, gapCount } = await runWrapupReview();
|
|
2167
2242
|
lastReviewData = data;
|
|
2168
2243
|
lastReviewSuggestions = suggestions;
|
|
2169
2244
|
lastReviewSessionId = getAgentSessionId();
|
|
@@ -2174,14 +2249,16 @@ ${text}` : text;
|
|
|
2174
2249
|
return failureResult(fullText, failureCode, text);
|
|
2175
2250
|
}
|
|
2176
2251
|
const next = data.drafts.length > 0 ? [{ tool: "session-wrapup", description: "Commit all drafts", parameters: { action: "commit-all" } }] : void 0;
|
|
2252
|
+
const gapsSummary = gapCount ? `, ${gapCount} knowledge gaps detected` : "";
|
|
2177
2253
|
return successResult(
|
|
2178
2254
|
fullText,
|
|
2179
|
-
`Session review: ${data.drafts.length} uncommitted, ${data.committed.length} committed, ${suggestions.length} link suggestions.`,
|
|
2255
|
+
`Session review: ${data.drafts.length} uncommitted, ${data.committed.length} committed, ${suggestions.length} link suggestions${gapsSummary}.`,
|
|
2180
2256
|
{
|
|
2181
2257
|
drafts: data.drafts.length,
|
|
2182
2258
|
committed: data.committed.length,
|
|
2183
2259
|
uncommitted: data.summary.uncommitted,
|
|
2184
|
-
suggestedLinks: suggestions.length
|
|
2260
|
+
suggestedLinks: suggestions.length,
|
|
2261
|
+
knowledgeGaps: gapCount ?? 0
|
|
2185
2262
|
},
|
|
2186
2263
|
next
|
|
2187
2264
|
);
|
|
@@ -2296,6 +2373,7 @@ async function handleClose() {
|
|
|
2296
2373
|
}
|
|
2297
2374
|
}
|
|
2298
2375
|
await closeAgentSession();
|
|
2376
|
+
clearSessionGaps();
|
|
2299
2377
|
const lines = [
|
|
2300
2378
|
`Session ${sessionId} closed.`,
|
|
2301
2379
|
""
|
|
@@ -3469,7 +3547,7 @@ var workflowsSchema = z11.object({
|
|
|
3469
3547
|
"'list': browse available workflows. 'checkpoint': record round output or final summary. 'get-run': inspect a persisted workflow run."
|
|
3470
3548
|
),
|
|
3471
3549
|
workflowId: z11.string().optional().describe("Workflow ID for checkpoint, e.g. 'retro'"),
|
|
3472
|
-
runId: z11.string().optional().describe("Workflow run ID to
|
|
3550
|
+
runId: z11.string().optional().describe("Workflow run ID: for get-run, which run to load; for checkpoint, target this run (avoids session drift \u2014 pass runId from get-run)."),
|
|
3473
3551
|
roundId: z11.string().optional().describe("Round ID for checkpoint, e.g. 'what-went-well'"),
|
|
3474
3552
|
output: z11.union([z11.string(), workflowRunOutputInputSchema]).optional().describe(
|
|
3475
3553
|
"The round's output \u2014 either legacy synthesized text or a typed workflow run payload."
|
|
@@ -3504,6 +3582,38 @@ function validateFinalWorkflowCheckpoint(workflow, roundId) {
|
|
|
3504
3582
|
}
|
|
3505
3583
|
return null;
|
|
3506
3584
|
}
|
|
3585
|
+
function levenshtein(a, b) {
|
|
3586
|
+
const m = a.length;
|
|
3587
|
+
const n = b.length;
|
|
3588
|
+
const dp = Array.from(
|
|
3589
|
+
{ length: m + 1 },
|
|
3590
|
+
(_, i) => Array.from({ length: n + 1 }, (_2, j) => i === 0 ? j : j === 0 ? i : 0)
|
|
3591
|
+
);
|
|
3592
|
+
for (let i = 1; i <= m; i++) {
|
|
3593
|
+
for (let j = 1; j <= n; j++) {
|
|
3594
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
3595
|
+
}
|
|
3596
|
+
}
|
|
3597
|
+
return dp[m][n];
|
|
3598
|
+
}
|
|
3599
|
+
function findClosestRound(input, rounds) {
|
|
3600
|
+
if (rounds.length === 0) return null;
|
|
3601
|
+
const substringMatch = rounds.find(
|
|
3602
|
+
(r) => r.id.includes(input) || input.includes(r.id)
|
|
3603
|
+
);
|
|
3604
|
+
if (substringMatch) return substringMatch;
|
|
3605
|
+
let best = rounds[0];
|
|
3606
|
+
let bestDist = levenshtein(input, best.id);
|
|
3607
|
+
for (const r of rounds.slice(1)) {
|
|
3608
|
+
const dist = levenshtein(input, r.id);
|
|
3609
|
+
if (dist < bestDist) {
|
|
3610
|
+
bestDist = dist;
|
|
3611
|
+
best = r;
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
const threshold = Math.ceil(Math.max(input.length, best.id.length) / 2);
|
|
3615
|
+
return bestDist <= threshold ? best : null;
|
|
3616
|
+
}
|
|
3507
3617
|
function registerWorkflowTools(server) {
|
|
3508
3618
|
const workflowsTool = server.registerTool(
|
|
3509
3619
|
"workflows",
|
|
@@ -3554,7 +3664,8 @@ function registerWorkflowTools(server) {
|
|
|
3554
3664
|
restart,
|
|
3555
3665
|
summaryName,
|
|
3556
3666
|
summaryDescription,
|
|
3557
|
-
summaryEntryId
|
|
3667
|
+
summaryEntryId,
|
|
3668
|
+
runId
|
|
3558
3669
|
);
|
|
3559
3670
|
}
|
|
3560
3671
|
return unknownAction(action, WORKFLOWS_ACTIONS);
|
|
@@ -3662,7 +3773,7 @@ async function handleGetRun(runId, workflowId) {
|
|
|
3662
3773
|
)
|
|
3663
3774
|
};
|
|
3664
3775
|
}
|
|
3665
|
-
async function handleCheckpoint(workflowId, roundId, output, isFinal, restart, summaryName, summaryDescription, summaryEntryId) {
|
|
3776
|
+
async function handleCheckpoint(workflowId, roundId, output, isFinal, restart, summaryName, summaryDescription, summaryEntryId, runId) {
|
|
3666
3777
|
const wf = getWorkflow(workflowId);
|
|
3667
3778
|
if (!wf) {
|
|
3668
3779
|
return {
|
|
@@ -3683,18 +3794,33 @@ This checkpoint was NOT saved. Continue the conversation \u2014 the facilitator
|
|
|
3683
3794
|
const round = wf.rounds.find((r) => r.id === roundId);
|
|
3684
3795
|
if (!round) {
|
|
3685
3796
|
const availableRounds = wf.rounds.map((r) => r.id).join(", ");
|
|
3797
|
+
const closest = findClosestRound(roundId, wf.rounds);
|
|
3798
|
+
const correctionActions = closest ? [
|
|
3799
|
+
{
|
|
3800
|
+
tool: "workflows",
|
|
3801
|
+
description: `Retry with corrected round ID '${closest.id}'`,
|
|
3802
|
+
parameters: { action: "checkpoint", workflowId, roundId: closest.id }
|
|
3803
|
+
}
|
|
3804
|
+
] : [
|
|
3805
|
+
{
|
|
3806
|
+
tool: "workflows",
|
|
3807
|
+
description: "List workflows to inspect round IDs",
|
|
3808
|
+
parameters: { action: "list" }
|
|
3809
|
+
}
|
|
3810
|
+
];
|
|
3811
|
+
const suggestionHint = closest ? ` Did you mean '${closest.id}'?` : "";
|
|
3686
3812
|
return {
|
|
3687
3813
|
content: [{
|
|
3688
3814
|
type: "text",
|
|
3689
|
-
text: `Round "${roundId}" not found in workflow "${workflowId}"
|
|
3815
|
+
text: `Round "${roundId}" not found in workflow "${workflowId}".${suggestionHint} Available rounds: ${availableRounds}.
|
|
3690
3816
|
|
|
3691
3817
|
This checkpoint was NOT saved. The conversation context is preserved \u2014 continue facilitating.`
|
|
3692
3818
|
}],
|
|
3693
3819
|
structuredContent: failure(
|
|
3694
3820
|
"NOT_FOUND",
|
|
3695
3821
|
`Round '${roundId}' not found in workflow '${workflowId}'.`,
|
|
3696
|
-
`Available rounds: ${availableRounds}`,
|
|
3697
|
-
|
|
3822
|
+
closest ? `Did you mean '${closest.id}'? Available rounds: ${availableRounds}` : `Available rounds: ${availableRounds}`,
|
|
3823
|
+
correctionActions
|
|
3698
3824
|
)
|
|
3699
3825
|
};
|
|
3700
3826
|
}
|
|
@@ -3755,7 +3881,8 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
|
|
|
3755
3881
|
normalizedOutput,
|
|
3756
3882
|
agentSessionId,
|
|
3757
3883
|
summaryEntryId,
|
|
3758
|
-
lines
|
|
3884
|
+
lines,
|
|
3885
|
+
runId
|
|
3759
3886
|
);
|
|
3760
3887
|
}
|
|
3761
3888
|
const existingRun = await mcpQuery(
|
|
@@ -3888,6 +4015,7 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
|
|
|
3888
4015
|
recordedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3889
4016
|
},
|
|
3890
4017
|
restart,
|
|
4018
|
+
...runId ? { runId } : {},
|
|
3891
4019
|
...summaryEntryId ? { summaryEntryId } : {}
|
|
3892
4020
|
});
|
|
3893
4021
|
lines.push(
|
|
@@ -3952,7 +4080,7 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
|
|
|
3952
4080
|
}
|
|
3953
4081
|
}
|
|
3954
4082
|
}
|
|
3955
|
-
async function handleFacilitatedFinalization(wf, descriptor, roundId, normalizedOutput, agentSessionId, summaryEntryId, lines) {
|
|
4083
|
+
async function handleFacilitatedFinalization(wf, descriptor, roundId, normalizedOutput, agentSessionId, summaryEntryId, lines, runId) {
|
|
3956
4084
|
const template = descriptor ? getWorkflowTemplateMetadata(descriptor) : { slug: wf.id, name: wf.name };
|
|
3957
4085
|
try {
|
|
3958
4086
|
const checkpointResult = await mcpMutation("chainwork.recordWorkflowCheckpoint", {
|
|
@@ -3974,7 +4102,8 @@ async function handleFacilitatedFinalization(wf, descriptor, roundId, normalized
|
|
|
3974
4102
|
output: normalizedOutput,
|
|
3975
4103
|
recordedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3976
4104
|
},
|
|
3977
|
-
summaryEntryId
|
|
4105
|
+
summaryEntryId,
|
|
4106
|
+
...runId ? { runId } : {}
|
|
3978
4107
|
});
|
|
3979
4108
|
const linked = checkpointResult.summary?.linked ?? false;
|
|
3980
4109
|
lines.push(
|
|
@@ -6176,7 +6305,7 @@ async function handleCommitConstellation(args) {
|
|
|
6176
6305
|
}
|
|
6177
6306
|
let contradictionWarnings = [];
|
|
6178
6307
|
try {
|
|
6179
|
-
const { runContradictionCheck } = await import("./smart-capture-
|
|
6308
|
+
const { runContradictionCheck } = await import("./smart-capture-NYOKGAFS.js");
|
|
6180
6309
|
const descField = betData.problem ?? betData.description ?? "";
|
|
6181
6310
|
contradictionWarnings = await runContradictionCheck(
|
|
6182
6311
|
betEntry.name ?? betId,
|
|
@@ -7024,7 +7153,7 @@ function extractionToBatchEntries(extracted) {
|
|
|
7024
7153
|
}
|
|
7025
7154
|
function getInterviewInstructions(workspaceName) {
|
|
7026
7155
|
return {
|
|
7027
|
-
systemPrompt: `You are activating the **${workspaceName}** Product Brain. Ask 1\u20132 focused questions, then extract structured knowledge and feed it to batch-capture.
|
|
7156
|
+
systemPrompt: `You are activating the **${workspaceName}** Product Brain. Ask 1\u20132 focused questions, then extract structured knowledge and feed it to batch-capture. In Open governance mode, entries are committed automatically. In consensus/role mode, they stay as drafts for review.`,
|
|
7028
7157
|
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.)`,
|
|
7029
7158
|
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.)`,
|
|
7030
7159
|
extractionGuidance: `After the user answers, extract:
|
|
@@ -7042,9 +7171,25 @@ Map to batch-capture entries:
|
|
|
7042
7171
|
- keyTerms \u2192 glossary (1 per term)
|
|
7043
7172
|
- keyDecisions \u2192 decisions (1 per decision)
|
|
7044
7173
|
- tensions \u2192 tensions (1 per tension)`,
|
|
7045
|
-
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.`,
|
|
7046
|
-
qualityNote:
|
|
7047
|
-
|
|
7174
|
+
captureInstructions: `Call batch-capture with the extracted entries. Omit \`autoCommit\` to follow workspace governance automatically, or pass \`autoCommit: false\` if the user wants a review-first pass. 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.`,
|
|
7175
|
+
qualityNote: (
|
|
7176
|
+
// FEAT-149: Retrieval-First Proof Moment.
|
|
7177
|
+
// After capture, demonstrate retrieval instead of offering commit ceremony.
|
|
7178
|
+
// The aha moment is the AI using the user's knowledge, not the user entering it.
|
|
7179
|
+
`After capturing, silently call graph action=suggest on the strategy or architecture entry to build connections.
|
|
7180
|
+
|
|
7181
|
+
**Then demonstrate the proof moment \u2014 this is the most important step:**
|
|
7182
|
+
Based on what the user told you, invent a specific, plausible development task they might actually do next (e.g. "add user authentication" for a SaaS app, "build the recommendation engine" for a marketplace, "set up the API layer" for a developer tool). Call \`context action=gather task="<your invented task>"\` to load their captured knowledge.
|
|
7183
|
+
|
|
7184
|
+
Present the results conversationally:
|
|
7185
|
+
"Here's what I already know about your product that I didn't know 5 minutes ago \u2014 if you asked me to **<task>**, I'd bring this context:"
|
|
7186
|
+
|
|
7187
|
+
Then list the key entries that context returned: their product vision, audience, tech decisions, glossary terms. Keep it concrete \u2014 entry names and one-line summaries, not abstract descriptions.
|
|
7188
|
+
|
|
7189
|
+
End with: "**Try it \u2014 ask me to help with something on your product.** I'll use everything I just learned."`
|
|
7190
|
+
),
|
|
7191
|
+
scanOffer: `After the proof moment, offer the codebase scan as a way to deepen the knowledge:
|
|
7192
|
+
"Want me to learn even more? I can scan your project files (README, package.json, source structure) and pick up technical decisions, conventions, and architecture that I missed. Takes about 2 minutes."`
|
|
7048
7193
|
};
|
|
7049
7194
|
}
|
|
7050
7195
|
function buildInterviewResponse(workspaceName) {
|
|
@@ -7054,15 +7199,11 @@ function buildInterviewResponse(workspaceName) {
|
|
|
7054
7199
|
"",
|
|
7055
7200
|
instructions.systemPrompt,
|
|
7056
7201
|
"",
|
|
7057
|
-
"I'll ask you 1\u20132 questions. Your answers become the
|
|
7202
|
+
"I'll ask you 1\u20132 questions. Your answers become the foundation of your product knowledge.",
|
|
7058
7203
|
"",
|
|
7059
7204
|
`**${instructions.question1}**`,
|
|
7060
7205
|
"",
|
|
7061
|
-
"_Take your time \u2014 a sentence or two is enough. I'll extract the structure from your answer._"
|
|
7062
|
-
"",
|
|
7063
|
-
`> ${instructions.scanOffer}`,
|
|
7064
|
-
"",
|
|
7065
|
-
"_Everything stays pending until you confirm._"
|
|
7206
|
+
"_Take your time \u2014 a sentence or two is enough. I'll extract the structure from your answer._"
|
|
7066
7207
|
].join("\n");
|
|
7067
7208
|
}
|
|
7068
7209
|
|
|
@@ -7087,7 +7228,7 @@ function registerStartTools(server) {
|
|
|
7087
7228
|
"start",
|
|
7088
7229
|
{
|
|
7089
7230
|
title: "Start Product Brain",
|
|
7090
|
-
description: "The zero-friction entry point. Say 'start PB' to begin.\n\n- **Fresh workspace (blank)**:
|
|
7231
|
+
description: "The zero-friction entry point. Say 'start PB' to begin.\n\n- **Fresh workspace (blank)**: asks what you're building, captures the essentials, and can scan your codebase if useful.\n- **Early workspace (seeded)**: picks up where you left off with the top gaps to fill.\n- **Active workspace (grounded/connected)**: standup briefing with recent activity and open items.\n\nUse this as your first call. Replaces the need to call orient or health separately.",
|
|
7091
7232
|
inputSchema: startSchema,
|
|
7092
7233
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
7093
7234
|
},
|
|
@@ -7192,14 +7333,15 @@ function buildBlankResponse(wsCtx, sessionCtx) {
|
|
|
7192
7333
|
instructions.extractionGuidance,
|
|
7193
7334
|
"",
|
|
7194
7335
|
instructions.captureInstructions,
|
|
7195
|
-
"Omit `autoCommit` \u2014
|
|
7336
|
+
"Omit `autoCommit` \u2014 capture tools auto-commit in Open mode and create drafts in consensus/role mode.",
|
|
7337
|
+
"",
|
|
7338
|
+
instructions.qualityNote,
|
|
7196
7339
|
"",
|
|
7197
|
-
|
|
7198
|
-
`"Now that I know what you're building, I can scan your project files and find technical decisions, features, and conventions that are relevant to **your** product. Want me to?"`,
|
|
7340
|
+
instructions.scanOffer,
|
|
7199
7341
|
"",
|
|
7200
7342
|
"If they say yes, scan README.md, package.json/pyproject.toml/Cargo.toml, and top-level source dirs.",
|
|
7201
7343
|
"Infer entries WITH the product context you now have. Present as a numbered list for confirmation.",
|
|
7202
|
-
"
|
|
7344
|
+
"Do not capture anything until the user confirms what to keep. Then capture only the confirmed entries and let normal workspace governance apply.",
|
|
7203
7345
|
"",
|
|
7204
7346
|
"## Path 2: Scan first",
|
|
7205
7347
|
"",
|
|
@@ -7209,17 +7351,18 @@ function buildBlankResponse(wsCtx, sessionCtx) {
|
|
|
7209
7351
|
"3. Top-level source directories and their purpose",
|
|
7210
7352
|
"",
|
|
7211
7353
|
"Infer 5\u20138 product knowledge entries (product name, tech decisions, features, conventions).",
|
|
7212
|
-
"Present as a numbered list. Ask which to keep.
|
|
7354
|
+
"Present as a numbered list. Ask which to keep. Only after confirmation, capture the confirmed entries and let normal workspace governance apply.",
|
|
7213
7355
|
"",
|
|
7214
7356
|
"**If the codebase is sparse** (no README, empty project): fall back to Path 1 (ask the user about their product).",
|
|
7215
7357
|
"",
|
|
7216
7358
|
"_Prefer 5 specific entries over 8 generic ones. Skip anything that would apply to any codebase._",
|
|
7217
7359
|
"",
|
|
7218
|
-
"
|
|
7360
|
+
"**After scan capture, run the proof moment (same as Path 1):**",
|
|
7361
|
+
instructions.qualityNote,
|
|
7219
7362
|
"",
|
|
7220
|
-
"
|
|
7363
|
+
"## Path 3: Preset",
|
|
7221
7364
|
"",
|
|
7222
|
-
|
|
7365
|
+
"If the user picks a preset, call `start` again with `preset: '<chosen-preset>'`."
|
|
7223
7366
|
].join("\n");
|
|
7224
7367
|
return {
|
|
7225
7368
|
text,
|
|
@@ -7256,6 +7399,19 @@ async function buildSeededResponse(wsCtx, readiness, agentSessionId) {
|
|
|
7256
7399
|
} else {
|
|
7257
7400
|
lines.push("No gaps detected \u2014 your workspace is filling up nicely. What would you like to work on?");
|
|
7258
7401
|
}
|
|
7402
|
+
const knowledgeGaps = getTopGaps(3);
|
|
7403
|
+
if (knowledgeGaps.length > 0) {
|
|
7404
|
+
lines.push("");
|
|
7405
|
+
lines.push("### Learning Opportunities");
|
|
7406
|
+
lines.push("_Topics agents recently looked for but aren't on the Chain yet:_");
|
|
7407
|
+
lines.push("");
|
|
7408
|
+
for (const gap of knowledgeGaps) {
|
|
7409
|
+
const freq = gap.count > 1 ? ` _(${gap.count}x)_` : "";
|
|
7410
|
+
lines.push(`- **${gap.query}**${freq}`);
|
|
7411
|
+
}
|
|
7412
|
+
lines.push("");
|
|
7413
|
+
lines.push("_Capturing these will make future sessions more effective._");
|
|
7414
|
+
}
|
|
7259
7415
|
const { oriented, orientationStatus } = await tryMarkOriented(agentSessionId);
|
|
7260
7416
|
const next = gaps.length > 0 ? [{ tool: "capture", description: `Fill gap: ${gaps[0].label}`, parameters: {} }] : [];
|
|
7261
7417
|
return {
|
|
@@ -7395,6 +7551,7 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors) {
|
|
|
7395
7551
|
const isLowReadiness = readiness !== null && readiness.score < 50;
|
|
7396
7552
|
const isHighReadiness = readiness !== null && readiness.score >= 50;
|
|
7397
7553
|
const stage = readiness?.stage ?? null;
|
|
7554
|
+
const captureBehaviorNote = wsFullCtx.governanceMode === "open" ? "_In Open mode, user-authored captures commit immediately unless you ask me to keep them as drafts._" : "_Everything I capture stays pending until you confirm._";
|
|
7398
7555
|
lines.push(`# ${wsCtx.workspaceName}`);
|
|
7399
7556
|
lines.push("_Product Brain is ready._");
|
|
7400
7557
|
lines.push("");
|
|
@@ -7415,7 +7572,7 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors) {
|
|
|
7415
7572
|
lines.push("");
|
|
7416
7573
|
lines.push(nextAction.cta);
|
|
7417
7574
|
lines.push("");
|
|
7418
|
-
lines.push(
|
|
7575
|
+
lines.push(captureBehaviorNote);
|
|
7419
7576
|
lines.push("");
|
|
7420
7577
|
const remainingGaps = (readiness.gaps ?? []).length - 1;
|
|
7421
7578
|
if (remainingGaps > 0 || openTensions.length > 0) {
|
|
@@ -9957,6 +10114,7 @@ function registerHealthTools(server) {
|
|
|
9957
10114
|
if (isLowReadiness) {
|
|
9958
10115
|
lines.push(`**Brain stage: ${orientStage}.**`);
|
|
9959
10116
|
lines.push("");
|
|
10117
|
+
const captureBehaviorNote = (readiness?.governanceMode ?? wsCtx?.governanceMode ?? "open") === "open" ? "_In Open mode, user-authored captures commit immediately unless you ask me to keep them as drafts._" : '_Everything stays as a draft until you confirm. Say "commit" or "looks good" to promote to the Chain._';
|
|
9960
10118
|
const gaps = readiness.gaps ?? [];
|
|
9961
10119
|
if (gaps.length > 0) {
|
|
9962
10120
|
const gap = gaps[0];
|
|
@@ -9966,7 +10124,7 @@ function registerHealthTools(server) {
|
|
|
9966
10124
|
lines.push("");
|
|
9967
10125
|
lines.push(cta);
|
|
9968
10126
|
lines.push("");
|
|
9969
|
-
lines.push(
|
|
10127
|
+
lines.push(captureBehaviorNote);
|
|
9970
10128
|
lines.push("");
|
|
9971
10129
|
const remainingGaps = gaps.length - 1;
|
|
9972
10130
|
if (remainingGaps > 0 || openTensions.length > 0) {
|
|
@@ -11353,7 +11511,7 @@ If ready, ask the user to confirm. If not, suggest specific improvements.
|
|
|
11353
11511
|
);
|
|
11354
11512
|
server.prompt(
|
|
11355
11513
|
"project-scan",
|
|
11356
|
-
"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.
|
|
11514
|
+
"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. The trust boundary is confirmation before capture: only capture entries the user explicitly keeps, then let normal workspace governance apply.",
|
|
11357
11515
|
{
|
|
11358
11516
|
workspaceContext: z21.string().optional().describe("Optional context about the workspace preset and existing collections (paste from start/orient output)")
|
|
11359
11517
|
},
|
|
@@ -11431,6 +11589,9 @@ Before calling batch-capture, check your extraction:
|
|
|
11431
11589
|
|
|
11432
11590
|
If validation fails for any entry, drop it rather than sending malformed data.
|
|
11433
11591
|
|
|
11592
|
+
Then present the inferred entries as a numbered list. Ask which to keep, and do NOT call batch-capture yet.
|
|
11593
|
+
Only after the user confirms which entries to keep should you capture the confirmed entries.
|
|
11594
|
+
|
|
11434
11595
|
## Step 4: Map to batch-capture
|
|
11435
11596
|
|
|
11436
11597
|
Map your extracted data to entries using these rules:
|
|
@@ -11448,19 +11609,22 @@ ${exampleOutput}
|
|
|
11448
11609
|
|
|
11449
11610
|
## Step 5: Call batch-capture
|
|
11450
11611
|
|
|
11612
|
+
After the user confirms which entries to keep, call batch-capture with only those confirmed entries.
|
|
11613
|
+
Omit \`autoCommit\` to let normal workspace governance apply.
|
|
11614
|
+
|
|
11451
11615
|
\`\`\`
|
|
11452
|
-
batch-capture entries=[...your
|
|
11616
|
+
batch-capture entries=[...your confirmed entries...]
|
|
11453
11617
|
\`\`\`
|
|
11454
11618
|
|
|
11455
11619
|
If \`failed > 0\` in the response, inspect \`failedEntries\` and retry those individually.
|
|
11456
11620
|
|
|
11457
|
-
## Step 6: Connect +
|
|
11621
|
+
## Step 6: Connect + Prove Value
|
|
11458
11622
|
|
|
11459
11623
|
After capture:
|
|
11460
11624
|
1. Call \`graph action=suggest\` on 2\u20133 key entries (vision, architecture, a decision)
|
|
11461
|
-
2.
|
|
11462
|
-
3.
|
|
11463
|
-
4.
|
|
11625
|
+
2. Invent a plausible next task and call \`context action=gather task="<that task>"\`
|
|
11626
|
+
3. Present what Product Brain now knows in that task context \u2014 this is the proof moment
|
|
11627
|
+
4. Then show one clear result summary so the user sees exactly what was added
|
|
11464
11628
|
|
|
11465
11629
|
**Begin with Step 1 now.** Read the files, then report what you found before extracting.`
|
|
11466
11630
|
}
|
|
@@ -11581,4 +11745,4 @@ export {
|
|
|
11581
11745
|
SERVER_VERSION,
|
|
11582
11746
|
createProductBrainServer
|
|
11583
11747
|
};
|
|
11584
|
-
//# sourceMappingURL=chunk-
|
|
11748
|
+
//# sourceMappingURL=chunk-GKMXGRJW.js.map
|