@productbrain/mcp 0.0.1-beta.32 → 0.0.1-beta.34
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-THAYIBTL.js → chunk-HTL6Y2AO.js} +2023 -184
- package/dist/chunk-HTL6Y2AO.js.map +1 -0
- package/dist/{chunk-SJ2ODB3Y.js → chunk-TB24VJ4Z.js} +3 -2
- package/dist/chunk-TB24VJ4Z.js.map +1 -0
- package/dist/{chunk-UOT3O5FN.js → chunk-TPX6TOGV.js} +2 -2
- package/dist/cli/index.js +1 -1
- package/dist/http.js +3 -3
- package/dist/index.js +3 -3
- package/dist/{setup-S2B5LCXQ.js → setup-AJCCLPQP.js} +2 -2
- package/dist/{smart-capture-6TQM35CV.js → smart-capture-QMBL4KGX.js} +3 -3
- package/package.json +2 -2
- package/dist/chunk-SJ2ODB3Y.js.map +0 -1
- package/dist/chunk-THAYIBTL.js.map +0 -1
- /package/dist/{chunk-UOT3O5FN.js.map → chunk-TPX6TOGV.js.map} +0 -0
- /package/dist/{setup-S2B5LCXQ.js.map → setup-AJCCLPQP.js.map} +0 -0
- /package/dist/{smart-capture-6TQM35CV.js.map → smart-capture-QMBL4KGX.js.map} +0 -0
|
@@ -25,11 +25,11 @@ import {
|
|
|
25
25
|
startAgentSession,
|
|
26
26
|
trackWriteTool,
|
|
27
27
|
translateStaleToolNames
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-TPX6TOGV.js";
|
|
29
29
|
import {
|
|
30
30
|
trackQualityCheck,
|
|
31
31
|
trackQualityVerdict
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-TB24VJ4Z.js";
|
|
33
33
|
|
|
34
34
|
// src/server.ts
|
|
35
35
|
import { McpServer as McpServer2 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -125,7 +125,7 @@ ${formatted}` }]
|
|
|
125
125
|
},
|
|
126
126
|
async ({ entryId }) => {
|
|
127
127
|
requireWriteAccess();
|
|
128
|
-
const { runContradictionCheck } = await import("./smart-capture-
|
|
128
|
+
const { runContradictionCheck } = await import("./smart-capture-QMBL4KGX.js");
|
|
129
129
|
const entry = await mcpQuery("chain.getEntry", { entryId });
|
|
130
130
|
if (!entry) {
|
|
131
131
|
return {
|
|
@@ -1569,7 +1569,7 @@ function registerLabelTools(server) {
|
|
|
1569
1569
|
}
|
|
1570
1570
|
|
|
1571
1571
|
// src/tools/health.ts
|
|
1572
|
-
import { z as
|
|
1572
|
+
import { z as z19 } from "zod";
|
|
1573
1573
|
|
|
1574
1574
|
// src/tools/session.ts
|
|
1575
1575
|
import { z as z9 } from "zod";
|
|
@@ -2311,8 +2311,200 @@ Create a Cursor Plan with these rounds as tasks. Mark each in_progress as you en
|
|
|
2311
2311
|
|
|
2312
2312
|
The retro must never fail silently. Always communicate state.`
|
|
2313
2313
|
};
|
|
2314
|
+
var SHAPE_WORKFLOW = {
|
|
2315
|
+
id: "shape",
|
|
2316
|
+
name: "Shape a Bet",
|
|
2317
|
+
shortDescription: "Coached shaping session \u2014 problem \u2192 appetite \u2192 elements \u2192 architecture \u2192 de-risk \u2192 pitch. Uses the `facilitate` tool for scoring and Chain capture.",
|
|
2318
|
+
icon: "\u25C6",
|
|
2319
|
+
facilitatorPreamble: `You are now in **Shaping Mode**. You are an **investigating** shaping facilitator powered by the \`facilitate\` tool and Cursor sub-agents.
|
|
2320
|
+
|
|
2321
|
+
## Your Behavior
|
|
2322
|
+
|
|
2323
|
+
1. **Start with \`facilitate action=start\`.** This creates a draft bet on the Chain and returns the Studio URL, initial coaching, and an investigationBrief.
|
|
2324
|
+
2. **Investigate before asking.** At each phase transition, read the \`investigationBrief\` in the structured response. Spawn sub-agents to search the codebase and Chain. Synthesize findings into proposals. Present proposals for user reaction.
|
|
2325
|
+
3. **Use \`facilitate action=respond\` for every input.** Pass the user's text, betEntryId, dimension, and the \`source\` field:
|
|
2326
|
+
- \`source="user"\` \u2014 user typed this directly (default, full score weight)
|
|
2327
|
+
- \`source="agent_proposal"\` \u2014 you generated this proposal text (discounted score)
|
|
2328
|
+
- \`source="user_reaction"\` \u2014 user reacting to your proposal (full score weight)
|
|
2329
|
+
4. **Read the structured response carefully.** The coaching object tells you what to say next. The scorecard tells you what's missing. The investigationBrief tells you what to investigate for the next phase.
|
|
2330
|
+
5. **One dimension at a time.** Follow the round progression below. Don't dump all dimensions at once.
|
|
2331
|
+
6. **Push back with evidence.** Don't just ask "Who's affected?" \u2014 search the codebase for evidence and say "I found 3 workarounds in the codebase related to this. Here's what they suggest about the problem."
|
|
2332
|
+
7. **Capture elements and risks inline** using the \`capture\` parameter on respond calls. Each becomes a Chain entry with a relation to the bet.
|
|
2333
|
+
8. **CRITICAL: Elements, rabbit holes, and no-gos MUST use the \`capture\` parameter.** Never pass them as plain \`userInput\` without \`capture\`. Without \`capture\`, nothing is saved to the bet \u2014 the data is silently lost. Always include \`capture={type:"element"|"risk"|"noGo", name:"...", description:"..."}\` for every structured item. Each element, risk, and no-go needs its own \`respond\` call with \`capture\`.
|
|
2334
|
+
9. **When captureReady is true, offer to finalize** \u2014 but don't force it. The user decides when the shape is complete.
|
|
2335
|
+
|
|
2336
|
+
## Investigation Pattern (ENT-59)
|
|
2337
|
+
|
|
2338
|
+
At phase transitions, follow this pattern:
|
|
2339
|
+
1. Read the \`investigationBrief\` from the structured response
|
|
2340
|
+
2. Spawn sub-agents in parallel (max 2, 30s budget each):
|
|
2341
|
+
- \`explore\` sub-agent: codebase searches (files, patterns, architecture)
|
|
2342
|
+
- \`generalPurpose\` sub-agent: Chain searches (entries, governance, context)
|
|
2343
|
+
3. Synthesize findings into a concrete proposal (cite files and entry IDs)
|
|
2344
|
+
4. Present the proposal for user reaction \u2014 "Based on the codebase, I'd suggest..."
|
|
2345
|
+
5. Score the user's reaction with \`source="user_reaction"\` (full weight)
|
|
2346
|
+
|
|
2347
|
+
If investigation fails or times out, continue with the conversation \u2014 the user's knowledge is always the primary input.
|
|
2348
|
+
|
|
2349
|
+
## DEC-56 Boundary
|
|
2350
|
+
|
|
2351
|
+
The **server judges** \u2014 it scores input against 6 rubric dimensions, detects overlap, checks governance alignment. Agent-generated text is scored at ${0.6}x weight to prevent keyword inflation (RH1).
|
|
2352
|
+
**You coach** \u2014 you interpret the structured response, synthesize naturally, investigate proactively, propose grounded solutions, and push back when shaping isn't sharp.
|
|
2353
|
+
The \`coaching.suggestedQuestion\` is a suggestion, not a script \u2014 rephrase or skip based on conversation flow.
|
|
2354
|
+
|
|
2355
|
+
## Error Recovery
|
|
2356
|
+
|
|
2357
|
+
If any facilitate call fails:
|
|
2358
|
+
1. Tell the user what you were trying to do
|
|
2359
|
+
2. Explain what went wrong (one sentence)
|
|
2360
|
+
3. Continue shaping \u2014 the conversation IS the backup
|
|
2361
|
+
4. Suggest retrying or capturing manually later
|
|
2362
|
+
|
|
2363
|
+
## Communication Style
|
|
2364
|
+
|
|
2365
|
+
- Start each round with its phase header
|
|
2366
|
+
- Keep chat messages to 5 lines or fewer \u2014 heavy context degrades performance
|
|
2367
|
+
- Use AskQuestion for structured choices (appetite, gates)
|
|
2368
|
+
- Synthesize between phases \u2014 reflect what was decided before moving on
|
|
2369
|
+
- When proposing, cite evidence: "I found X in the codebase" or "The Chain has DEC-Y about this"
|
|
2370
|
+
- If the user seems stuck, investigate and propose rather than just asking`,
|
|
2371
|
+
rounds: [
|
|
2372
|
+
{
|
|
2373
|
+
id: "context",
|
|
2374
|
+
num: "01",
|
|
2375
|
+
label: "Gather Context",
|
|
2376
|
+
type: "open",
|
|
2377
|
+
instruction: "Before shaping, let's see what the Chain already knows. Search for related entries, business rules, and existing decisions.",
|
|
2378
|
+
facilitatorGuidance: "**Investigate proactively.** Spawn sub-agents in parallel: (1) `explore` agent to search the codebase for code related to the bet topic \u2014 find modules, patterns, and pain points. (2) `generalPurpose` agent to search the Chain: call `orient`, use `entries action=search` with keywords, surface related tensions, active bets, and applicable business rules. Synthesize both into a 3-5 line context brief: what the codebase reveals, what the Chain knows, what governance applies. Present with IDs and file paths. Then transition: 'Here's what I found. Does this match the problem you're seeing?'",
|
|
2379
|
+
outputSchema: {
|
|
2380
|
+
field: "context",
|
|
2381
|
+
description: "Chain context: related entries, governance, existing decisions",
|
|
2382
|
+
format: "structured"
|
|
2383
|
+
},
|
|
2384
|
+
maxDurationHint: "2 min"
|
|
2385
|
+
},
|
|
2386
|
+
{
|
|
2387
|
+
id: "framing",
|
|
2388
|
+
num: "02",
|
|
2389
|
+
label: "Frame the Problem",
|
|
2390
|
+
type: "open",
|
|
2391
|
+
instruction: "What's the problem? Who's affected? What's the workaround today? How much time is this worth?",
|
|
2392
|
+
facilitatorGuidance: "This round covers problem_clarity AND appetite. If the investigationBrief has framing tasks, search the codebase for workarounds, TODOs, and hacks related to the problem \u2014 cite what you find as evidence. Call `facilitate action=respond` with dimension='problem_clarity' for the problem input, then dimension='appetite' for the time budget. Push with evidence: 'I found 3 workaround patterns in the codebase \u2014 this suggests the problem is bigger than it looks.' Use AskQuestion for appetite: Small Batch (1-2 weeks) or Big Batch (6 weeks). When both dimensions score 6+, present the frame and ask for Frame Go confirmation. If new terms surfaced, capture them to the glossary via `capture`. If tensions surfaced, capture them too.",
|
|
2393
|
+
questions: [
|
|
2394
|
+
{
|
|
2395
|
+
id: "appetite",
|
|
2396
|
+
prompt: "How much time is this idea worth? Not how long it'll take \u2014 how much are you willing to invest?",
|
|
2397
|
+
options: [
|
|
2398
|
+
{ id: "small", label: "Small Batch \u2014 1-2 weeks, focused scope" },
|
|
2399
|
+
{ id: "big", label: "Big Batch \u2014 6 weeks, full treatment" }
|
|
2400
|
+
]
|
|
2401
|
+
},
|
|
2402
|
+
{
|
|
2403
|
+
id: "frame-go",
|
|
2404
|
+
prompt: "Frame Go \u2014 the problem is tight enough to shape a solution. Proceed to elements?",
|
|
2405
|
+
options: [
|
|
2406
|
+
{ id: "go", label: "Frame Go \u2014 move to elements" },
|
|
2407
|
+
{ id: "refine", label: "Refine the frame \u2014 something's not right" }
|
|
2408
|
+
]
|
|
2409
|
+
}
|
|
2410
|
+
],
|
|
2411
|
+
outputSchema: {
|
|
2412
|
+
field: "framing",
|
|
2413
|
+
description: "Confirmed problem statement and appetite",
|
|
2414
|
+
format: "structured"
|
|
2415
|
+
},
|
|
2416
|
+
kbCollection: "bets",
|
|
2417
|
+
maxDurationHint: "10 min"
|
|
2418
|
+
},
|
|
2419
|
+
{
|
|
2420
|
+
id: "elements",
|
|
2421
|
+
num: "03",
|
|
2422
|
+
label: "Find the Elements",
|
|
2423
|
+
type: "open",
|
|
2424
|
+
instruction: "What are the solution elements? Think breadboard level \u2014 places, affordances, connections. What's the architecture?",
|
|
2425
|
+
facilitatorGuidance: "**Investigate first.** Read the investigationBrief. Spawn sub-agents to search the codebase for affected modules, existing architecture patterns, and API boundaries. Synthesize findings into 3-5 proposed solution elements at breadboard level, citing specific files and modules. Present proposals for user reaction: 'Based on the codebase, here are the elements I'd suggest.' For each confirmed element, capture with its own respond call: `facilitate action=respond betEntryId='...' dimension='elements' source='user_reaction' userInput='...' capture={type:'element', name:'Element Name', description:'What it does'}`. Without the `capture` parameter, the element is NOT saved to the bet \u2014 you will get a WARNING if you forget. Each captured element becomes a features entry linked to the bet via part_of. For Big Batch: also investigate architecture \u2014 search for layer boundaries and dependency directions, then propose architecture. Call respond with dimension='architecture' for architecture input (no capture needed \u2014 free-form text). Check business rules: do any elements conflict with governance surfaced in Round 1? Capture new glossary terms and decisions as they surface. Aim for 3-5 elements. When elements score 6+ (and architecture for Big Batch), present for confirmation.",
|
|
2426
|
+
outputSchema: {
|
|
2427
|
+
field: "elements",
|
|
2428
|
+
description: "Core solution elements and architecture",
|
|
2429
|
+
format: "structured"
|
|
2430
|
+
},
|
|
2431
|
+
kbCollection: "features",
|
|
2432
|
+
maxDurationHint: "15 min"
|
|
2433
|
+
},
|
|
2434
|
+
{
|
|
2435
|
+
id: "derisking",
|
|
2436
|
+
num: "04",
|
|
2437
|
+
label: "De-risk \u2014 Rabbit Holes & No-Gos",
|
|
2438
|
+
type: "open",
|
|
2439
|
+
instruction: "What could go wrong? What are we NOT building? Walk through the solution slowly and find the risks.",
|
|
2440
|
+
facilitatorGuidance: "**Investigate first.** Read the investigationBrief. Spawn sub-agents to search the codebase for fragile code, tight coupling, missing tests, performance-sensitive paths, and external dependencies near the affected modules. Propose risks with evidence: 'I found that X module has no tests and is tightly coupled to Y \u2014 this is a rabbit hole.' Every confirmed risk MUST be captured with its own respond call: `facilitate action=respond betEntryId='...' dimension='risks' source='user_reaction' userInput='...' capture={type:'risk', name:'Risk Name', description:'What could go wrong', theme:'implementation'}`. Each becomes a tensions entry linked to the bet via constrains. Push for mitigations \u2014 every risk needs a patch or explicit acceptance. Then declare no-gos: 'Based on what I've seen, I'd suggest these no-gos.' Every no-go MUST use capture: `facilitate action=respond betEntryId='...' dimension='boundaries' source='user_reaction' userInput='...' capture={type:'noGo', name:'No-Go Title', description:'Why we are not doing this'}`. Without the `capture` parameter on each call, the item is NOT saved \u2014 you will get a WARNING. When risks score 6+ and boundaries score 4+, present Shape Go gate: 'Is this buildable within the appetite?'",
|
|
2441
|
+
questions: [
|
|
2442
|
+
{
|
|
2443
|
+
id: "shape-go",
|
|
2444
|
+
prompt: "Shape Go \u2014 the solution fits the appetite and risks are managed. Proceed to validation?",
|
|
2445
|
+
options: [
|
|
2446
|
+
{ id: "go", label: "Shape Go \u2014 move to validation" },
|
|
2447
|
+
{ id: "cut", label: "Cut scope \u2014 this grew beyond appetite" },
|
|
2448
|
+
{ id: "refine", label: "Refine \u2014 more de-risking needed" }
|
|
2449
|
+
]
|
|
2450
|
+
}
|
|
2451
|
+
],
|
|
2452
|
+
outputSchema: {
|
|
2453
|
+
field: "derisking",
|
|
2454
|
+
description: "Rabbit holes with patches, explicit no-gos",
|
|
2455
|
+
format: "structured"
|
|
2456
|
+
},
|
|
2457
|
+
kbCollection: "tensions",
|
|
2458
|
+
maxDurationHint: "10 min"
|
|
2459
|
+
},
|
|
2460
|
+
{
|
|
2461
|
+
id: "validation",
|
|
2462
|
+
num: "05",
|
|
2463
|
+
label: "Validate & Build Contract",
|
|
2464
|
+
type: "synthesis",
|
|
2465
|
+
instruction: "Check completeness. Generate the build contract. Review the full scorecard.",
|
|
2466
|
+
facilitatorGuidance: "Call `facilitate action=score` to get the full scorecard. Present it as a table. Check: does the response include a buildContract? If captureReady is true, it should. Verify the 5 core ingredients: Problem, Appetite, Solution elements, Rabbit Holes, No-Gos. For Big Batch: also verify Architecture, narrative hook, and value-flow annotations. Present the build contract. Ask: 'Anything to adjust before we capture to the Chain?'",
|
|
2467
|
+
outputSchema: {
|
|
2468
|
+
field: "validation",
|
|
2469
|
+
description: "Completeness check result and build contract",
|
|
2470
|
+
format: "structured"
|
|
2471
|
+
},
|
|
2472
|
+
maxDurationHint: "5 min"
|
|
2473
|
+
},
|
|
2474
|
+
{
|
|
2475
|
+
id: "capture",
|
|
2476
|
+
num: "06",
|
|
2477
|
+
label: "Capture & Commit",
|
|
2478
|
+
type: "close",
|
|
2479
|
+
instruction: "Capture the pitch to the Chain. Commit draft entries. Connect the constellation.",
|
|
2480
|
+
facilitatorGuidance: "List all draft entries created during shaping (elements, risks, decisions, glossary terms). Ask the user to confirm: 'Ready to commit these to the Chain?' For each confirmed entry: `commit-entry entryId='...'`. Then `graph action=suggest entryId='<betId>'` to discover connections. Create the top 3 suggested relations. Update the bet status from 'shaping' to 'shaped'. Thank the user and summarize: how many entries captured, relations created, Chain enrichment.",
|
|
2481
|
+
outputSchema: {
|
|
2482
|
+
field: "capture",
|
|
2483
|
+
description: "Final capture summary \u2014 entries committed, relations created",
|
|
2484
|
+
format: "structured"
|
|
2485
|
+
},
|
|
2486
|
+
kbCollection: "bets",
|
|
2487
|
+
maxDurationHint: "5 min"
|
|
2488
|
+
}
|
|
2489
|
+
],
|
|
2490
|
+
kbOutputCollection: "bets",
|
|
2491
|
+
kbOutputTemplate: {
|
|
2492
|
+
nameTemplate: "{betName}",
|
|
2493
|
+
descriptionField: "problem"
|
|
2494
|
+
},
|
|
2495
|
+
errorRecovery: `If anything goes wrong during shaping:
|
|
2496
|
+
|
|
2497
|
+
1. **facilitate tool failure**: Continue the conversation. The shaping knowledge is in the chat. Suggest retrying the tool call or capturing manually later.
|
|
2498
|
+
2. **Chain capture failure (duplicate)**: The tool handles duplicates by linking existing entries. If it still fails, note the entry name and move on \u2014 capture manually after the session.
|
|
2499
|
+
3. **Score seems wrong**: Call \`facilitate action=score\` to get a clean read. Scores are heuristic \u2014 the agent's judgment matters more than the number.
|
|
2500
|
+
4. **User wants to stop mid-session**: Respect it. Everything captured so far is on the Chain as drafts. Use \`facilitate action=resume\` to pick up later.
|
|
2501
|
+
5. **MCP server unreachable**: Run the shaping from conversation knowledge. Skip chain capture. Say: "Product Brain is temporarily unavailable \u2014 I'll shape from our conversation and we can commit afterward."
|
|
2502
|
+
|
|
2503
|
+
The shaping must never fail silently. Always communicate state.`
|
|
2504
|
+
};
|
|
2314
2505
|
var WORKFLOWS = /* @__PURE__ */ new Map([
|
|
2315
|
-
["retro", RETRO_WORKFLOW]
|
|
2506
|
+
["retro", RETRO_WORKFLOW],
|
|
2507
|
+
["shape", SHAPE_WORKFLOW]
|
|
2316
2508
|
]);
|
|
2317
2509
|
function getWorkflow(id) {
|
|
2318
2510
|
return WORKFLOWS.get(id);
|
|
@@ -2498,10 +2690,1567 @@ This checkpoint was NOT saved. The conversation context is preserved \u2014 cont
|
|
|
2498
2690
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
2499
2691
|
}
|
|
2500
2692
|
|
|
2693
|
+
// src/tools/facilitate.ts
|
|
2694
|
+
import { z as z12 } from "zod";
|
|
2695
|
+
|
|
2696
|
+
// src/tools/facilitate-rubrics.ts
|
|
2697
|
+
var AGENT_DISCOUNT_FACTOR = 0.6;
|
|
2698
|
+
var DIMENSIONS = [
|
|
2699
|
+
"problem_clarity",
|
|
2700
|
+
"appetite",
|
|
2701
|
+
"elements",
|
|
2702
|
+
"architecture",
|
|
2703
|
+
"risks",
|
|
2704
|
+
"boundaries"
|
|
2705
|
+
];
|
|
2706
|
+
var DIMENSION_LABELS = {
|
|
2707
|
+
problem_clarity: "Problem Clarity",
|
|
2708
|
+
appetite: "Appetite Definition",
|
|
2709
|
+
elements: "Element Decomposition",
|
|
2710
|
+
architecture: "Architecture Grounding",
|
|
2711
|
+
risks: "Risk Coverage",
|
|
2712
|
+
boundaries: "Boundary Specification"
|
|
2713
|
+
};
|
|
2714
|
+
var SUGGESTED_ORDER = [
|
|
2715
|
+
"problem_clarity",
|
|
2716
|
+
"appetite",
|
|
2717
|
+
"elements",
|
|
2718
|
+
"architecture",
|
|
2719
|
+
"risks",
|
|
2720
|
+
"boundaries"
|
|
2721
|
+
];
|
|
2722
|
+
var PHASE_LABELS = {
|
|
2723
|
+
context: "Phase 0: Gather Context",
|
|
2724
|
+
framing: "Phase 1: Frame the Problem",
|
|
2725
|
+
elements: "Phase 2: Find the Elements",
|
|
2726
|
+
derisking: "Phase 3: De-risk",
|
|
2727
|
+
validation: "Phase 4: Validate & Contract",
|
|
2728
|
+
capture: "Phase 5: Capture & Commit"
|
|
2729
|
+
};
|
|
2730
|
+
function inferPhase(scorecard, isSmallBatch = false) {
|
|
2731
|
+
if (scorecard.problem_clarity === 0 && scorecard.appetite === 0) return "context";
|
|
2732
|
+
if (scorecard.problem_clarity < 6 || scorecard.appetite < 6) return "framing";
|
|
2733
|
+
const archGate = isSmallBatch ? true : scorecard.architecture >= 4;
|
|
2734
|
+
if (scorecard.elements < 6 || !archGate) return "elements";
|
|
2735
|
+
if (scorecard.risks < 6 || scorecard.boundaries < 4) return "derisking";
|
|
2736
|
+
const activeDims = activeDimensions(isSmallBatch);
|
|
2737
|
+
const allAboveThreshold = activeDims.every((d) => scorecard[d] >= 4);
|
|
2738
|
+
if (!allAboveThreshold) return "derisking";
|
|
2739
|
+
const highQuality = activeDims.every((d) => scorecard[d] >= 6);
|
|
2740
|
+
if (!highQuality) return "validation";
|
|
2741
|
+
return "capture";
|
|
2742
|
+
}
|
|
2743
|
+
function activeDimensions(isSmallBatch) {
|
|
2744
|
+
return isSmallBatch ? SUGGESTED_ORDER.filter((d) => d !== "architecture") : SUGGESTED_ORDER;
|
|
2745
|
+
}
|
|
2746
|
+
var WORKAROUND_SIGNALS = [
|
|
2747
|
+
"workaround",
|
|
2748
|
+
"currently",
|
|
2749
|
+
"today",
|
|
2750
|
+
"right now",
|
|
2751
|
+
"manually",
|
|
2752
|
+
"hack",
|
|
2753
|
+
"instead",
|
|
2754
|
+
"rather than",
|
|
2755
|
+
"in practice",
|
|
2756
|
+
"as-is"
|
|
2757
|
+
];
|
|
2758
|
+
var AFFECTED_SIGNALS = [
|
|
2759
|
+
"user",
|
|
2760
|
+
"team",
|
|
2761
|
+
"developer",
|
|
2762
|
+
"engineer",
|
|
2763
|
+
"customer",
|
|
2764
|
+
"everyone",
|
|
2765
|
+
"nobody",
|
|
2766
|
+
"people",
|
|
2767
|
+
"stakeholder",
|
|
2768
|
+
"builder"
|
|
2769
|
+
];
|
|
2770
|
+
var FREQUENCY_SIGNALS = [
|
|
2771
|
+
"every time",
|
|
2772
|
+
"always",
|
|
2773
|
+
"often",
|
|
2774
|
+
"rarely",
|
|
2775
|
+
"daily",
|
|
2776
|
+
"weekly",
|
|
2777
|
+
"never",
|
|
2778
|
+
"sometimes",
|
|
2779
|
+
"constantly",
|
|
2780
|
+
"repeatedly",
|
|
2781
|
+
"each session",
|
|
2782
|
+
"per session"
|
|
2783
|
+
];
|
|
2784
|
+
var APPETITE_SIGNALS = [
|
|
2785
|
+
"week",
|
|
2786
|
+
"weeks",
|
|
2787
|
+
"day",
|
|
2788
|
+
"days",
|
|
2789
|
+
"month",
|
|
2790
|
+
"sprint",
|
|
2791
|
+
"small batch",
|
|
2792
|
+
"big batch",
|
|
2793
|
+
"appetite",
|
|
2794
|
+
"time box",
|
|
2795
|
+
"timebox",
|
|
2796
|
+
"budget",
|
|
2797
|
+
"phased"
|
|
2798
|
+
];
|
|
2799
|
+
var SCOPE_SIGNALS = [
|
|
2800
|
+
"trade-off",
|
|
2801
|
+
"tradeoff",
|
|
2802
|
+
"trade off",
|
|
2803
|
+
"not including",
|
|
2804
|
+
"out of scope",
|
|
2805
|
+
"defer",
|
|
2806
|
+
"skip",
|
|
2807
|
+
"cut",
|
|
2808
|
+
"80%",
|
|
2809
|
+
"good enough",
|
|
2810
|
+
"mvp",
|
|
2811
|
+
"v1",
|
|
2812
|
+
"phase"
|
|
2813
|
+
];
|
|
2814
|
+
var RISK_SIGNALS = [
|
|
2815
|
+
"risk",
|
|
2816
|
+
"rabbit hole",
|
|
2817
|
+
"unknown",
|
|
2818
|
+
"complexity",
|
|
2819
|
+
"might",
|
|
2820
|
+
"could fail",
|
|
2821
|
+
"latency",
|
|
2822
|
+
"performance",
|
|
2823
|
+
"coupling",
|
|
2824
|
+
"dependency",
|
|
2825
|
+
"fragile",
|
|
2826
|
+
"brittle",
|
|
2827
|
+
"unclear",
|
|
2828
|
+
"uncertain",
|
|
2829
|
+
"tricky"
|
|
2830
|
+
];
|
|
2831
|
+
var MITIGATION_SIGNALS = [
|
|
2832
|
+
"patch",
|
|
2833
|
+
"mitigate",
|
|
2834
|
+
"accept",
|
|
2835
|
+
"workaround",
|
|
2836
|
+
"fallback",
|
|
2837
|
+
"circuit breaker",
|
|
2838
|
+
"degrade",
|
|
2839
|
+
"graceful",
|
|
2840
|
+
"monitor",
|
|
2841
|
+
"benchmark"
|
|
2842
|
+
];
|
|
2843
|
+
var NOGO_SIGNALS = [
|
|
2844
|
+
"no-go",
|
|
2845
|
+
"nogo",
|
|
2846
|
+
"won't",
|
|
2847
|
+
"will not",
|
|
2848
|
+
"must not",
|
|
2849
|
+
"not building",
|
|
2850
|
+
"out of scope",
|
|
2851
|
+
"explicitly excluded",
|
|
2852
|
+
"defer",
|
|
2853
|
+
"not in v1",
|
|
2854
|
+
"not in this bet"
|
|
2855
|
+
];
|
|
2856
|
+
var ARCHITECTURE_SIGNALS = [
|
|
2857
|
+
"layer",
|
|
2858
|
+
"boundary",
|
|
2859
|
+
"dependency",
|
|
2860
|
+
"api",
|
|
2861
|
+
"contract",
|
|
2862
|
+
"interface",
|
|
2863
|
+
"module",
|
|
2864
|
+
"component",
|
|
2865
|
+
"service",
|
|
2866
|
+
"schema",
|
|
2867
|
+
"mermaid",
|
|
2868
|
+
"diagram",
|
|
2869
|
+
"infra",
|
|
2870
|
+
"core",
|
|
2871
|
+
"feature"
|
|
2872
|
+
];
|
|
2873
|
+
var ARCH_QUALITY_SIGNALS = [
|
|
2874
|
+
"direction",
|
|
2875
|
+
"imports",
|
|
2876
|
+
"never imports",
|
|
2877
|
+
"http only",
|
|
2878
|
+
"separation",
|
|
2879
|
+
"encapsulat",
|
|
2880
|
+
"decouple",
|
|
2881
|
+
"isolat"
|
|
2882
|
+
];
|
|
2883
|
+
function countMatches(text, signals) {
|
|
2884
|
+
const lower = text.toLowerCase();
|
|
2885
|
+
return signals.filter((s) => lower.includes(s)).length;
|
|
2886
|
+
}
|
|
2887
|
+
function clamp(n, min, max) {
|
|
2888
|
+
return Math.max(min, Math.min(max, n));
|
|
2889
|
+
}
|
|
2890
|
+
function scoreProblemClarity(ctx) {
|
|
2891
|
+
const text = ctx.dimensionTexts.problem_clarity;
|
|
2892
|
+
const missing = [];
|
|
2893
|
+
const satisfied = [];
|
|
2894
|
+
let score = 0;
|
|
2895
|
+
if (countMatches(text, WORKAROUND_SIGNALS) > 0) {
|
|
2896
|
+
satisfied.push("Current workaround described");
|
|
2897
|
+
score += 3;
|
|
2898
|
+
} else {
|
|
2899
|
+
missing.push("Describe the current workaround \u2014 how do people deal with this today?");
|
|
2900
|
+
}
|
|
2901
|
+
if (countMatches(text, AFFECTED_SIGNALS) > 0) {
|
|
2902
|
+
satisfied.push("Affected people identified");
|
|
2903
|
+
score += 2;
|
|
2904
|
+
} else {
|
|
2905
|
+
missing.push("Who experiences this problem? Be specific about the role or persona.");
|
|
2906
|
+
}
|
|
2907
|
+
if (countMatches(text, FREQUENCY_SIGNALS) > 0) {
|
|
2908
|
+
satisfied.push("Frequency or severity stated");
|
|
2909
|
+
score += 2;
|
|
2910
|
+
} else {
|
|
2911
|
+
missing.push("How often does this happen, or how severe is it when it does?");
|
|
2912
|
+
}
|
|
2913
|
+
if (ctx.existingEntryIds.length > 0) {
|
|
2914
|
+
satisfied.push(`Differentiated from ${ctx.existingEntryIds.length} existing entries`);
|
|
2915
|
+
score += 2;
|
|
2916
|
+
} else if (/\b(TEN|DEC|ENT|FEAT|BET)-\w+/i.test(text)) {
|
|
2917
|
+
satisfied.push("References existing Chain entries");
|
|
2918
|
+
score += 1;
|
|
2919
|
+
} else {
|
|
2920
|
+
missing.push("How does this differ from existing tensions or bets on the Chain?");
|
|
2921
|
+
}
|
|
2922
|
+
if (text.length > 200) {
|
|
2923
|
+
satisfied.push("Substantive description provided");
|
|
2924
|
+
score += 1;
|
|
2925
|
+
}
|
|
2926
|
+
return { score: clamp(score, 0, 10), missing, satisfied };
|
|
2927
|
+
}
|
|
2928
|
+
function scoreAppetite(ctx) {
|
|
2929
|
+
const text = ctx.dimensionTexts.appetite;
|
|
2930
|
+
const missing = [];
|
|
2931
|
+
const satisfied = [];
|
|
2932
|
+
let score = 0;
|
|
2933
|
+
const hasSizeDecl = /small\s*batch|big\s*batch|[1-6]\s*week/i.test(text);
|
|
2934
|
+
const hasTimeSignals = countMatches(text, APPETITE_SIGNALS) > 0;
|
|
2935
|
+
if (hasSizeDecl) {
|
|
2936
|
+
satisfied.push("Size and timeframe declared");
|
|
2937
|
+
score += 4;
|
|
2938
|
+
} else if (hasTimeSignals) {
|
|
2939
|
+
satisfied.push("Time constraint mentioned");
|
|
2940
|
+
score += 2;
|
|
2941
|
+
missing.push("Declare the batch size explicitly \u2014 Small Batch (1-2 weeks) or Big Batch (6 weeks).");
|
|
2942
|
+
} else {
|
|
2943
|
+
missing.push("Set a time constraint \u2014 how long is this bet allowed to take?");
|
|
2944
|
+
}
|
|
2945
|
+
if (countMatches(text, SCOPE_SIGNALS) > 0) {
|
|
2946
|
+
satisfied.push("Scope bounded with trade-offs");
|
|
2947
|
+
score += 2;
|
|
2948
|
+
} else {
|
|
2949
|
+
missing.push("What trade-offs are you making? What's the 80% cut?");
|
|
2950
|
+
}
|
|
2951
|
+
if (/phase\s*[a-c1-3]|phased|milestone/i.test(text)) {
|
|
2952
|
+
satisfied.push("Phased delivery strategy");
|
|
2953
|
+
score += 2;
|
|
2954
|
+
}
|
|
2955
|
+
if (/gate|validate|dogfood|benchmark|circuit.?breaker/i.test(text)) {
|
|
2956
|
+
satisfied.push("Validation gate defined");
|
|
2957
|
+
score += 2;
|
|
2958
|
+
} else {
|
|
2959
|
+
missing.push("How will you know if this bet is working before full commitment?");
|
|
2960
|
+
}
|
|
2961
|
+
return { score: clamp(score, 0, 10), missing, satisfied };
|
|
2962
|
+
}
|
|
2963
|
+
function scoreElements(ctx) {
|
|
2964
|
+
const text = ctx.dimensionTexts.elements;
|
|
2965
|
+
const missing = [];
|
|
2966
|
+
const satisfied = [];
|
|
2967
|
+
let score = 0;
|
|
2968
|
+
const headingCount = (text.match(/###\s*Element\s*\d/gi) ?? []).length;
|
|
2969
|
+
const inlineCount = (text.match(/element\s*\d|component\s*\d|piece\s*\d/gi) ?? []).length;
|
|
2970
|
+
const ordinalCount = (text.match(/\b(first|second|third|fourth|fifth)\b[,:]?\s/gi) ?? []).length;
|
|
2971
|
+
const numberedCount = (text.match(/^\s*\d+[.)]\s/gm) ?? []).length;
|
|
2972
|
+
const namedCount = (text.match(/\belement\s+\d|[A-Z][a-z]+ (?:Engine|Store|Gate|Manager|Service|Module|Handler|Layer|Registry|Pipeline)\b/g) ?? []).length;
|
|
2973
|
+
const textDescribed = Math.max(headingCount, inlineCount, ordinalCount, numberedCount, namedCount);
|
|
2974
|
+
if (ctx.elementCount >= 3) {
|
|
2975
|
+
satisfied.push(`${ctx.elementCount} elements captured on Chain`);
|
|
2976
|
+
score += 5;
|
|
2977
|
+
} else if (ctx.elementCount > 0) {
|
|
2978
|
+
satisfied.push(`${ctx.elementCount} element(s) captured`);
|
|
2979
|
+
score += ctx.elementCount * 2;
|
|
2980
|
+
if (ctx.elementCount < 3) {
|
|
2981
|
+
missing.push(`Identify ${3 - ctx.elementCount} more solution elements for adequate decomposition.`);
|
|
2982
|
+
}
|
|
2983
|
+
} else if (textDescribed >= 3) {
|
|
2984
|
+
satisfied.push(`${textDescribed} elements described in text`);
|
|
2985
|
+
score += 4;
|
|
2986
|
+
missing.push("Capture these elements as typed entries on the Chain.");
|
|
2987
|
+
} else if (textDescribed > 0) {
|
|
2988
|
+
satisfied.push(`${textDescribed} element(s) described in text`);
|
|
2989
|
+
score += textDescribed * 2;
|
|
2990
|
+
missing.push("Identify more solution elements \u2014 aim for 3+ independently describable pieces.");
|
|
2991
|
+
} else {
|
|
2992
|
+
missing.push("Identify solution elements \u2014 breadboard-level pieces, each independently describable.");
|
|
2993
|
+
}
|
|
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
|
+
const totalElements = Math.max(ctx.elementCount, textDescribed);
|
|
3004
|
+
if (totalElements >= 2) {
|
|
3005
|
+
satisfied.push("Multiple independently describable pieces");
|
|
3006
|
+
score += 2;
|
|
3007
|
+
}
|
|
3008
|
+
if (ctx.governanceCount > 0) {
|
|
3009
|
+
satisfied.push(`${ctx.governanceCount} governance entries constrain the solution`);
|
|
3010
|
+
score += 1;
|
|
3011
|
+
}
|
|
3012
|
+
return { score: clamp(score, 0, 10), missing, satisfied };
|
|
3013
|
+
}
|
|
3014
|
+
function scoreArchitecture(ctx) {
|
|
3015
|
+
const text = ctx.dimensionTexts.architecture;
|
|
3016
|
+
const missing = [];
|
|
3017
|
+
const satisfied = [];
|
|
3018
|
+
let score = 0;
|
|
3019
|
+
if (ctx.hasArchitectureText) {
|
|
3020
|
+
satisfied.push("Architecture section present in bet");
|
|
3021
|
+
score += 3;
|
|
3022
|
+
} else {
|
|
3023
|
+
missing.push("Where does this live in the architecture? Name each layer and what belongs where.");
|
|
3024
|
+
}
|
|
3025
|
+
const archSignals = countMatches(text, ARCHITECTURE_SIGNALS);
|
|
3026
|
+
if (archSignals >= 3) {
|
|
3027
|
+
satisfied.push("Architecture vocabulary used");
|
|
3028
|
+
score += 2;
|
|
3029
|
+
} else if (archSignals > 0) {
|
|
3030
|
+
score += 1;
|
|
3031
|
+
} else {
|
|
3032
|
+
missing.push("Describe the API boundary, modules, and services involved.");
|
|
3033
|
+
}
|
|
3034
|
+
if (countMatches(text, ARCH_QUALITY_SIGNALS) > 0) {
|
|
3035
|
+
satisfied.push("Dependency direction / boundaries specified");
|
|
3036
|
+
score += 2;
|
|
3037
|
+
} else {
|
|
3038
|
+
missing.push("What dependencies does this create or remove? Specify direction.");
|
|
3039
|
+
}
|
|
3040
|
+
if (/mermaid|diagram|flowchart|graph/i.test(text)) {
|
|
3041
|
+
satisfied.push("Visual architecture representation");
|
|
3042
|
+
score += 2;
|
|
3043
|
+
} else {
|
|
3044
|
+
missing.push("Add an architecture diagram (Mermaid) showing layers and data flow.");
|
|
3045
|
+
}
|
|
3046
|
+
if (ctx.governanceCount > 0) {
|
|
3047
|
+
satisfied.push("Architecture checked against governance");
|
|
3048
|
+
score += 1;
|
|
3049
|
+
}
|
|
3050
|
+
return { score: clamp(score, 0, 10), missing, satisfied };
|
|
3051
|
+
}
|
|
3052
|
+
function scoreRisks(ctx) {
|
|
3053
|
+
const text = ctx.dimensionTexts.risks;
|
|
3054
|
+
const missing = [];
|
|
3055
|
+
const satisfied = [];
|
|
3056
|
+
let score = 0;
|
|
3057
|
+
const riskSignalCount = countMatches(text, RISK_SIGNALS);
|
|
3058
|
+
const textRiskCount = (text.match(/rabbit hole|risk:|risk \d|R\d:/gi) ?? []).length;
|
|
3059
|
+
const totalRisks = Math.max(ctx.riskCount, textRiskCount, Math.min(riskSignalCount, 3));
|
|
3060
|
+
if (ctx.riskCount >= 2) {
|
|
3061
|
+
satisfied.push(`${ctx.riskCount} risks captured on Chain`);
|
|
3062
|
+
score += 4;
|
|
3063
|
+
} else if (totalRisks >= 2) {
|
|
3064
|
+
satisfied.push(`${totalRisks} risks identified`);
|
|
3065
|
+
score += 3;
|
|
3066
|
+
if (ctx.riskCount === 0) missing.push("Capture identified risks as entries on the Chain.");
|
|
3067
|
+
} else if (totalRisks > 0 || riskSignalCount > 0) {
|
|
3068
|
+
satisfied.push("Risk language present");
|
|
3069
|
+
score += 2;
|
|
3070
|
+
missing.push("Identify more risks \u2014 aim for 2+ rabbit holes with mitigations.");
|
|
3071
|
+
} else {
|
|
3072
|
+
missing.push("Name the rabbit holes \u2014 what could go wrong, what's unknown?");
|
|
3073
|
+
}
|
|
3074
|
+
const mitigationCount = countMatches(text, MITIGATION_SIGNALS);
|
|
3075
|
+
if (mitigationCount >= 2) {
|
|
3076
|
+
satisfied.push("Multiple mitigations specified");
|
|
3077
|
+
score += 3;
|
|
3078
|
+
} else if (mitigationCount > 0) {
|
|
3079
|
+
satisfied.push("Mitigation present");
|
|
3080
|
+
score += 2;
|
|
3081
|
+
missing.push("Each risk should have a mitigation or explicit acceptance.");
|
|
3082
|
+
} else {
|
|
3083
|
+
missing.push("Each risk needs a mitigation or explicit acceptance.");
|
|
3084
|
+
}
|
|
3085
|
+
if (/codebase|architecture|coupling|dependency|schema|migration/i.test(text)) {
|
|
3086
|
+
satisfied.push("Codebase-level risks identified");
|
|
3087
|
+
score += 2;
|
|
3088
|
+
} else {
|
|
3089
|
+
missing.push("Are there high-risk areas in the codebase? Check architecture and dependencies.");
|
|
3090
|
+
}
|
|
3091
|
+
if (totalRisks >= 3) {
|
|
3092
|
+
satisfied.push("Thorough risk coverage");
|
|
3093
|
+
score += 1;
|
|
3094
|
+
}
|
|
3095
|
+
return { score: clamp(score, 0, 10), missing, satisfied };
|
|
3096
|
+
}
|
|
3097
|
+
function scoreBoundaries(ctx) {
|
|
3098
|
+
const text = ctx.dimensionTexts.boundaries;
|
|
3099
|
+
const missing = [];
|
|
3100
|
+
const satisfied = [];
|
|
3101
|
+
let score = 0;
|
|
3102
|
+
const noGoSignals = countMatches(text, NOGO_SIGNALS);
|
|
3103
|
+
const wontStatements = (text.match(/\bwon'?t\b|\bwill not\b/gi) ?? []).length;
|
|
3104
|
+
const totalNoGos = Math.max(ctx.noGoCount, noGoSignals, wontStatements);
|
|
3105
|
+
if (totalNoGos >= 3) {
|
|
3106
|
+
satisfied.push(`${totalNoGos} explicit no-gos declared`);
|
|
3107
|
+
score += 5;
|
|
3108
|
+
} else if (totalNoGos > 0) {
|
|
3109
|
+
satisfied.push(`${totalNoGos} no-go(s) declared`);
|
|
3110
|
+
score += totalNoGos * 2;
|
|
3111
|
+
missing.push("Add more explicit no-gos to prevent scope creep.");
|
|
3112
|
+
} else {
|
|
3113
|
+
missing.push("Declare what you're NOT building \u2014 explicit no-gos prevent scope creep.");
|
|
3114
|
+
}
|
|
3115
|
+
if (/scope creep|prevent|boundary|limit|constrain|excluded/i.test(text)) {
|
|
3116
|
+
satisfied.push("Scope creep prevention language");
|
|
3117
|
+
score += 2;
|
|
3118
|
+
}
|
|
3119
|
+
if (/direction|instead.*will|rather.*than|not.*but/i.test(text)) {
|
|
3120
|
+
satisfied.push("Each no-go prevents scope creep in a specific direction");
|
|
3121
|
+
score += 2;
|
|
3122
|
+
} else if (totalNoGos > 0) {
|
|
3123
|
+
missing.push("Each no-go should prevent scope creep in a specific direction.");
|
|
3124
|
+
}
|
|
3125
|
+
if (totalNoGos >= 5) {
|
|
3126
|
+
satisfied.push("Comprehensive boundary specification");
|
|
3127
|
+
score += 1;
|
|
3128
|
+
}
|
|
3129
|
+
return { score: clamp(score, 0, 10), missing, satisfied };
|
|
3130
|
+
}
|
|
3131
|
+
var SCORERS = {
|
|
3132
|
+
problem_clarity: scoreProblemClarity,
|
|
3133
|
+
appetite: scoreAppetite,
|
|
3134
|
+
elements: scoreElements,
|
|
3135
|
+
architecture: scoreArchitecture,
|
|
3136
|
+
risks: scoreRisks,
|
|
3137
|
+
boundaries: scoreBoundaries
|
|
3138
|
+
};
|
|
3139
|
+
function scoreDimension(dimension, ctx) {
|
|
3140
|
+
const raw = SCORERS[dimension](ctx);
|
|
3141
|
+
if (ctx.source === "agent_proposal") {
|
|
3142
|
+
return {
|
|
3143
|
+
...raw,
|
|
3144
|
+
score: clamp(Math.round(raw.score * AGENT_DISCOUNT_FACTOR), 0, 10)
|
|
3145
|
+
};
|
|
3146
|
+
}
|
|
3147
|
+
return raw;
|
|
3148
|
+
}
|
|
3149
|
+
function scoreAll(ctx) {
|
|
3150
|
+
const results = {};
|
|
3151
|
+
for (const dim of DIMENSIONS) {
|
|
3152
|
+
results[dim] = scoreDimension(dim, ctx);
|
|
3153
|
+
}
|
|
3154
|
+
return results;
|
|
3155
|
+
}
|
|
3156
|
+
function buildScorecard(ctx) {
|
|
3157
|
+
const results = scoreAll(ctx);
|
|
3158
|
+
return {
|
|
3159
|
+
problem_clarity: results.problem_clarity.score,
|
|
3160
|
+
appetite: results.appetite.score,
|
|
3161
|
+
elements: results.elements.score,
|
|
3162
|
+
architecture: results.architecture.score,
|
|
3163
|
+
risks: results.risks.score,
|
|
3164
|
+
boundaries: results.boundaries.score
|
|
3165
|
+
};
|
|
3166
|
+
}
|
|
3167
|
+
function inferActiveDimension(scorecard, isSmallBatch = false) {
|
|
3168
|
+
const order = activeDimensions(isSmallBatch);
|
|
3169
|
+
const firstZero = order.find((d) => scorecard[d] === 0);
|
|
3170
|
+
if (firstZero) return firstZero;
|
|
3171
|
+
const firstWeak = order.find((d) => scorecard[d] < 6);
|
|
3172
|
+
if (firstWeak) return firstWeak;
|
|
3173
|
+
return order[order.length - 1];
|
|
3174
|
+
}
|
|
3175
|
+
function completedDimensions(scorecard, threshold = 6, isSmallBatch = false) {
|
|
3176
|
+
return activeDimensions(isSmallBatch).filter((d) => scorecard[d] >= threshold);
|
|
3177
|
+
}
|
|
3178
|
+
function suggestCaptures(ctx, activeDimension) {
|
|
3179
|
+
const suggestions = [];
|
|
3180
|
+
const text = ctx.dimensionTexts[activeDimension];
|
|
3181
|
+
if (text.length < 30) return suggestions;
|
|
3182
|
+
if (activeDimension === "problem_clarity" || activeDimension === "appetite") {
|
|
3183
|
+
if (countMatches(text, WORKAROUND_SIGNALS) > 0 && ctx.riskCount === 0) {
|
|
3184
|
+
const match = text.match(/(?:workaround|currently|today|right now)[^.]*\./i);
|
|
3185
|
+
if (match) {
|
|
3186
|
+
suggestions.push({
|
|
3187
|
+
type: "tension",
|
|
3188
|
+
name: extractPhrase(match[0], 60),
|
|
3189
|
+
reason: "Workaround described \u2014 captures the pain point as a trackable tension",
|
|
3190
|
+
confidence: 0.7
|
|
3191
|
+
});
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
if (activeDimension === "elements") {
|
|
3196
|
+
if (ctx.elementCount === 0 && /(?:service|component|module|layer|store|engine|kernel)/i.test(text)) {
|
|
3197
|
+
const match = text.match(/(?:a |the )?(\w[\w\s]{2,30}(?:service|component|module|layer|store|engine|kernel))/i);
|
|
3198
|
+
if (match) {
|
|
3199
|
+
suggestions.push({
|
|
3200
|
+
type: "element",
|
|
3201
|
+
name: capitalize(match[1].trim()),
|
|
3202
|
+
reason: "Solution component identified \u2014 capture as a feature entry",
|
|
3203
|
+
confidence: 0.8
|
|
3204
|
+
});
|
|
3205
|
+
}
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
if (activeDimension === "risks") {
|
|
3209
|
+
if (ctx.riskCount === 0 && countMatches(text, RISK_SIGNALS) >= 2) {
|
|
3210
|
+
const match = text.match(/(?:risk|rabbit hole|unknown|might|could fail)[^.]*\./i);
|
|
3211
|
+
if (match) {
|
|
3212
|
+
suggestions.push({
|
|
3213
|
+
type: "risk",
|
|
3214
|
+
name: extractPhrase(match[0], 60),
|
|
3215
|
+
reason: "Risk language detected \u2014 capture as a tension with mitigation",
|
|
3216
|
+
confidence: 0.7
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
if (/(?:we(?:'ll| will) (?:use|go with|pick|choose)|decided to|decision:|instead of)/i.test(text)) {
|
|
3222
|
+
const match = text.match(/(?:we(?:'ll| will) (?:use|go with|pick|choose)|decided to|decision:)[^.]*\./i);
|
|
3223
|
+
if (match) {
|
|
3224
|
+
suggestions.push({
|
|
3225
|
+
type: "decision",
|
|
3226
|
+
name: extractPhrase(match[0], 60),
|
|
3227
|
+
reason: "Decision language detected \u2014 capture rationale while it's fresh",
|
|
3228
|
+
confidence: 0.6
|
|
3229
|
+
});
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
return suggestions.slice(0, 3);
|
|
3233
|
+
}
|
|
3234
|
+
function extractPhrase(text, maxLen) {
|
|
3235
|
+
const cleaned = text.replace(/^(?:the |a |an )/i, "").trim();
|
|
3236
|
+
return cleaned.length > maxLen ? cleaned.slice(0, maxLen - 3) + "..." : cleaned;
|
|
3237
|
+
}
|
|
3238
|
+
function capitalize(s) {
|
|
3239
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
3240
|
+
}
|
|
3241
|
+
var PHASE_INVESTIGATIONS = {
|
|
3242
|
+
context: (betName) => ({
|
|
3243
|
+
phase: "context",
|
|
3244
|
+
dimension: "problem_clarity",
|
|
3245
|
+
tasks: [
|
|
3246
|
+
{ target: "chain", query: `Search for entries related to: ${betName}`, purpose: "Find existing tensions, decisions, and bets that overlap" },
|
|
3247
|
+
{ target: "chain", query: "List active governance: principles, standards, business-rules", purpose: "Surface constraints the solution must honor" },
|
|
3248
|
+
{ target: "codebase", query: `Search for code related to: ${betName}`, purpose: "Understand current implementation and pain points" }
|
|
3249
|
+
],
|
|
3250
|
+
proposalGuidance: "Synthesize findings into a 3-5 line context brief: what the Chain knows, what the codebase reveals, what governance applies.",
|
|
3251
|
+
reactionPrompt: "Here's what I found. Does this match what you're seeing, or is the problem different from what the Chain suggests?"
|
|
3252
|
+
}),
|
|
3253
|
+
framing: (betName) => ({
|
|
3254
|
+
phase: "framing",
|
|
3255
|
+
dimension: "problem_clarity",
|
|
3256
|
+
tasks: [
|
|
3257
|
+
{ target: "codebase", query: `Find workarounds, TODOs, or hacks related to: ${betName}`, purpose: "Surface concrete evidence of the problem" },
|
|
3258
|
+
{ target: "chain", query: "Search for related tensions and user complaints", purpose: "Quantify how often this problem occurs" }
|
|
3259
|
+
],
|
|
3260
|
+
proposalGuidance: "Propose a problem statement based on codebase evidence. Include who's affected and what the workaround looks like in the code.",
|
|
3261
|
+
reactionPrompt: "Based on the codebase, here's a draft problem statement. Does this capture it, or is the real problem different?"
|
|
3262
|
+
}),
|
|
3263
|
+
elements: (betName) => ({
|
|
3264
|
+
phase: "elements",
|
|
3265
|
+
dimension: "elements",
|
|
3266
|
+
tasks: [
|
|
3267
|
+
{ target: "codebase", query: `Find modules, services, and components that would be affected by: ${betName}`, purpose: "Map the solution to existing architecture" },
|
|
3268
|
+
{ target: "architecture", query: "Identify layer boundaries, API contracts, and dependency directions", purpose: "Ground elements in real architecture" },
|
|
3269
|
+
{ target: "chain", query: "Check DEC-31, STA-3, and other architecture standards", purpose: "Ensure elements respect existing decisions" }
|
|
3270
|
+
],
|
|
3271
|
+
proposalGuidance: "Propose 3-5 solution elements at breadboard level. Each element should name the layer it lives in, what it does, and how it connects to existing code.",
|
|
3272
|
+
reactionPrompt: "I've mapped out these solution elements based on the codebase. Which feel right? Which need adjustment?"
|
|
3273
|
+
}),
|
|
3274
|
+
derisking: (betName) => ({
|
|
3275
|
+
phase: "derisking",
|
|
3276
|
+
dimension: "risks",
|
|
3277
|
+
tasks: [
|
|
3278
|
+
{ target: "codebase", query: `Find fragile code, tight coupling, or missing tests near: ${betName}`, purpose: "Surface technical risks from code" },
|
|
3279
|
+
{ target: "codebase", query: "Check for performance-sensitive paths, database queries, and external dependencies", purpose: "Identify performance and reliability risks" },
|
|
3280
|
+
{ target: "chain", query: "Search for related tensions and past decisions that constrain this solution", purpose: "Surface risks from existing constraints" }
|
|
3281
|
+
],
|
|
3282
|
+
proposalGuidance: "Propose rabbit holes with severity and mitigations. Each risk should cite specific code or Chain evidence. Also propose no-gos \u2014 what should be explicitly excluded.",
|
|
3283
|
+
reactionPrompt: "These are the risks I found in the codebase. Are these real concerns? What am I missing?"
|
|
3284
|
+
}),
|
|
3285
|
+
validation: () => null,
|
|
3286
|
+
capture: () => null
|
|
3287
|
+
};
|
|
3288
|
+
function buildInvestigationBrief(phase, betName, betEntryId) {
|
|
3289
|
+
const builder = PHASE_INVESTIGATIONS[phase];
|
|
3290
|
+
return builder(betName, betEntryId);
|
|
3291
|
+
}
|
|
3292
|
+
function generateBuildContract(ctx) {
|
|
3293
|
+
const lines = ["## Build Contract"];
|
|
3294
|
+
const consult = [
|
|
3295
|
+
...ctx.governanceEntries.map((e) => `- \`${e.entryId}\` ${e.name} [${e.collection}]`),
|
|
3296
|
+
...ctx.relatedDecisions.map((e) => `- \`${e.entryId}\` ${e.name} [decision]`),
|
|
3297
|
+
...ctx.relatedTensions.map((e) => `- \`${e.entryId}\` ${e.name} [tension]`)
|
|
3298
|
+
];
|
|
3299
|
+
if (consult.length > 0) {
|
|
3300
|
+
lines.push("", "**Chain entries to consult before building:**");
|
|
3301
|
+
lines.push(...consult);
|
|
3302
|
+
} else {
|
|
3303
|
+
lines.push("", "**Chain entries to consult:** Run `orient` to discover applicable rules.");
|
|
3304
|
+
}
|
|
3305
|
+
lines.push(
|
|
3306
|
+
"",
|
|
3307
|
+
"**Capture obligations during build:**",
|
|
3308
|
+
"- New glossary terms that emerge from implementation",
|
|
3309
|
+
"- Implementation decisions not covered in shaping",
|
|
3310
|
+
"- Bugs or tensions discovered during build",
|
|
3311
|
+
"- Any deviation from the shaped solution (capture as decision with rationale)",
|
|
3312
|
+
"",
|
|
3313
|
+
"**Post-build Chain update:**",
|
|
3314
|
+
`- Update \`${ctx.betEntryId}\` with actual outcome vs. shaped intent`,
|
|
3315
|
+
"- Commit any draft entries created during the build",
|
|
3316
|
+
"- Capture a retrospective if the build deviated significantly"
|
|
3317
|
+
);
|
|
3318
|
+
return lines.join("\n");
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
// src/tools/facilitate-format.ts
|
|
3322
|
+
function appendElement(existing, element) {
|
|
3323
|
+
const existingCount = (existing?.match(/###\s*Element\s*\d/gi) ?? []).length;
|
|
3324
|
+
const num = existingCount + 1;
|
|
3325
|
+
const id = element.entryId ? ` (${element.entryId})` : "";
|
|
3326
|
+
const newBlock = `### Element ${num}: ${element.name}${id}
|
|
3327
|
+
|
|
3328
|
+
${element.description}`;
|
|
3329
|
+
return existing ? `${existing}
|
|
3330
|
+
|
|
3331
|
+
${newBlock}` : newBlock;
|
|
3332
|
+
}
|
|
3333
|
+
function appendRabbitHole(existing, risk) {
|
|
3334
|
+
const existingCount = (existing?.match(/###\s*RH\d/gi) ?? []).length;
|
|
3335
|
+
const num = existingCount + 1;
|
|
3336
|
+
const theme = risk.theme ? ` (${risk.theme})` : "";
|
|
3337
|
+
const status = risk.status ? ` \u2014 ${risk.status}` : "";
|
|
3338
|
+
const id = risk.entryId ? ` [${risk.entryId}]` : "";
|
|
3339
|
+
const newBlock = `### RH${num}: ${risk.name}${theme}${status}${id}
|
|
3340
|
+
|
|
3341
|
+
${risk.description}`;
|
|
3342
|
+
return existing ? `${existing}
|
|
3343
|
+
|
|
3344
|
+
${newBlock}` : newBlock;
|
|
3345
|
+
}
|
|
3346
|
+
function appendNoGo(existing, item) {
|
|
3347
|
+
const line = `- **${item.title}.** ${item.explanation}`;
|
|
3348
|
+
return existing ? `${existing}
|
|
3349
|
+
${line}` : line;
|
|
3350
|
+
}
|
|
3351
|
+
|
|
3352
|
+
// src/tools/facilitate.ts
|
|
3353
|
+
var FACILITATE_ACTIONS = ["start", "respond", "score", "resume"];
|
|
3354
|
+
var facilitateSchema = z12.object({
|
|
3355
|
+
action: z12.enum(FACILITATE_ACTIONS).describe(
|
|
3356
|
+
"'start': create draft bet and begin session. 'respond': process user input, score, capture, return coaching. 'score': return current scorecard without advancing. 'resume': reconstruct session from existing bet entry."
|
|
3357
|
+
),
|
|
3358
|
+
sessionType: z12.enum(["shape"]).default("shape").optional().describe("Session type. Only 'shape' in v1."),
|
|
3359
|
+
betEntryId: z12.string().optional().describe("Bet entry ID for resume action."),
|
|
3360
|
+
userInput: z12.string().optional().describe("User's input text for respond action."),
|
|
3361
|
+
dimension: z12.string().optional().describe("Explicit dimension to score against (e.g. 'problem_clarity'). If omitted, inferred from scorecard."),
|
|
3362
|
+
betName: z12.string().optional().describe("Name for the bet (used in start action)."),
|
|
3363
|
+
source: z12.enum(["user", "agent_proposal", "user_reaction"]).default("user").optional().describe("ENT-59: Source of the input text. 'user' = typed by user (default, full score weight). 'agent_proposal' = agent-generated proposal (discounted score). 'user_reaction' = user reacting to agent proposal (full weight)."),
|
|
3364
|
+
capture: z12.object({
|
|
3365
|
+
type: z12.enum(["element", "risk", "noGo", "decision"]).describe("What to capture"),
|
|
3366
|
+
name: z12.string().describe("Entry name"),
|
|
3367
|
+
description: z12.string().describe("Entry description"),
|
|
3368
|
+
theme: z12.string().optional().describe("Risk theme (for risk type)")
|
|
3369
|
+
}).optional().describe("Entry to capture alongside the respond action.")
|
|
3370
|
+
});
|
|
3371
|
+
function registerFacilitateTools(server) {
|
|
3372
|
+
server.registerTool(
|
|
3373
|
+
"facilitate",
|
|
3374
|
+
{
|
|
3375
|
+
title: "Facilitate \u2014 Coached Shaping",
|
|
3376
|
+
description: "Server-controlled coached shaping session with real-time Chain capture.\n\n- **start**: Create a draft bet on the Chain and begin a coached session. Returns Studio URL, initial context, and first coaching prompt.\n- **respond**: Process user input \u2014 scores against 6 shaping rubrics (problem, appetite, elements, architecture, risks, boundaries), searches for overlap, captures to Chain, returns structured coaching response with phase tracking.\n- **score**: Return the current scorecard without advancing the session.\n- **resume**: Reconstruct session state from an existing bet entry on the Chain.\n\nThe structured response separates judgment (server) from coaching (agent) per DEC-56. Read the phase, scorecard, and coaching fields to determine what to say next. When captureReady is true, buildContract is auto-generated from Chain governance.",
|
|
3377
|
+
inputSchema: facilitateSchema,
|
|
3378
|
+
annotations: {
|
|
3379
|
+
readOnlyHint: false,
|
|
3380
|
+
destructiveHint: false,
|
|
3381
|
+
idempotentHint: false,
|
|
3382
|
+
openWorldHint: false
|
|
3383
|
+
}
|
|
3384
|
+
},
|
|
3385
|
+
async (args) => {
|
|
3386
|
+
const parsed = facilitateSchema.safeParse(args);
|
|
3387
|
+
if (!parsed.success) {
|
|
3388
|
+
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
3389
|
+
return {
|
|
3390
|
+
content: [{ type: "text", text: `Invalid arguments: ${issues}` }]
|
|
3391
|
+
};
|
|
3392
|
+
}
|
|
3393
|
+
const { action } = parsed.data;
|
|
3394
|
+
return runWithToolContext({ tool: "facilitate", action }, async () => {
|
|
3395
|
+
switch (action) {
|
|
3396
|
+
case "start":
|
|
3397
|
+
return handleStart2(parsed.data);
|
|
3398
|
+
case "respond":
|
|
3399
|
+
return handleRespond(parsed.data);
|
|
3400
|
+
case "score":
|
|
3401
|
+
return handleScore(parsed.data);
|
|
3402
|
+
case "resume":
|
|
3403
|
+
return handleResume(parsed.data);
|
|
3404
|
+
default:
|
|
3405
|
+
return {
|
|
3406
|
+
content: [{
|
|
3407
|
+
type: "text",
|
|
3408
|
+
text: `Unknown action '${action}'. Valid: ${FACILITATE_ACTIONS.join(", ")}.`
|
|
3409
|
+
}]
|
|
3410
|
+
};
|
|
3411
|
+
}
|
|
3412
|
+
});
|
|
3413
|
+
}
|
|
3414
|
+
);
|
|
3415
|
+
}
|
|
3416
|
+
function buildStudioUrl(workspaceSlug, entryId) {
|
|
3417
|
+
return `https://productbrain.app/${workspaceSlug}/studio/entries/${entryId}`;
|
|
3418
|
+
}
|
|
3419
|
+
function buildDirective(scorecard, phase, betEntryId, captureReady, activeDimension, nextDimension) {
|
|
3420
|
+
const b = betEntryId;
|
|
3421
|
+
if (scorecard.problem_clarity >= 6 && scorecard.appetite === 0) {
|
|
3422
|
+
return [
|
|
3423
|
+
`Present this to the user exactly as formatted:`,
|
|
3424
|
+
``,
|
|
3425
|
+
`**Appetite** \u2014 How much time is this idea worth? (Not how long it takes \u2014 how much you're willing to invest.)`,
|
|
3426
|
+
`1. **Small Batch** \u2014 1-2 weeks, focused scope`,
|
|
3427
|
+
`2. **Big Batch** \u2014 6 weeks, full treatment`,
|
|
3428
|
+
``,
|
|
3429
|
+
`Wait for their answer. Then call \`facilitate action=respond betEntryId="${b}" dimension="appetite"\` with their choice.`
|
|
3430
|
+
].join("\n");
|
|
3431
|
+
}
|
|
3432
|
+
if (scorecard.problem_clarity >= 6 && scorecard.appetite >= 6 && scorecard.elements === 0) {
|
|
3433
|
+
return [
|
|
3434
|
+
`Synthesize the problem and appetite back to the user in 2-3 lines, then present:`,
|
|
3435
|
+
``,
|
|
3436
|
+
`**Frame Go** \u2014 The problem is framed and appetite is set.`,
|
|
3437
|
+
`1. **Go** \u2014 move to solution elements`,
|
|
3438
|
+
`2. **Refine** \u2014 something's not right, let's adjust`,
|
|
3439
|
+
``,
|
|
3440
|
+
`If Go: **investigate first** \u2014 use the investigationBrief in the structured response to search the codebase and Chain.`,
|
|
3441
|
+
`Spawn sub-agents to explore the codebase for affected modules, existing architecture, and related patterns.`,
|
|
3442
|
+
`Then propose 3-5 solution elements grounded in what you found.`,
|
|
3443
|
+
`Present proposals for user reaction: "Based on the codebase, here are the elements I'd suggest. Which feel right?"`,
|
|
3444
|
+
`For each confirmed element, call \`facilitate action=respond betEntryId="${b}" dimension="elements" source="user_reaction"\` with the user's reaction.`,
|
|
3445
|
+
`If Refine: ask what needs adjusting, then call respond with dimension="problem_clarity" or "appetite".`
|
|
3446
|
+
].join("\n");
|
|
3447
|
+
}
|
|
3448
|
+
if (phase === "derisking" && scorecard.elements >= 6 && scorecard.risks >= 4 && scorecard.boundaries >= 4) {
|
|
3449
|
+
return [
|
|
3450
|
+
`Synthesize the shape so far in 3-4 lines, then present:`,
|
|
3451
|
+
``,
|
|
3452
|
+
`**Shape Go** \u2014 Elements defined, risks covered.`,
|
|
3453
|
+
`1. **Go** \u2014 validate and capture the pitch`,
|
|
3454
|
+
`2. **Refine** \u2014 there's more to cover`,
|
|
3455
|
+
``,
|
|
3456
|
+
`If Go: call \`facilitate action=score betEntryId="${b}"\` and present the full scorecard.`,
|
|
3457
|
+
`If Refine: ask what needs work, continue with respond.`
|
|
3458
|
+
].join("\n");
|
|
3459
|
+
}
|
|
3460
|
+
if (captureReady) {
|
|
3461
|
+
return [
|
|
3462
|
+
`Present the full scorecard, then:`,
|
|
3463
|
+
``,
|
|
3464
|
+
`**Ready to commit?**`,
|
|
3465
|
+
`1. **Commit** \u2014 publish to the Chain as source of truth`,
|
|
3466
|
+
`2. **Review** \u2014 show the full pitch first`,
|
|
3467
|
+
`3. **Keep as draft** \u2014 not ready yet`,
|
|
3468
|
+
``,
|
|
3469
|
+
`If Commit: call \`commit-entry entryId="${b}"\`.`,
|
|
3470
|
+
`If Review: call \`facilitate action=score betEntryId="${b}"\` for the detailed view.`,
|
|
3471
|
+
`If Draft: acknowledge and end the session.`
|
|
3472
|
+
].join("\n");
|
|
3473
|
+
}
|
|
3474
|
+
if (nextDimension && nextDimension !== activeDimension) {
|
|
3475
|
+
const nextLabel = DIMENSION_LABELS[nextDimension];
|
|
3476
|
+
const investigationPhases = ["elements", "architecture", "risks", "boundaries"];
|
|
3477
|
+
const shouldInvestigate = investigationPhases.includes(nextDimension);
|
|
3478
|
+
if (shouldInvestigate) {
|
|
3479
|
+
return [
|
|
3480
|
+
`Briefly synthesize what was just covered (2 lines max), then move to **${nextLabel}**.`,
|
|
3481
|
+
`**Investigate first:** Use the investigationBrief to search the codebase and Chain before asking the user.`,
|
|
3482
|
+
`Spawn sub-agents to gather evidence, then propose findings for user reaction.`,
|
|
3483
|
+
`After the user reacts, call \`facilitate action=respond betEntryId="${b}" dimension="${nextDimension}" source="user_reaction"\` with their reaction.`,
|
|
3484
|
+
`If the user provides their own input instead, call with source="user".`
|
|
3485
|
+
].join("\n");
|
|
3486
|
+
}
|
|
3487
|
+
return [
|
|
3488
|
+
`Briefly synthesize what was just covered (2 lines max), then move to **${nextLabel}**.`,
|
|
3489
|
+
`Ask ONE open question about ${nextLabel.toLowerCase()}. Keep it to 3 lines.`,
|
|
3490
|
+
`After the user answers, call \`facilitate action=respond betEntryId="${b}" dimension="${nextDimension}"\` with their input.`
|
|
3491
|
+
].join("\n");
|
|
3492
|
+
}
|
|
3493
|
+
return null;
|
|
3494
|
+
}
|
|
3495
|
+
function emptyScorecard() {
|
|
3496
|
+
return { problem_clarity: 0, appetite: 0, elements: 0, architecture: 0, risks: 0, boundaries: 0 };
|
|
3497
|
+
}
|
|
3498
|
+
function detectSmallBatch(appetiteText) {
|
|
3499
|
+
if (!appetiteText) return false;
|
|
3500
|
+
return /small\s*batch|1[-–]2\s*week|2[-–]week|one.?week|two.?week/i.test(appetiteText);
|
|
3501
|
+
}
|
|
3502
|
+
function emptyResponse(betEntryId, studioUrl) {
|
|
3503
|
+
return {
|
|
3504
|
+
phase: "context",
|
|
3505
|
+
phaseLabel: PHASE_LABELS.context,
|
|
3506
|
+
scorecard: emptyScorecard(),
|
|
3507
|
+
overlap: [],
|
|
3508
|
+
alignment: [],
|
|
3509
|
+
chainSurfaced: [],
|
|
3510
|
+
suggestedCaptures: [],
|
|
3511
|
+
sessionDrafts: [],
|
|
3512
|
+
coaching: {
|
|
3513
|
+
observation: "New shaping session started",
|
|
3514
|
+
missing: ["Problem statement", "Appetite", "Solution elements", "Architecture", "Risks", "Boundaries"],
|
|
3515
|
+
pushback: "",
|
|
3516
|
+
suggestedQuestion: "Describe the problem you want to solve. Who's affected? What's the workaround today?",
|
|
3517
|
+
captureReady: false,
|
|
3518
|
+
nextDimension: "problem_clarity"
|
|
3519
|
+
},
|
|
3520
|
+
captured: {
|
|
3521
|
+
betEntryId,
|
|
3522
|
+
studioUrl,
|
|
3523
|
+
entriesCreated: [],
|
|
3524
|
+
relationsCreated: 0
|
|
3525
|
+
},
|
|
3526
|
+
captureErrors: [],
|
|
3527
|
+
navigation: {
|
|
3528
|
+
completedDimensions: [],
|
|
3529
|
+
activeDimension: "problem_clarity",
|
|
3530
|
+
suggestedOrder: [...SUGGESTED_ORDER]
|
|
3531
|
+
}
|
|
3532
|
+
};
|
|
3533
|
+
}
|
|
3534
|
+
async function loadBetEntry(entryId) {
|
|
3535
|
+
try {
|
|
3536
|
+
return await mcpQuery("chain.getEntry", { entryId });
|
|
3537
|
+
} catch {
|
|
3538
|
+
return null;
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
async function loadConstellationState(betEntryId, betInternalId) {
|
|
3542
|
+
try {
|
|
3543
|
+
const relations = await mcpQuery("chain.listEntryRelations", {
|
|
3544
|
+
entryId: betEntryId
|
|
3545
|
+
});
|
|
3546
|
+
const internalId = betInternalId ?? (await loadBetEntry(betEntryId))?._id;
|
|
3547
|
+
const elementCount = relations.filter(
|
|
3548
|
+
(r) => r.type === "part_of" && r.toId === internalId
|
|
3549
|
+
).length;
|
|
3550
|
+
const riskCount = relations.filter((r) => r.type === "constrains").length;
|
|
3551
|
+
const decisionCount = relations.filter((r) => r.type === "informs").length;
|
|
3552
|
+
return { relations, elementCount, riskCount, decisionCount };
|
|
3553
|
+
} catch {
|
|
3554
|
+
return { relations: [], elementCount: 0, riskCount: 0, decisionCount: 0 };
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
var RELATION_TO_COLLECTION = {
|
|
3558
|
+
part_of: "features",
|
|
3559
|
+
constrains: "tensions",
|
|
3560
|
+
informs: "decisions"
|
|
3561
|
+
};
|
|
3562
|
+
async function loadSessionDrafts(betEntryId, betInternalId) {
|
|
3563
|
+
try {
|
|
3564
|
+
const relations = await mcpQuery("chain.listEntryRelations", {
|
|
3565
|
+
entryId: betEntryId
|
|
3566
|
+
});
|
|
3567
|
+
const internalId = betInternalId ?? (await loadBetEntry(betEntryId))?._id;
|
|
3568
|
+
const incoming = relations.filter(
|
|
3569
|
+
(r) => r.toId === internalId && ["part_of", "constrains", "informs"].includes(r.type)
|
|
3570
|
+
);
|
|
3571
|
+
const drafts = [];
|
|
3572
|
+
for (const rel of incoming.slice(0, 15)) {
|
|
3573
|
+
try {
|
|
3574
|
+
const entry = await mcpQuery(
|
|
3575
|
+
"chain.getEntry",
|
|
3576
|
+
{ id: rel.fromId }
|
|
3577
|
+
);
|
|
3578
|
+
if (entry?.entryId) {
|
|
3579
|
+
drafts.push({
|
|
3580
|
+
entryId: entry.entryId,
|
|
3581
|
+
name: entry.name,
|
|
3582
|
+
collection: RELATION_TO_COLLECTION[rel.type] ?? "unknown",
|
|
3583
|
+
relationType: rel.type
|
|
3584
|
+
});
|
|
3585
|
+
}
|
|
3586
|
+
} catch {
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
return drafts;
|
|
3590
|
+
} catch {
|
|
3591
|
+
return [];
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
function buildScoringContext(userInput, betData, constellation, overlapIds, chainSurfaced = [], activeDimension, source = "user") {
|
|
3595
|
+
const str = (key) => betData[key] ?? "";
|
|
3596
|
+
const accParts = [
|
|
3597
|
+
str("problem"),
|
|
3598
|
+
str("appetite"),
|
|
3599
|
+
str("elements"),
|
|
3600
|
+
str("architecture"),
|
|
3601
|
+
str("rabbitHoles"),
|
|
3602
|
+
str("noGos"),
|
|
3603
|
+
str("solution"),
|
|
3604
|
+
userInput
|
|
3605
|
+
];
|
|
3606
|
+
const noGoText = str("noGos");
|
|
3607
|
+
const bulletNoGos = (noGoText.match(/^- \*\*/gm) ?? []).length;
|
|
3608
|
+
const boundariesFull = [noGoText, activeDimension === "boundaries" ? userInput : ""].join("\n");
|
|
3609
|
+
const wontNoGos = (boundariesFull.match(/\bwon'?t\b|\bwill not\b/gi) ?? []).length;
|
|
3610
|
+
const noGoCount = Math.max(bulletNoGos, wontNoGos);
|
|
3611
|
+
const governanceCollections = /* @__PURE__ */ new Set(["principles", "standards", "business-rules"]);
|
|
3612
|
+
const governanceCount = chainSurfaced.filter((e) => governanceCollections.has(e.collection)).length;
|
|
3613
|
+
const hasArchitectureText = str("architecture").length > 20;
|
|
3614
|
+
const join2 = (...parts) => parts.filter(Boolean).join("\n\n");
|
|
3615
|
+
const inputFor = (dim) => activeDimension === dim ? userInput : "";
|
|
3616
|
+
const dimensionTexts = {
|
|
3617
|
+
problem_clarity: join2(str("problem"), inputFor("problem_clarity")),
|
|
3618
|
+
appetite: join2(str("appetite"), inputFor("appetite")),
|
|
3619
|
+
elements: join2(str("elements"), str("solution"), inputFor("elements")),
|
|
3620
|
+
architecture: join2(str("architecture"), inputFor("architecture")),
|
|
3621
|
+
risks: join2(str("rabbitHoles"), inputFor("risks")),
|
|
3622
|
+
boundaries: join2(str("noGos"), inputFor("boundaries"))
|
|
3623
|
+
};
|
|
3624
|
+
return {
|
|
3625
|
+
userInput,
|
|
3626
|
+
accumulatedText: accParts.filter(Boolean).join("\n\n"),
|
|
3627
|
+
dimensionTexts,
|
|
3628
|
+
existingEntryIds: overlapIds,
|
|
3629
|
+
elementCount: constellation.elementCount,
|
|
3630
|
+
riskCount: constellation.riskCount,
|
|
3631
|
+
noGoCount,
|
|
3632
|
+
governanceCount,
|
|
3633
|
+
decisionCount: constellation.decisionCount,
|
|
3634
|
+
hasArchitectureText,
|
|
3635
|
+
source
|
|
3636
|
+
};
|
|
3637
|
+
}
|
|
3638
|
+
async function searchOverlap(text) {
|
|
3639
|
+
if (text.length < 20) return [];
|
|
3640
|
+
const query = text.slice(0, 200);
|
|
3641
|
+
try {
|
|
3642
|
+
const results = await mcpQuery(
|
|
3643
|
+
"chain.searchEntries",
|
|
3644
|
+
{ query }
|
|
3645
|
+
);
|
|
3646
|
+
return (results ?? []).filter((r) => r.entryId).slice(0, 5).map((r) => ({
|
|
3647
|
+
entryId: r.entryId,
|
|
3648
|
+
similarity: "text_match",
|
|
3649
|
+
summary: `${r.name} [${r.collectionSlug ?? "unknown"}]`
|
|
3650
|
+
}));
|
|
3651
|
+
} catch {
|
|
3652
|
+
return [];
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
async function gatherChainContext(text, betName) {
|
|
3656
|
+
if (text.length < 10 && (!betName || betName.length < 5)) return [];
|
|
3657
|
+
const searchText = (text.length >= 10 ? text : betName ?? "").slice(0, 300);
|
|
3658
|
+
try {
|
|
3659
|
+
const results = await mcpQuery(
|
|
3660
|
+
"chain.searchEntries",
|
|
3661
|
+
{ query: searchText }
|
|
3662
|
+
);
|
|
3663
|
+
return (results ?? []).filter((e) => e.entryId).slice(0, 8).map((e) => ({
|
|
3664
|
+
entryId: e.entryId,
|
|
3665
|
+
relevance: e.name,
|
|
3666
|
+
collection: e.collectionSlug ?? "unknown"
|
|
3667
|
+
}));
|
|
3668
|
+
} catch {
|
|
3669
|
+
return [];
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
async function findAndLinkExisting(name, collectionSlug, betEntryId, relationType) {
|
|
3673
|
+
try {
|
|
3674
|
+
const results = await mcpQuery(
|
|
3675
|
+
"chain.searchEntries",
|
|
3676
|
+
{ query: name }
|
|
3677
|
+
);
|
|
3678
|
+
const match = (results ?? []).find(
|
|
3679
|
+
(r) => r.entryId && r.name.toLowerCase() === name.toLowerCase() && (r.collectionSlug === collectionSlug || !r.collectionSlug)
|
|
3680
|
+
);
|
|
3681
|
+
if (!match?.entryId) return null;
|
|
3682
|
+
try {
|
|
3683
|
+
await mcpMutation("chain.createEntryRelation", {
|
|
3684
|
+
fromEntryId: match.entryId,
|
|
3685
|
+
toEntryId: betEntryId,
|
|
3686
|
+
type: relationType
|
|
3687
|
+
});
|
|
3688
|
+
await recordSessionActivity({ relationCreated: true });
|
|
3689
|
+
return { entryId: match.entryId, linked: true };
|
|
3690
|
+
} catch {
|
|
3691
|
+
return { entryId: match.entryId, linked: false };
|
|
3692
|
+
}
|
|
3693
|
+
} catch {
|
|
3694
|
+
return null;
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
async function handleStart2(args) {
|
|
3698
|
+
requireWriteAccess();
|
|
3699
|
+
const wsCtx = await getWorkspaceContext();
|
|
3700
|
+
const betName = args.betName ?? "Untitled Bet (Shaping)";
|
|
3701
|
+
let orientContext = "";
|
|
3702
|
+
try {
|
|
3703
|
+
const orient = await mcpQuery("chain.getOrientEntries", {});
|
|
3704
|
+
const sc = orient?.strategicContext;
|
|
3705
|
+
const parts = [];
|
|
3706
|
+
if (sc?.vision) parts.push(`Vision: ${sc.vision}`);
|
|
3707
|
+
if (sc?.activeBetCount) parts.push(`${sc.activeBetCount} active bet(s)`);
|
|
3708
|
+
if (sc?.activeTensionCount) parts.push(`${sc.activeTensionCount} open tension(s)`);
|
|
3709
|
+
if (orient?.activeBets?.length) {
|
|
3710
|
+
parts.push("Active bets: " + orient.activeBets.map((b) => `${b.entryId ?? "?"} ${b.name}`).join(", "));
|
|
3711
|
+
}
|
|
3712
|
+
orientContext = parts.join(". ");
|
|
3713
|
+
} catch {
|
|
3714
|
+
orientContext = "Could not load workspace context.";
|
|
3715
|
+
}
|
|
3716
|
+
let betEntryId;
|
|
3717
|
+
let docId;
|
|
3718
|
+
const agentId = getAgentSessionId();
|
|
3719
|
+
try {
|
|
3720
|
+
const result = await mcpMutation(
|
|
3721
|
+
"chain.createEntry",
|
|
3722
|
+
{
|
|
3723
|
+
collectionSlug: "bets",
|
|
3724
|
+
name: betName,
|
|
3725
|
+
status: "draft",
|
|
3726
|
+
data: {
|
|
3727
|
+
problem: "",
|
|
3728
|
+
appetite: "",
|
|
3729
|
+
elements: "",
|
|
3730
|
+
rabbitHoles: "",
|
|
3731
|
+
noGos: "",
|
|
3732
|
+
architecture: "",
|
|
3733
|
+
buildContract: "",
|
|
3734
|
+
description: `Shaping session for: ${betName}`,
|
|
3735
|
+
status: "shaping",
|
|
3736
|
+
shapingSessionActive: true
|
|
3737
|
+
},
|
|
3738
|
+
createdBy: agentId ? `agent:${agentId}` : "facilitate",
|
|
3739
|
+
sessionId: agentId ?? void 0
|
|
3740
|
+
}
|
|
3741
|
+
);
|
|
3742
|
+
betEntryId = result.entryId;
|
|
3743
|
+
docId = result.docId;
|
|
3744
|
+
await recordSessionActivity({ entryCreated: docId });
|
|
3745
|
+
} catch (err) {
|
|
3746
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3747
|
+
return {
|
|
3748
|
+
content: [{ type: "text", text: `Failed to create bet entry: ${msg}` }],
|
|
3749
|
+
isError: true
|
|
3750
|
+
};
|
|
3751
|
+
}
|
|
3752
|
+
const studioUrl = buildStudioUrl(wsCtx.workspaceSlug, betEntryId);
|
|
3753
|
+
const response = emptyResponse(betEntryId, studioUrl);
|
|
3754
|
+
const investigationBrief = buildInvestigationBrief("context", betName, betEntryId);
|
|
3755
|
+
if (investigationBrief) {
|
|
3756
|
+
response.investigationBrief = investigationBrief;
|
|
3757
|
+
}
|
|
3758
|
+
const output = [
|
|
3759
|
+
`# Shaping Session Started`,
|
|
3760
|
+
"",
|
|
3761
|
+
`**Bet:** ${betName} (\`${betEntryId}\`)`,
|
|
3762
|
+
`**Studio:** ${studioUrl}`,
|
|
3763
|
+
`**Phase:** ${PHASE_LABELS.context}`,
|
|
3764
|
+
"",
|
|
3765
|
+
orientContext ? `**Workspace context:** ${orientContext}` : "",
|
|
3766
|
+
"",
|
|
3767
|
+
"Session is active. The bet exists as a draft on the Chain.",
|
|
3768
|
+
"",
|
|
3769
|
+
"---",
|
|
3770
|
+
"## Agent Directive",
|
|
3771
|
+
"**Investigate first** \u2014 before asking the user, use the investigationBrief to search the codebase and Chain for context related to this bet.",
|
|
3772
|
+
"Spawn sub-agents: one `explore` agent to search the codebase, one `generalPurpose` agent to search the Chain.",
|
|
3773
|
+
"Synthesize findings into a 3-5 line context brief, then present to the user.",
|
|
3774
|
+
"After presenting context, ask: **What's not working well? Describe the problem \u2014 who's affected, and what's the workaround today?**",
|
|
3775
|
+
"Keep your message to 5 lines or fewer. Heavy context degrades the shaping experience.",
|
|
3776
|
+
`After the user responds, call \`facilitate action=respond betEntryId="${betEntryId}" dimension="problem_clarity"\` with their answer as userInput.`
|
|
3777
|
+
].filter(Boolean).join("\n");
|
|
3778
|
+
return {
|
|
3779
|
+
content: [{ type: "text", text: output }],
|
|
3780
|
+
structuredContent: response
|
|
3781
|
+
};
|
|
3782
|
+
}
|
|
3783
|
+
async function handleRespond(args) {
|
|
3784
|
+
requireWriteAccess();
|
|
3785
|
+
const { userInput, betEntryId: argBetId, dimension: argDimension, capture: captureArg, source: argSource } = args;
|
|
3786
|
+
const source = argSource ?? "user";
|
|
3787
|
+
if (!userInput) {
|
|
3788
|
+
return {
|
|
3789
|
+
content: [{ type: "text", text: "`userInput` is required for respond action." }]
|
|
3790
|
+
};
|
|
3791
|
+
}
|
|
3792
|
+
if (!argBetId) {
|
|
3793
|
+
return {
|
|
3794
|
+
content: [{ type: "text", text: "`betEntryId` is required for respond action. Use start first." }]
|
|
3795
|
+
};
|
|
3796
|
+
}
|
|
3797
|
+
const captureErrors = [];
|
|
3798
|
+
const entriesCreated = [];
|
|
3799
|
+
let relationsCreated = 0;
|
|
3800
|
+
const [betEntry, constellation, overlap, chainSurfaced] = await Promise.all([
|
|
3801
|
+
loadBetEntry(argBetId),
|
|
3802
|
+
loadConstellationState(argBetId),
|
|
3803
|
+
captureArg ? Promise.resolve([]) : searchOverlap(userInput),
|
|
3804
|
+
gatherChainContext(userInput)
|
|
3805
|
+
]);
|
|
3806
|
+
if (!betEntry) {
|
|
3807
|
+
return {
|
|
3808
|
+
content: [{ type: "text", text: `Bet \`${argBetId}\` not found. Use start to create a session.` }]
|
|
3809
|
+
};
|
|
3810
|
+
}
|
|
3811
|
+
const betData = betEntry.data ?? {};
|
|
3812
|
+
if (captureArg) {
|
|
3813
|
+
const capAgentId = getAgentSessionId();
|
|
3814
|
+
const collectionMap = {
|
|
3815
|
+
element: { slug: "features", dataField: "description", relationType: "part_of" },
|
|
3816
|
+
risk: { slug: "tensions", dataField: "description", relationType: "constrains" },
|
|
3817
|
+
decision: { slug: "decisions", dataField: "rationale", relationType: "informs" }
|
|
3818
|
+
};
|
|
3819
|
+
if (captureArg.type === "noGo") {
|
|
3820
|
+
const updatedNoGos = appendNoGo(
|
|
3821
|
+
betData.noGos,
|
|
3822
|
+
{ title: captureArg.name, explanation: captureArg.description }
|
|
3823
|
+
);
|
|
3824
|
+
try {
|
|
3825
|
+
await mcpMutation("chain.updateEntry", {
|
|
3826
|
+
entryId: argBetId,
|
|
3827
|
+
data: { noGos: updatedNoGos },
|
|
3828
|
+
changeNote: `Added no-go: ${captureArg.name}`
|
|
3829
|
+
});
|
|
3830
|
+
await recordSessionActivity({ entryModified: betEntry._id });
|
|
3831
|
+
} catch (updErr) {
|
|
3832
|
+
captureErrors.push({
|
|
3833
|
+
operation: "update",
|
|
3834
|
+
detail: `noGos field: ${updErr instanceof Error ? updErr.message : String(updErr)}`
|
|
3835
|
+
});
|
|
3836
|
+
}
|
|
3837
|
+
} else {
|
|
3838
|
+
const mapping = collectionMap[captureArg.type];
|
|
3839
|
+
if (mapping) {
|
|
3840
|
+
let capturedEntryId = null;
|
|
3841
|
+
try {
|
|
3842
|
+
const result = await mcpMutation(
|
|
3843
|
+
"chain.createEntry",
|
|
3844
|
+
{
|
|
3845
|
+
collectionSlug: mapping.slug,
|
|
3846
|
+
name: captureArg.name,
|
|
3847
|
+
status: "draft",
|
|
3848
|
+
data: { [mapping.dataField]: captureArg.description },
|
|
3849
|
+
createdBy: capAgentId ? `agent:${capAgentId}` : "facilitate",
|
|
3850
|
+
sessionId: capAgentId ?? void 0
|
|
3851
|
+
}
|
|
3852
|
+
);
|
|
3853
|
+
capturedEntryId = result.entryId;
|
|
3854
|
+
entriesCreated.push(result.entryId);
|
|
3855
|
+
await recordSessionActivity({ entryCreated: result.docId });
|
|
3856
|
+
} catch (createErr) {
|
|
3857
|
+
const msg = createErr instanceof Error ? createErr.message : String(createErr);
|
|
3858
|
+
if (msg.includes("Duplicate entry") || msg.includes("already exists")) {
|
|
3859
|
+
const fallback = await findAndLinkExisting(
|
|
3860
|
+
captureArg.name,
|
|
3861
|
+
mapping.slug,
|
|
3862
|
+
argBetId,
|
|
3863
|
+
mapping.relationType
|
|
3864
|
+
);
|
|
3865
|
+
if (fallback) {
|
|
3866
|
+
capturedEntryId = fallback.entryId;
|
|
3867
|
+
entriesCreated.push(fallback.entryId);
|
|
3868
|
+
if (fallback.linked) relationsCreated++;
|
|
3869
|
+
captureErrors.push({
|
|
3870
|
+
operation: "info",
|
|
3871
|
+
detail: `Linked existing ${mapping.slug} entry \`${fallback.entryId}\` instead of creating duplicate.`
|
|
3872
|
+
});
|
|
3873
|
+
} else {
|
|
3874
|
+
captureErrors.push({ operation: "capture", detail: `${captureArg.type}: ${msg}` });
|
|
3875
|
+
}
|
|
3876
|
+
} else {
|
|
3877
|
+
captureErrors.push({ operation: "capture", detail: `${captureArg.type}: ${msg}` });
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
if (capturedEntryId) {
|
|
3881
|
+
try {
|
|
3882
|
+
await mcpMutation("chain.createEntryRelation", {
|
|
3883
|
+
fromEntryId: capturedEntryId,
|
|
3884
|
+
toEntryId: argBetId,
|
|
3885
|
+
type: mapping.relationType
|
|
3886
|
+
});
|
|
3887
|
+
relationsCreated++;
|
|
3888
|
+
await recordSessionActivity({ relationCreated: true });
|
|
3889
|
+
} catch (relErr) {
|
|
3890
|
+
const msg = relErr instanceof Error ? relErr.message : String(relErr);
|
|
3891
|
+
if (!msg.includes("already exists") && !msg.includes("Duplicate")) {
|
|
3892
|
+
captureErrors.push({
|
|
3893
|
+
operation: "relation",
|
|
3894
|
+
detail: `${mapping.relationType} from ${capturedEntryId} to ${argBetId}: ${msg}`
|
|
3895
|
+
});
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
if (captureArg.type === "element") {
|
|
3899
|
+
const updatedElements = appendElement(
|
|
3900
|
+
betData.elements,
|
|
3901
|
+
{ name: captureArg.name, description: captureArg.description, entryId: capturedEntryId }
|
|
3902
|
+
);
|
|
3903
|
+
try {
|
|
3904
|
+
await mcpMutation("chain.updateEntry", {
|
|
3905
|
+
entryId: argBetId,
|
|
3906
|
+
data: { elements: updatedElements },
|
|
3907
|
+
changeNote: `Added element: ${captureArg.name}`
|
|
3908
|
+
});
|
|
3909
|
+
await recordSessionActivity({ entryModified: betEntry._id });
|
|
3910
|
+
} catch (updErr) {
|
|
3911
|
+
captureErrors.push({
|
|
3912
|
+
operation: "update",
|
|
3913
|
+
detail: `elements field: ${updErr instanceof Error ? updErr.message : String(updErr)}`
|
|
3914
|
+
});
|
|
3915
|
+
}
|
|
3916
|
+
} else if (captureArg.type === "risk") {
|
|
3917
|
+
const updatedRisks = appendRabbitHole(
|
|
3918
|
+
betData.rabbitHoles,
|
|
3919
|
+
{ name: captureArg.name, description: captureArg.description, theme: captureArg.theme, entryId: capturedEntryId }
|
|
3920
|
+
);
|
|
3921
|
+
try {
|
|
3922
|
+
await mcpMutation("chain.updateEntry", {
|
|
3923
|
+
entryId: argBetId,
|
|
3924
|
+
data: { rabbitHoles: updatedRisks },
|
|
3925
|
+
changeNote: `Added risk: ${captureArg.name}`
|
|
3926
|
+
});
|
|
3927
|
+
await recordSessionActivity({ entryModified: betEntry._id });
|
|
3928
|
+
} catch (updErr) {
|
|
3929
|
+
captureErrors.push({
|
|
3930
|
+
operation: "update",
|
|
3931
|
+
detail: `rabbitHoles field: ${updErr instanceof Error ? updErr.message : String(updErr)}`
|
|
3932
|
+
});
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
const refreshedBet = await loadBetEntry(argBetId);
|
|
3940
|
+
const refreshedData = refreshedBet?.data ?? betData;
|
|
3941
|
+
const refreshedConstellation = captureArg ? await loadConstellationState(argBetId, refreshedBet?._id ?? betEntry._id) : constellation;
|
|
3942
|
+
const overlapIds = overlap.map((o) => o.entryId);
|
|
3943
|
+
const appetiteText = refreshedData.appetite ?? "";
|
|
3944
|
+
const isSmallBatch = detectSmallBatch(
|
|
3945
|
+
argDimension === "appetite" ? userInput : appetiteText
|
|
3946
|
+
);
|
|
3947
|
+
let activeDimension;
|
|
3948
|
+
if (argDimension && DIMENSIONS.includes(argDimension)) {
|
|
3949
|
+
activeDimension = argDimension;
|
|
3950
|
+
} else {
|
|
3951
|
+
const prelim = buildScoringContext(userInput, refreshedData, refreshedConstellation, overlapIds, chainSurfaced, void 0, source);
|
|
3952
|
+
activeDimension = inferActiveDimension(buildScorecard(prelim), isSmallBatch);
|
|
3953
|
+
}
|
|
3954
|
+
const scoringCtx = buildScoringContext(
|
|
3955
|
+
userInput,
|
|
3956
|
+
refreshedData,
|
|
3957
|
+
refreshedConstellation,
|
|
3958
|
+
overlapIds,
|
|
3959
|
+
chainSurfaced,
|
|
3960
|
+
activeDimension,
|
|
3961
|
+
source
|
|
3962
|
+
);
|
|
3963
|
+
const dimensionResult = scoreDimension(activeDimension, scoringCtx);
|
|
3964
|
+
const scorecard = buildScorecard(scoringCtx);
|
|
3965
|
+
if (isSmallBatch) scorecard.architecture = -1;
|
|
3966
|
+
const completed = completedDimensions(scorecard, 6, isSmallBatch);
|
|
3967
|
+
const nextDimension = inferActiveDimension(scorecard, isSmallBatch);
|
|
3968
|
+
const phase = inferPhase(scorecard, isSmallBatch);
|
|
3969
|
+
try {
|
|
3970
|
+
const fieldUpdates = {};
|
|
3971
|
+
const persist = (dim, field, minLen) => {
|
|
3972
|
+
if (activeDimension === dim && !refreshedData[field] && userInput.length > minLen) {
|
|
3973
|
+
fieldUpdates[field] = userInput;
|
|
3974
|
+
}
|
|
3975
|
+
};
|
|
3976
|
+
persist("problem_clarity", "problem", 50);
|
|
3977
|
+
persist("appetite", "appetite", 20);
|
|
3978
|
+
persist("architecture", "architecture", 50);
|
|
3979
|
+
if (overlapIds.length > 0 && !refreshedData._overlapIds) {
|
|
3980
|
+
fieldUpdates._overlapIds = overlapIds.join(",");
|
|
3981
|
+
}
|
|
3982
|
+
const govCount = chainSurfaced.filter(
|
|
3983
|
+
(e) => ["principles", "standards", "business-rules"].includes(e.collection)
|
|
3984
|
+
).length;
|
|
3985
|
+
if (govCount > 0) {
|
|
3986
|
+
fieldUpdates._governanceCount = govCount;
|
|
3987
|
+
}
|
|
3988
|
+
if (Object.keys(fieldUpdates).length > 0) {
|
|
3989
|
+
await mcpMutation("chain.updateEntry", {
|
|
3990
|
+
entryId: argBetId,
|
|
3991
|
+
data: fieldUpdates,
|
|
3992
|
+
changeNote: `Updated ${Object.keys(fieldUpdates).join(", ")} from shaping session`
|
|
3993
|
+
});
|
|
3994
|
+
await recordSessionActivity({ entryModified: refreshedBet?._id ?? betEntry._id });
|
|
3995
|
+
}
|
|
3996
|
+
} catch (updErr) {
|
|
3997
|
+
captureErrors.push({
|
|
3998
|
+
operation: "update",
|
|
3999
|
+
detail: `bet fields: ${updErr instanceof Error ? updErr.message : String(updErr)}`
|
|
4000
|
+
});
|
|
4001
|
+
}
|
|
4002
|
+
const alignment = [];
|
|
4003
|
+
const governanceCollections = /* @__PURE__ */ new Set(["principles", "standards", "business-rules", "strategy", "bets"]);
|
|
4004
|
+
for (const surfaced of chainSurfaced) {
|
|
4005
|
+
if (governanceCollections.has(surfaced.collection)) {
|
|
4006
|
+
alignment.push({ entryId: surfaced.entryId, relationship: `governs [${surfaced.collection}]` });
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
4009
|
+
const dims = activeDimensions(isSmallBatch);
|
|
4010
|
+
const captureReady = completed.length >= (isSmallBatch ? 3 : 4) && dims.every((d) => scorecard[d] > 0);
|
|
4011
|
+
const observation = dimensionResult.satisfied.length > 0 ? `${DIMENSION_LABELS[activeDimension]}: ${dimensionResult.satisfied.join("; ")}` : `${DIMENSION_LABELS[activeDimension]}: needs more detail`;
|
|
4012
|
+
const pushback = !captureArg && overlap.length > 0 ? `${overlap[0].summary} already exists on the Chain. How does your bet differ?` : dimensionResult.missing.length > 0 ? dimensionResult.missing[0] : "";
|
|
4013
|
+
const suggestedQuestion = dimensionResult.missing.length > 1 ? dimensionResult.missing[1] : dimensionResult.missing.length > 0 ? dimensionResult.missing[0] : `${DIMENSION_LABELS[nextDimension]} is next \u2014 ready to move on?`;
|
|
4014
|
+
const wsCtx = await getWorkspaceContext();
|
|
4015
|
+
const studioUrl = buildStudioUrl(wsCtx.workspaceSlug, argBetId);
|
|
4016
|
+
let buildContract;
|
|
4017
|
+
if (captureReady) {
|
|
4018
|
+
const contractCtx = {
|
|
4019
|
+
betEntryId: argBetId,
|
|
4020
|
+
governanceEntries: chainSurfaced.filter((e) => ["principles", "standards", "business-rules"].includes(e.collection)).map((e) => ({ entryId: e.entryId, name: e.relevance, collection: e.collection })),
|
|
4021
|
+
relatedDecisions: chainSurfaced.filter((e) => e.collection === "decisions").map((e) => ({ entryId: e.entryId, name: e.relevance })),
|
|
4022
|
+
relatedTensions: chainSurfaced.filter((e) => e.collection === "tensions").map((e) => ({ entryId: e.entryId, name: e.relevance }))
|
|
4023
|
+
};
|
|
4024
|
+
buildContract = generateBuildContract(contractCtx);
|
|
4025
|
+
}
|
|
4026
|
+
const suggested = suggestCaptures(scoringCtx, activeDimension);
|
|
4027
|
+
const sessionDrafts = await loadSessionDrafts(argBetId, refreshedBet?._id ?? betEntry._id);
|
|
4028
|
+
const betName = refreshedData.description ?? betEntry.name;
|
|
4029
|
+
const investigationBrief = buildInvestigationBrief(phase, betName, argBetId) ?? void 0;
|
|
4030
|
+
const response = {
|
|
4031
|
+
phase,
|
|
4032
|
+
phaseLabel: PHASE_LABELS[phase],
|
|
4033
|
+
scorecard,
|
|
4034
|
+
overlap,
|
|
4035
|
+
alignment,
|
|
4036
|
+
chainSurfaced,
|
|
4037
|
+
suggestedCaptures: suggested,
|
|
4038
|
+
sessionDrafts,
|
|
4039
|
+
coaching: {
|
|
4040
|
+
observation,
|
|
4041
|
+
missing: dimensionResult.missing,
|
|
4042
|
+
pushback,
|
|
4043
|
+
suggestedQuestion,
|
|
4044
|
+
captureReady,
|
|
4045
|
+
nextDimension
|
|
4046
|
+
},
|
|
4047
|
+
captured: {
|
|
4048
|
+
betEntryId: argBetId,
|
|
4049
|
+
studioUrl,
|
|
4050
|
+
entriesCreated,
|
|
4051
|
+
relationsCreated
|
|
4052
|
+
},
|
|
4053
|
+
captureErrors,
|
|
4054
|
+
navigation: {
|
|
4055
|
+
completedDimensions: completed,
|
|
4056
|
+
activeDimension,
|
|
4057
|
+
suggestedOrder: dims
|
|
4058
|
+
},
|
|
4059
|
+
buildContract,
|
|
4060
|
+
investigationBrief
|
|
4061
|
+
};
|
|
4062
|
+
const STRUCTURED_DIMS = {
|
|
4063
|
+
elements: "element",
|
|
4064
|
+
risks: "risk",
|
|
4065
|
+
boundaries: "noGo"
|
|
4066
|
+
};
|
|
4067
|
+
const expectedCaptureType = STRUCTURED_DIMS[activeDimension];
|
|
4068
|
+
const dataLossWarning = expectedCaptureType && !captureArg ? `**WARNING:** You discussed ${activeDimension} but did not use the \`capture\` parameter \u2014 nothing was saved to the bet. Re-send with \`capture={type:"${expectedCaptureType}", name:"...", description:"..."}\` to persist this.` : "";
|
|
4069
|
+
const directive = buildDirective(scorecard, phase, argBetId, captureReady, activeDimension, nextDimension);
|
|
4070
|
+
const summaryParts = [
|
|
4071
|
+
`# ${PHASE_LABELS[phase]} \u2014 ${DIMENSION_LABELS[activeDimension]}`,
|
|
4072
|
+
"",
|
|
4073
|
+
dataLossWarning,
|
|
4074
|
+
`**Score:** ${scorecard[activeDimension]}/10 | **Phase:** ${PHASE_LABELS[phase]}`,
|
|
4075
|
+
observation,
|
|
4076
|
+
pushback ? `**Pushback:** ${pushback}` : "",
|
|
4077
|
+
`**Next:** ${suggestedQuestion}`,
|
|
4078
|
+
captureReady ? "\n**Capture ready** \u2014 the bet has enough shape to finalize." : "",
|
|
4079
|
+
entriesCreated.length > 0 ? `**Captured:** ${entriesCreated.join(", ")}` : "",
|
|
4080
|
+
suggested.length > 0 ? `**Suggest capturing:** ${suggested.map((s) => `${s.type}: "${s.name}"`).join("; ")}` : "",
|
|
4081
|
+
sessionDrafts.length > 0 ? `**Session drafts (${sessionDrafts.length}):** ${sessionDrafts.map((d) => `\`${d.entryId}\` ${d.name}`).join(", ")}` : "",
|
|
4082
|
+
captureErrors.length > 0 ? `**Warnings:** ${captureErrors.map((e) => e.detail).join("; ")}` : "",
|
|
4083
|
+
directive ? `
|
|
4084
|
+
---
|
|
4085
|
+
## Agent Directive
|
|
4086
|
+
${directive}` : "",
|
|
4087
|
+
investigationBrief ? `
|
|
4088
|
+
## Investigation Brief
|
|
4089
|
+
${investigationBrief.tasks.map((t) => `- **${t.target}:** ${t.query} \u2014 ${t.purpose}`).join("\n")}
|
|
4090
|
+
|
|
4091
|
+
**Proposal guidance:** ${investigationBrief.proposalGuidance}
|
|
4092
|
+
**Reaction prompt:** ${investigationBrief.reactionPrompt}` : ""
|
|
4093
|
+
];
|
|
4094
|
+
return {
|
|
4095
|
+
content: [{ type: "text", text: summaryParts.filter(Boolean).join("\n") }],
|
|
4096
|
+
structuredContent: response
|
|
4097
|
+
};
|
|
4098
|
+
}
|
|
4099
|
+
async function handleScore(args) {
|
|
4100
|
+
const betId = args.betEntryId;
|
|
4101
|
+
if (!betId) {
|
|
4102
|
+
return {
|
|
4103
|
+
content: [{ type: "text", text: "`betEntryId` is required for score action." }]
|
|
4104
|
+
};
|
|
4105
|
+
}
|
|
4106
|
+
const betEntry = await loadBetEntry(betId);
|
|
4107
|
+
if (!betEntry) {
|
|
4108
|
+
return {
|
|
4109
|
+
content: [{ type: "text", text: `Bet \`${betId}\` not found.` }]
|
|
4110
|
+
};
|
|
4111
|
+
}
|
|
4112
|
+
const constellation = await loadConstellationState(betId, betEntry._id);
|
|
4113
|
+
const betData = betEntry.data ?? {};
|
|
4114
|
+
const isSmallBatch = detectSmallBatch(betData.appetite ?? "");
|
|
4115
|
+
const cachedOverlapIds = typeof betData._overlapIds === "string" ? betData._overlapIds.split(",").filter(Boolean) : [];
|
|
4116
|
+
const cachedGovCount = typeof betData._governanceCount === "number" ? betData._governanceCount : 0;
|
|
4117
|
+
const syntheticChainSurfaced = Array.from({ length: cachedGovCount }, () => ({ collection: "principles" }));
|
|
4118
|
+
const scoringCtx = buildScoringContext("", betData, constellation, cachedOverlapIds, syntheticChainSurfaced);
|
|
4119
|
+
const scorecard = buildScorecard(scoringCtx);
|
|
4120
|
+
if (isSmallBatch) scorecard.architecture = -1;
|
|
4121
|
+
const dims = activeDimensions(isSmallBatch);
|
|
4122
|
+
const completed = completedDimensions(scorecard, 6, isSmallBatch);
|
|
4123
|
+
const active = inferActiveDimension(scorecard, isSmallBatch);
|
|
4124
|
+
const phase = inferPhase(scorecard, isSmallBatch);
|
|
4125
|
+
const lines = [
|
|
4126
|
+
`# Scorecard \u2014 ${betEntry.name}`,
|
|
4127
|
+
`**Phase:** ${PHASE_LABELS[phase]}`,
|
|
4128
|
+
"",
|
|
4129
|
+
"| Dimension | Score | Status |",
|
|
4130
|
+
"|-----------|-------|--------|"
|
|
4131
|
+
];
|
|
4132
|
+
for (const dim of dims) {
|
|
4133
|
+
const label = DIMENSION_LABELS[dim];
|
|
4134
|
+
const score = scorecard[dim];
|
|
4135
|
+
const status = score >= 6 ? "done" : score > 0 ? "in progress" : "not started";
|
|
4136
|
+
const marker = dim === active ? " **<-**" : "";
|
|
4137
|
+
lines.push(`| ${label} | ${score}/10 | ${status}${marker} |`);
|
|
4138
|
+
}
|
|
4139
|
+
lines.push("");
|
|
4140
|
+
lines.push(`Completed: ${completed.length}/${dims.length}`);
|
|
4141
|
+
lines.push(`Next dimension: ${DIMENSION_LABELS[active]}`);
|
|
4142
|
+
const sessionDrafts = await loadSessionDrafts(betId, betEntry._id);
|
|
4143
|
+
if (sessionDrafts.length > 0) {
|
|
4144
|
+
lines.push("");
|
|
4145
|
+
lines.push(`**Session drafts (${sessionDrafts.length}):** ${sessionDrafts.map((d) => `\`${d.entryId}\` ${d.name}`).join(", ")}`);
|
|
4146
|
+
}
|
|
4147
|
+
return {
|
|
4148
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
4149
|
+
structuredContent: { scorecard, phase, phaseLabel: PHASE_LABELS[phase], completedDimensions: completed, nextDimension: active, sessionDrafts }
|
|
4150
|
+
};
|
|
4151
|
+
}
|
|
4152
|
+
async function handleResume(args) {
|
|
4153
|
+
const betId = args.betEntryId;
|
|
4154
|
+
if (!betId) {
|
|
4155
|
+
return {
|
|
4156
|
+
content: [{ type: "text", text: "`betEntryId` is required for resume action." }]
|
|
4157
|
+
};
|
|
4158
|
+
}
|
|
4159
|
+
const betEntry = await loadBetEntry(betId);
|
|
4160
|
+
if (!betEntry) {
|
|
4161
|
+
return {
|
|
4162
|
+
content: [{ type: "text", text: `Bet \`${betId}\` not found. Cannot resume.` }]
|
|
4163
|
+
};
|
|
4164
|
+
}
|
|
4165
|
+
const constellation = await loadConstellationState(betId, betEntry._id);
|
|
4166
|
+
const betData = betEntry.data ?? {};
|
|
4167
|
+
const isSmallBatch = detectSmallBatch(betData.appetite ?? "");
|
|
4168
|
+
const cachedOverlapIds = typeof betData._overlapIds === "string" ? betData._overlapIds.split(",").filter(Boolean) : [];
|
|
4169
|
+
const cachedGovCount = typeof betData._governanceCount === "number" ? betData._governanceCount : 0;
|
|
4170
|
+
const syntheticChainSurfaced = Array.from({ length: cachedGovCount }, () => ({ collection: "principles" }));
|
|
4171
|
+
const scoringCtx = buildScoringContext("", betData, constellation, cachedOverlapIds, syntheticChainSurfaced);
|
|
4172
|
+
const scorecard = buildScorecard(scoringCtx);
|
|
4173
|
+
if (isSmallBatch) scorecard.architecture = -1;
|
|
4174
|
+
const dims = activeDimensions(isSmallBatch);
|
|
4175
|
+
const completed = completedDimensions(scorecard, 6, isSmallBatch);
|
|
4176
|
+
const active = inferActiveDimension(scorecard, isSmallBatch);
|
|
4177
|
+
const phase = inferPhase(scorecard, isSmallBatch);
|
|
4178
|
+
const wsCtx = await getWorkspaceContext();
|
|
4179
|
+
const studioUrl = buildStudioUrl(wsCtx.workspaceSlug, betId);
|
|
4180
|
+
const constellationSummary = [];
|
|
4181
|
+
if (constellation.elementCount > 0) constellationSummary.push(`${constellation.elementCount} element(s)`);
|
|
4182
|
+
if (constellation.riskCount > 0) constellationSummary.push(`${constellation.riskCount} risk(s)`);
|
|
4183
|
+
if (constellation.decisionCount > 0) constellationSummary.push(`${constellation.decisionCount} decision(s)`);
|
|
4184
|
+
const captureReady = completed.length >= (isSmallBatch ? 3 : 4) && dims.every((d) => scorecard[d] > 0);
|
|
4185
|
+
const sessionDrafts = await loadSessionDrafts(betId, betEntry._id);
|
|
4186
|
+
const response = {
|
|
4187
|
+
phase,
|
|
4188
|
+
phaseLabel: PHASE_LABELS[phase],
|
|
4189
|
+
scorecard,
|
|
4190
|
+
overlap: [],
|
|
4191
|
+
alignment: [],
|
|
4192
|
+
chainSurfaced: [],
|
|
4193
|
+
suggestedCaptures: [],
|
|
4194
|
+
sessionDrafts,
|
|
4195
|
+
coaching: {
|
|
4196
|
+
observation: `Resuming session for "${betEntry.name}". ${constellationSummary.length > 0 ? `Constellation: ${constellationSummary.join(", ")}.` : "No constellation entries yet."}`,
|
|
4197
|
+
missing: dims.filter((d) => scorecard[d] < 6).map((d) => `${DIMENSION_LABELS[d]} needs work (${scorecard[d]}/10)`),
|
|
4198
|
+
pushback: "",
|
|
4199
|
+
suggestedQuestion: `Continue with ${DIMENSION_LABELS[active]}?`,
|
|
4200
|
+
captureReady,
|
|
4201
|
+
nextDimension: active
|
|
4202
|
+
},
|
|
4203
|
+
captured: {
|
|
4204
|
+
betEntryId: betId,
|
|
4205
|
+
studioUrl,
|
|
4206
|
+
entriesCreated: [],
|
|
4207
|
+
relationsCreated: 0
|
|
4208
|
+
},
|
|
4209
|
+
captureErrors: [],
|
|
4210
|
+
navigation: {
|
|
4211
|
+
completedDimensions: completed,
|
|
4212
|
+
activeDimension: active,
|
|
4213
|
+
suggestedOrder: dims
|
|
4214
|
+
}
|
|
4215
|
+
};
|
|
4216
|
+
const directive = buildDirective(scorecard, phase, betId, captureReady, active, active);
|
|
4217
|
+
const scorecardLines = dims.map((dim) => {
|
|
4218
|
+
const s = scorecard[dim];
|
|
4219
|
+
const bar = s > 0 ? "\u2588".repeat(s) + "\u2591".repeat(10 - s) : "\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591";
|
|
4220
|
+
const arrow = dim === active ? " \u25C0 next" : "";
|
|
4221
|
+
return `| ${DIMENSION_LABELS[dim]} | ${bar} ${s}/10${arrow} |`;
|
|
4222
|
+
});
|
|
4223
|
+
const output = [
|
|
4224
|
+
`# Session Resumed \u2014 ${betEntry.name}`,
|
|
4225
|
+
"",
|
|
4226
|
+
`**Bet:** \`${betId}\``,
|
|
4227
|
+
`**Studio:** ${studioUrl}`,
|
|
4228
|
+
`**Status:** ${betEntry.status}`,
|
|
4229
|
+
`**Phase:** ${PHASE_LABELS[phase]}`,
|
|
4230
|
+
constellationSummary.length > 0 ? `**Constellation:** ${constellationSummary.join(", ")}` : "**Constellation:** Empty \u2014 no entries captured yet.",
|
|
4231
|
+
sessionDrafts.length > 0 ? `**Session drafts (${sessionDrafts.length}):** ${sessionDrafts.map((d) => `\`${d.entryId}\` ${d.name}`).join(", ")}` : "",
|
|
4232
|
+
"",
|
|
4233
|
+
"## Scorecard",
|
|
4234
|
+
"| Dimension | Score |",
|
|
4235
|
+
"|---|---|",
|
|
4236
|
+
...scorecardLines,
|
|
4237
|
+
"",
|
|
4238
|
+
`Continue with ${DIMENSION_LABELS[active]}?`,
|
|
4239
|
+
directive ? `
|
|
4240
|
+
---
|
|
4241
|
+
## Agent Directive
|
|
4242
|
+
${directive}` : ""
|
|
4243
|
+
].filter(Boolean).join("\n");
|
|
4244
|
+
return {
|
|
4245
|
+
content: [{ type: "text", text: output }],
|
|
4246
|
+
structuredContent: response
|
|
4247
|
+
};
|
|
4248
|
+
}
|
|
4249
|
+
|
|
2501
4250
|
// src/tools/verify.ts
|
|
2502
4251
|
import { existsSync, readFileSync } from "fs";
|
|
2503
4252
|
import { resolve } from "path";
|
|
2504
|
-
import { z as
|
|
4253
|
+
import { z as z13 } from "zod";
|
|
2505
4254
|
function resolveProjectRoot() {
|
|
2506
4255
|
const candidates = [
|
|
2507
4256
|
process.env.WORKSPACE_PATH,
|
|
@@ -2645,9 +4394,9 @@ function formatTrustReport(collection, entryCount, mappings, refs, fixes, mode,
|
|
|
2645
4394
|
lines.push("", "---", `_Schema: ${schemaTableCount} tables parsed from convex/schema.ts. Project root: ${projectRoot}_`);
|
|
2646
4395
|
return lines.join("\n");
|
|
2647
4396
|
}
|
|
2648
|
-
var verifySchema =
|
|
2649
|
-
collection:
|
|
2650
|
-
mode:
|
|
4397
|
+
var verifySchema = z13.object({
|
|
4398
|
+
collection: z13.string().default("glossary").describe("Collection slug to verify (default: glossary)"),
|
|
4399
|
+
mode: z13.enum(["report", "fix"]).default("report").describe("'report' = read-only trust report. 'fix' = also update drifted codeMapping statuses.")
|
|
2651
4400
|
});
|
|
2652
4401
|
function registerVerifyTools(server) {
|
|
2653
4402
|
const verifyTool = server.registerTool(
|
|
@@ -2789,7 +4538,7 @@ function registerVerifyTools(server) {
|
|
|
2789
4538
|
}
|
|
2790
4539
|
|
|
2791
4540
|
// src/tools/start.ts
|
|
2792
|
-
import { z as
|
|
4541
|
+
import { z as z14 } from "zod";
|
|
2793
4542
|
|
|
2794
4543
|
// src/presets/collections.ts
|
|
2795
4544
|
var GOVERNANCE_CORE = [
|
|
@@ -3073,8 +4822,8 @@ function buildOperatingProtocol(workspaceStandards) {
|
|
|
3073
4822
|
}
|
|
3074
4823
|
|
|
3075
4824
|
// src/tools/start.ts
|
|
3076
|
-
var startSchema =
|
|
3077
|
-
preset:
|
|
4825
|
+
var startSchema = z14.object({
|
|
4826
|
+
preset: z14.string().optional().describe(
|
|
3078
4827
|
"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."
|
|
3079
4828
|
)
|
|
3080
4829
|
});
|
|
@@ -3165,7 +4914,6 @@ Available presets: ${listPresets().map((p) => `\`${p.id}\``).join(", ")}`;
|
|
|
3165
4914
|
icon: col.icon,
|
|
3166
4915
|
idPrefix: col.idPrefix,
|
|
3167
4916
|
fields: col.fields,
|
|
3168
|
-
stratum: "working",
|
|
3169
4917
|
createdBy: "preset:" + presetId
|
|
3170
4918
|
});
|
|
3171
4919
|
seeded.push(col.name);
|
|
@@ -3411,9 +5159,9 @@ async function buildOrientResponse(wsCtx, agentSessionId, errors) {
|
|
|
3411
5159
|
}
|
|
3412
5160
|
|
|
3413
5161
|
// src/tools/usage.ts
|
|
3414
|
-
import { z as
|
|
3415
|
-
var usageSummarySchema =
|
|
3416
|
-
periodDays:
|
|
5162
|
+
import { z as z15 } from "zod";
|
|
5163
|
+
var usageSummarySchema = z15.object({
|
|
5164
|
+
periodDays: z15.number().min(1).max(90).optional().describe("Number of days to look back (default 30, max 90)")
|
|
3417
5165
|
});
|
|
3418
5166
|
function registerUsageTools(server) {
|
|
3419
5167
|
server.registerTool(
|
|
@@ -3476,43 +5224,43 @@ function registerUsageTools(server) {
|
|
|
3476
5224
|
}
|
|
3477
5225
|
|
|
3478
5226
|
// src/tools/gitchain.ts
|
|
3479
|
-
import { z as
|
|
3480
|
-
var chainSchema =
|
|
3481
|
-
action:
|
|
3482
|
-
chainEntryId:
|
|
3483
|
-
title:
|
|
3484
|
-
chainTypeId:
|
|
3485
|
-
description:
|
|
3486
|
-
linkId:
|
|
3487
|
-
content:
|
|
3488
|
-
status:
|
|
3489
|
-
author:
|
|
5227
|
+
import { z as z16 } from "zod";
|
|
5228
|
+
var chainSchema = z16.object({
|
|
5229
|
+
action: z16.enum(["create", "get", "list", "edit"]).describe("Action: create a process, get process details, list all processes, or edit a process link"),
|
|
5230
|
+
chainEntryId: z16.string().optional().describe("Chain entry ID (required for get/edit)"),
|
|
5231
|
+
title: z16.string().optional().describe("Process title (required for create)"),
|
|
5232
|
+
chainTypeId: z16.string().optional().default("strategy-coherence").describe("Process template slug for create: 'strategy-coherence', 'idm-proposal', or any custom template slug"),
|
|
5233
|
+
description: z16.string().optional().describe("Description (for create)"),
|
|
5234
|
+
linkId: z16.string().optional().describe("Link to edit (for edit action): problem, insight, choice, action, outcome"),
|
|
5235
|
+
content: z16.string().optional().describe("New content for the link (for edit action)"),
|
|
5236
|
+
status: z16.string().optional().describe("Filter by status for list: 'draft' or 'active'"),
|
|
5237
|
+
author: z16.string().optional().describe("Who is performing the action. Defaults to 'mcp'.")
|
|
3490
5238
|
});
|
|
3491
|
-
var chainVersionSchema =
|
|
3492
|
-
action:
|
|
3493
|
-
chainEntryId:
|
|
3494
|
-
commitMessage:
|
|
3495
|
-
versionA:
|
|
3496
|
-
versionB:
|
|
3497
|
-
toVersion:
|
|
3498
|
-
author:
|
|
5239
|
+
var chainVersionSchema = z16.object({
|
|
5240
|
+
action: z16.enum(["commit", "list", "diff", "revert", "history"]).describe("Action: commit a snapshot, list commits, diff two versions, revert to a version, or view history"),
|
|
5241
|
+
chainEntryId: z16.string().describe("The chain's entry ID"),
|
|
5242
|
+
commitMessage: z16.string().optional().describe("Commit message (required for commit). Convention: type(link): description"),
|
|
5243
|
+
versionA: z16.number().optional().describe("Earlier version for diff"),
|
|
5244
|
+
versionB: z16.number().optional().describe("Later version for diff"),
|
|
5245
|
+
toVersion: z16.number().optional().describe("Version number to revert to"),
|
|
5246
|
+
author: z16.string().optional().describe("Who is performing the action. Defaults to 'mcp'.")
|
|
3499
5247
|
});
|
|
3500
|
-
var chainBranchSchema =
|
|
3501
|
-
action:
|
|
3502
|
-
chainEntryId:
|
|
3503
|
-
branchName:
|
|
3504
|
-
strategy:
|
|
3505
|
-
author:
|
|
5248
|
+
var chainBranchSchema = z16.object({
|
|
5249
|
+
action: z16.enum(["create", "list", "merge", "conflicts"]).describe("Action: create a branch, list branches, merge a branch, or check for conflicts"),
|
|
5250
|
+
chainEntryId: z16.string().describe("The chain's entry ID"),
|
|
5251
|
+
branchName: z16.string().optional().describe("Branch name (required for merge/conflicts, optional for create)"),
|
|
5252
|
+
strategy: z16.enum(["merge_commit", "squash"]).optional().describe("Merge strategy: 'merge_commit' (default) or 'squash'"),
|
|
5253
|
+
author: z16.string().optional().describe("Who is performing the action. Defaults to 'mcp'.")
|
|
3506
5254
|
});
|
|
3507
|
-
var chainReviewSchema =
|
|
3508
|
-
action:
|
|
3509
|
-
chainEntryId:
|
|
3510
|
-
commitMessage:
|
|
3511
|
-
versionNumber:
|
|
3512
|
-
linkId:
|
|
3513
|
-
body:
|
|
3514
|
-
commentId:
|
|
3515
|
-
author:
|
|
5255
|
+
var chainReviewSchema = z16.object({
|
|
5256
|
+
action: z16.enum(["gate", "comment", "resolve-comment", "list-comments"]).describe("Action: run coherence gate, add a comment, resolve a comment, or list comments"),
|
|
5257
|
+
chainEntryId: z16.string().describe("The chain's entry ID"),
|
|
5258
|
+
commitMessage: z16.string().optional().describe("Commit message to lint (for gate action)"),
|
|
5259
|
+
versionNumber: z16.number().optional().describe("Version to comment on or list comments for"),
|
|
5260
|
+
linkId: z16.string().optional().describe("Link this comment targets (optional for comment)"),
|
|
5261
|
+
body: z16.string().optional().describe("Comment text (required for comment action)"),
|
|
5262
|
+
commentId: z16.string().optional().describe("Comment ID (required for resolve-comment)"),
|
|
5263
|
+
author: z16.string().optional().describe("Who is performing the action. Defaults to 'mcp'.")
|
|
3516
5264
|
});
|
|
3517
5265
|
function linkSummary(links) {
|
|
3518
5266
|
return Object.entries(links).map(([id, content]) => {
|
|
@@ -3895,38 +5643,38 @@ ${formatted}`
|
|
|
3895
5643
|
}
|
|
3896
5644
|
|
|
3897
5645
|
// src/tools/maps.ts
|
|
3898
|
-
import { z as
|
|
3899
|
-
var createAudienceMapSetSchema =
|
|
3900
|
-
audienceEntryId:
|
|
5646
|
+
import { z as z17 } from "zod";
|
|
5647
|
+
var createAudienceMapSetSchema = z17.object({
|
|
5648
|
+
audienceEntryId: z17.string().describe("Entry ID of the audience (e.g. STR-fb7hje)")
|
|
3901
5649
|
});
|
|
3902
|
-
var mapSchema =
|
|
3903
|
-
action:
|
|
3904
|
-
mapEntryId:
|
|
3905
|
-
title:
|
|
3906
|
-
templateId:
|
|
3907
|
-
description:
|
|
3908
|
-
slotIds:
|
|
3909
|
-
status:
|
|
5650
|
+
var mapSchema = z17.object({
|
|
5651
|
+
action: z17.enum(["create", "get", "list"]).describe("Action: create a map, get map details, or list all maps"),
|
|
5652
|
+
mapEntryId: z17.string().optional().describe("Map entry ID (for get)"),
|
|
5653
|
+
title: z17.string().optional().describe("Map title (for create)"),
|
|
5654
|
+
templateId: z17.string().optional().default("lean-canvas").describe("Template slug for create: 'lean-canvas' or any composed template"),
|
|
5655
|
+
description: z17.string().optional().describe("Description (for create)"),
|
|
5656
|
+
slotIds: z17.array(z17.string()).optional().describe("Slot IDs to initialize (for create; auto-populated from template if omitted)"),
|
|
5657
|
+
status: z17.string().optional().describe("Filter by status for list")
|
|
3910
5658
|
});
|
|
3911
|
-
var mapSlotSchema =
|
|
3912
|
-
action:
|
|
3913
|
-
mapEntryId:
|
|
3914
|
-
slotId:
|
|
3915
|
-
ingredientEntryId:
|
|
3916
|
-
newIngredientEntryId:
|
|
3917
|
-
label:
|
|
3918
|
-
author:
|
|
5659
|
+
var mapSlotSchema = z17.object({
|
|
5660
|
+
action: z17.enum(["add", "remove", "replace", "list"]).describe("Action: add/remove/replace an ingredient in a slot, or list slot contents"),
|
|
5661
|
+
mapEntryId: z17.string().describe("Map entry ID"),
|
|
5662
|
+
slotId: z17.string().optional().describe("Slot ID (e.g. 'problem', 'customer-segments')"),
|
|
5663
|
+
ingredientEntryId: z17.string().optional().describe("Ingredient entry ID to add/remove"),
|
|
5664
|
+
newIngredientEntryId: z17.string().optional().describe("New ingredient entry ID (for replace)"),
|
|
5665
|
+
label: z17.string().optional().describe("Display label override"),
|
|
5666
|
+
author: z17.string().optional().describe("Who is performing the action")
|
|
3919
5667
|
});
|
|
3920
|
-
var mapVersionSchema =
|
|
3921
|
-
action:
|
|
3922
|
-
mapEntryId:
|
|
3923
|
-
commitMessage:
|
|
3924
|
-
author:
|
|
5668
|
+
var mapVersionSchema = z17.object({
|
|
5669
|
+
action: z17.enum(["commit", "list", "history"]).describe("Action: commit the map, list commits, or view commit history"),
|
|
5670
|
+
mapEntryId: z17.string().describe("Map entry ID"),
|
|
5671
|
+
commitMessage: z17.string().optional().describe("Commit message (for commit action)"),
|
|
5672
|
+
author: z17.string().optional().describe("Who is committing")
|
|
3925
5673
|
});
|
|
3926
|
-
var mapSuggestSchema =
|
|
3927
|
-
mapEntryId:
|
|
3928
|
-
slotId:
|
|
3929
|
-
query:
|
|
5674
|
+
var mapSuggestSchema = z17.object({
|
|
5675
|
+
mapEntryId: z17.string().describe("Map entry ID to suggest ingredients for"),
|
|
5676
|
+
slotId: z17.string().optional().describe("Specific slot to find ingredients for (or all empty slots)"),
|
|
5677
|
+
query: z17.string().optional().describe("Optional search query to narrow ingredient suggestions")
|
|
3930
5678
|
});
|
|
3931
5679
|
function slotSummary(slots) {
|
|
3932
5680
|
return Object.entries(slots).map(([id, refs]) => {
|
|
@@ -4352,7 +6100,7 @@ Use \`map-slot action=add mapEntryId="${mapEntryId}" slotId="..." ingredientEntr
|
|
|
4352
6100
|
// src/tools/architecture.ts
|
|
4353
6101
|
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
|
|
4354
6102
|
import { resolve as resolve2, relative, dirname, normalize } from "path";
|
|
4355
|
-
import { z as
|
|
6103
|
+
import { z as z18 } from "zod";
|
|
4356
6104
|
var COLLECTION_SLUG = "architecture";
|
|
4357
6105
|
var COLLECTION_FIELDS = [
|
|
4358
6106
|
{ key: "archType", label: "Architecture Type", type: "select", required: true, options: ["template", "layer", "node", "flow"], searchable: true },
|
|
@@ -4509,14 +6257,14 @@ var SEED_FLOWS = [
|
|
|
4509
6257
|
{ 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" } },
|
|
4510
6258
|
{ 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" } }
|
|
4511
6259
|
];
|
|
4512
|
-
var architectureSchema =
|
|
4513
|
-
action:
|
|
4514
|
-
template:
|
|
4515
|
-
layer:
|
|
4516
|
-
flow:
|
|
6260
|
+
var architectureSchema = z18.object({
|
|
6261
|
+
action: z18.enum(["show", "explore", "flow"]).describe("Action: show full map, explore a layer, or visualize a flow"),
|
|
6262
|
+
template: z18.string().optional().describe("Template entry ID to filter by (for show)"),
|
|
6263
|
+
layer: z18.string().optional().describe("Layer name or entry ID (for explore), e.g. 'Core' or 'ARCH-layer-core'"),
|
|
6264
|
+
flow: z18.string().optional().describe("Flow name or entry ID (for flow), e.g. 'Smart Capture Flow'")
|
|
4517
6265
|
});
|
|
4518
|
-
var architectureAdminSchema =
|
|
4519
|
-
action:
|
|
6266
|
+
var architectureAdminSchema = z18.object({
|
|
6267
|
+
action: z18.enum(["seed", "check"]).describe("Action: seed default architecture data, or check dependency health")
|
|
4520
6268
|
});
|
|
4521
6269
|
function registerArchitectureTools(server) {
|
|
4522
6270
|
server.registerTool(
|
|
@@ -5191,45 +6939,45 @@ ${logLines.join("\n")}` }],
|
|
|
5191
6939
|
};
|
|
5192
6940
|
}
|
|
5193
6941
|
var HEALTH_ACTIONS = ["check", "whoami", "status", "audit", "self-test"];
|
|
5194
|
-
var healthSchema =
|
|
5195
|
-
action:
|
|
6942
|
+
var healthSchema = z19.object({
|
|
6943
|
+
action: z19.enum(HEALTH_ACTIONS).describe(
|
|
5196
6944
|
"'check': connectivity and workspace stats. 'whoami': session identity. 'status': workspace readiness. 'audit': session audit log. 'self-test': validate all tool schemas."
|
|
5197
6945
|
),
|
|
5198
|
-
limit:
|
|
6946
|
+
limit: z19.number().min(1).max(50).default(20).optional().describe("For audit: how many recent calls to show (max 50)")
|
|
5199
6947
|
});
|
|
5200
|
-
var orientSchema =
|
|
5201
|
-
mode:
|
|
5202
|
-
task:
|
|
6948
|
+
var orientSchema = z19.object({
|
|
6949
|
+
mode: z19.enum(["full", "brief"]).optional().default("full").describe("full = full context (default). brief = compact summary for mid-session re-orientation."),
|
|
6950
|
+
task: z19.string().optional().describe("Natural-language task description for task-scoped context. When provided, orient returns scored, relevant entries for the task.")
|
|
5203
6951
|
});
|
|
5204
|
-
var healthCheckOutputSchema =
|
|
5205
|
-
healthy:
|
|
5206
|
-
collections:
|
|
5207
|
-
entries:
|
|
5208
|
-
latencyMs:
|
|
5209
|
-
workspace:
|
|
6952
|
+
var healthCheckOutputSchema = z19.object({
|
|
6953
|
+
healthy: z19.boolean(),
|
|
6954
|
+
collections: z19.number(),
|
|
6955
|
+
entries: z19.number(),
|
|
6956
|
+
latencyMs: z19.number(),
|
|
6957
|
+
workspace: z19.string()
|
|
5210
6958
|
});
|
|
5211
|
-
var healthStatusOutputSchema =
|
|
5212
|
-
readinessScore:
|
|
5213
|
-
activeEntries:
|
|
5214
|
-
totalRelations:
|
|
5215
|
-
orphanedEntries:
|
|
5216
|
-
gaps:
|
|
6959
|
+
var healthStatusOutputSchema = z19.object({
|
|
6960
|
+
readinessScore: z19.number(),
|
|
6961
|
+
activeEntries: z19.number(),
|
|
6962
|
+
totalRelations: z19.number(),
|
|
6963
|
+
orphanedEntries: z19.number(),
|
|
6964
|
+
gaps: z19.array(z19.object({ label: z19.string(), guidance: z19.string() }))
|
|
5217
6965
|
});
|
|
5218
|
-
var healthAuditOutputSchema =
|
|
5219
|
-
totalCalls:
|
|
5220
|
-
calls:
|
|
5221
|
-
tool:
|
|
5222
|
-
action:
|
|
5223
|
-
timestamp:
|
|
5224
|
-
durationMs:
|
|
6966
|
+
var healthAuditOutputSchema = z19.object({
|
|
6967
|
+
totalCalls: z19.number(),
|
|
6968
|
+
calls: z19.array(z19.object({
|
|
6969
|
+
tool: z19.string(),
|
|
6970
|
+
action: z19.string().optional(),
|
|
6971
|
+
timestamp: z19.string(),
|
|
6972
|
+
durationMs: z19.number().optional()
|
|
5225
6973
|
}))
|
|
5226
6974
|
});
|
|
5227
|
-
var healthWhoamiOutputSchema =
|
|
5228
|
-
workspaceId:
|
|
5229
|
-
workspaceName:
|
|
5230
|
-
scope:
|
|
5231
|
-
sessionId:
|
|
5232
|
-
oriented:
|
|
6975
|
+
var healthWhoamiOutputSchema = z19.object({
|
|
6976
|
+
workspaceId: z19.string(),
|
|
6977
|
+
workspaceName: z19.string(),
|
|
6978
|
+
scope: z19.string(),
|
|
6979
|
+
sessionId: z19.union([z19.string(), z19.null()]),
|
|
6980
|
+
oriented: z19.boolean()
|
|
5233
6981
|
});
|
|
5234
6982
|
var ALL_TOOL_SCHEMAS = [
|
|
5235
6983
|
{ name: "entries", schema: entriesSchema },
|
|
@@ -5262,16 +7010,17 @@ var ALL_TOOL_SCHEMAS = [
|
|
|
5262
7010
|
{ name: "map-version", schema: mapVersionSchema },
|
|
5263
7011
|
{ name: "map-suggest", schema: mapSuggestSchema },
|
|
5264
7012
|
{ name: "architecture", schema: architectureSchema },
|
|
5265
|
-
{ name: "architecture-admin", schema: architectureAdminSchema }
|
|
7013
|
+
{ name: "architecture-admin", schema: architectureAdminSchema },
|
|
7014
|
+
{ name: "facilitate", schema: facilitateSchema }
|
|
5266
7015
|
];
|
|
5267
|
-
var selfTestOutputSchema =
|
|
5268
|
-
passed:
|
|
5269
|
-
failed:
|
|
5270
|
-
total:
|
|
5271
|
-
results:
|
|
5272
|
-
tool:
|
|
5273
|
-
valid:
|
|
5274
|
-
error:
|
|
7016
|
+
var selfTestOutputSchema = z19.object({
|
|
7017
|
+
passed: z19.number(),
|
|
7018
|
+
failed: z19.number(),
|
|
7019
|
+
total: z19.number(),
|
|
7020
|
+
results: z19.array(z19.object({
|
|
7021
|
+
tool: z19.string(),
|
|
7022
|
+
valid: z19.boolean(),
|
|
7023
|
+
error: z19.string().optional()
|
|
5275
7024
|
}))
|
|
5276
7025
|
});
|
|
5277
7026
|
function handleSelfTest() {
|
|
@@ -5354,6 +7103,12 @@ function registerHealthTools(server) {
|
|
|
5354
7103
|
async ({ mode = "full", task } = {}) => {
|
|
5355
7104
|
const errors = [];
|
|
5356
7105
|
const agentSessionId = getAgentSessionId();
|
|
7106
|
+
if (isSessionOriented() && mode === "brief" && !task) {
|
|
7107
|
+
return {
|
|
7108
|
+
content: [{ type: "text", text: "Already oriented. Session active, writes unlocked. Use `orient mode='full'` or `orient task='...'` for full context." }],
|
|
7109
|
+
structuredContent: { alreadyOriented: true, sessionId: agentSessionId }
|
|
7110
|
+
};
|
|
7111
|
+
}
|
|
5357
7112
|
let wsCtx = null;
|
|
5358
7113
|
try {
|
|
5359
7114
|
wsCtx = await getWorkspaceContext();
|
|
@@ -5624,7 +7379,7 @@ function registerHealthTools(server) {
|
|
|
5624
7379
|
const standardsForProtocol = (orientEntries.standards ?? []).map((e) => ({
|
|
5625
7380
|
entryId: e.entryId,
|
|
5626
7381
|
name: e.name,
|
|
5627
|
-
description: typeof e.
|
|
7382
|
+
description: typeof e.preview === "string" ? e.preview : void 0
|
|
5628
7383
|
}));
|
|
5629
7384
|
lines.push(...buildOperatingProtocol(standardsForProtocol));
|
|
5630
7385
|
}
|
|
@@ -6092,12 +7847,12 @@ ${entry.labels.map((l) => `- ${l.name ?? l.slug}`).join("\n")}`);
|
|
|
6092
7847
|
}
|
|
6093
7848
|
|
|
6094
7849
|
// src/prompts/index.ts
|
|
6095
|
-
import { z as
|
|
7850
|
+
import { z as z20 } from "zod";
|
|
6096
7851
|
function registerPrompts(server) {
|
|
6097
7852
|
server.prompt(
|
|
6098
7853
|
"review-against-rules",
|
|
6099
7854
|
"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.",
|
|
6100
|
-
{ domain:
|
|
7855
|
+
{ domain: z20.string().describe("Business rule domain (e.g. 'Identity & Access', 'Governance & Decision-Making')") },
|
|
6101
7856
|
async ({ domain }) => {
|
|
6102
7857
|
const entries = await mcpQuery("chain.listEntries", { collectionSlug: "business-rules" });
|
|
6103
7858
|
const rules = entries.filter((e) => e.data?.domain === domain);
|
|
@@ -6150,7 +7905,7 @@ Provide a structured review with a compliance status for each rule (COMPLIANT /
|
|
|
6150
7905
|
server.prompt(
|
|
6151
7906
|
"name-check",
|
|
6152
7907
|
"Check variable names, field names, or API names against the glossary for terminology alignment. Flags drift from canonical terms.",
|
|
6153
|
-
{ names:
|
|
7908
|
+
{ names: z20.string().describe("Comma-separated list of names to check (e.g. 'vendor_id, compliance_level, formulator_type')") },
|
|
6154
7909
|
async ({ names }) => {
|
|
6155
7910
|
const terms = await mcpQuery("chain.listEntries", { collectionSlug: "glossary" });
|
|
6156
7911
|
const glossaryContext = terms.map(
|
|
@@ -6186,7 +7941,7 @@ Format as a table: Name | Status | Canonical Form | Action Needed`
|
|
|
6186
7941
|
server.prompt(
|
|
6187
7942
|
"draft-decision-record",
|
|
6188
7943
|
"Draft a structured decision record from a description of what was decided. Includes context from recent decisions and relevant rules.",
|
|
6189
|
-
{ context:
|
|
7944
|
+
{ context: z20.string().describe("Description of the decision (e.g. 'We decided to use MRSL v3.1 as the conformance baseline because...')") },
|
|
6190
7945
|
async ({ context }) => {
|
|
6191
7946
|
const recentDecisions = await mcpQuery("chain.listEntries", { collectionSlug: "decisions" });
|
|
6192
7947
|
const sorted = [...recentDecisions].sort((a, b) => (b.data?.date ?? "") > (a.data?.date ?? "") ? 1 : -1).slice(0, 5);
|
|
@@ -6224,8 +7979,8 @@ After drafting, I can log it using the capture tool with collection "decisions".
|
|
|
6224
7979
|
"draft-rule-from-context",
|
|
6225
7980
|
"Draft a new business rule from an observation or discovery made while coding. Fetches existing rules for the domain to ensure consistency.",
|
|
6226
7981
|
{
|
|
6227
|
-
observation:
|
|
6228
|
-
domain:
|
|
7982
|
+
observation: z20.string().describe("What you observed or discovered (e.g. 'Suppliers can have multiple org types in Gateway')"),
|
|
7983
|
+
domain: z20.string().describe("Which domain this rule belongs to (e.g. 'Governance & Decision-Making')")
|
|
6229
7984
|
},
|
|
6230
7985
|
async ({ observation, domain }) => {
|
|
6231
7986
|
const allRules = await mcpQuery("chain.listEntries", { collectionSlug: "business-rules" });
|
|
@@ -6266,10 +8021,10 @@ Make sure the rule is consistent with existing rules and doesn't contradict them
|
|
|
6266
8021
|
"run-workflow",
|
|
6267
8022
|
"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.",
|
|
6268
8023
|
{
|
|
6269
|
-
workflow:
|
|
8024
|
+
workflow: z20.string().describe(
|
|
6270
8025
|
"Workflow ID to run. Available: " + listWorkflows().map((w) => `'${w.id}' (${w.name})`).join(", ")
|
|
6271
8026
|
),
|
|
6272
|
-
context:
|
|
8027
|
+
context: z20.string().optional().describe(
|
|
6273
8028
|
"Optional context from the participant (e.g., 'retro on last sprint', 'shape the Chainwork API bet')"
|
|
6274
8029
|
)
|
|
6275
8030
|
},
|
|
@@ -6371,11 +8126,52 @@ Description field: ${wf.kbOutputTemplate.descriptionField}
|
|
|
6371
8126
|
);
|
|
6372
8127
|
server.prompt(
|
|
6373
8128
|
"shape-a-bet",
|
|
6374
|
-
"
|
|
8129
|
+
"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.",
|
|
6375
8130
|
{
|
|
6376
|
-
idea:
|
|
8131
|
+
idea: z20.string().describe("Brief description of the idea or feature to shape (e.g. 'Improve the glossary editing flow')")
|
|
6377
8132
|
},
|
|
6378
8133
|
async ({ idea }) => {
|
|
8134
|
+
let strategicContext = "";
|
|
8135
|
+
let governanceContext = "";
|
|
8136
|
+
let activeBetsContext = "";
|
|
8137
|
+
let tensionsContext = "";
|
|
8138
|
+
try {
|
|
8139
|
+
const orient = await mcpQuery("chain.getOrientEntries", {});
|
|
8140
|
+
const sc = orient?.strategicContext;
|
|
8141
|
+
if (sc?.vision) strategicContext += `**Vision:** ${sc.vision}
|
|
8142
|
+
`;
|
|
8143
|
+
if (sc?.currentBet) strategicContext += `**Current bet:** ${sc.currentBet}
|
|
8144
|
+
`;
|
|
8145
|
+
strategicContext += `${sc?.activeBetCount ?? 0} active bet(s), ${sc?.activeTensionCount ?? 0} open tension(s).
|
|
8146
|
+
`;
|
|
8147
|
+
if (orient?.activeBets?.length) {
|
|
8148
|
+
activeBetsContext = orient.activeBets.map((b) => `- \`${b.entryId ?? "?"}\` ${b.name}`).join("\n");
|
|
8149
|
+
}
|
|
8150
|
+
const govParts = [];
|
|
8151
|
+
if (orient?.principles?.length) {
|
|
8152
|
+
govParts.push("**Principles:**\n" + orient.principles.map((p) => `- \`${p.entryId}\` ${p.name}`).join("\n"));
|
|
8153
|
+
}
|
|
8154
|
+
if (orient?.standards?.length) {
|
|
8155
|
+
govParts.push("**Standards:**\n" + orient.standards.map((s) => `- \`${s.entryId}\` ${s.name}`).join("\n"));
|
|
8156
|
+
}
|
|
8157
|
+
if (orient?.businessRules?.length) {
|
|
8158
|
+
govParts.push("**Business Rules:**\n" + orient.businessRules.map((r) => `- \`${r.entryId}\` ${r.name}`).join("\n"));
|
|
8159
|
+
}
|
|
8160
|
+
governanceContext = govParts.join("\n\n");
|
|
8161
|
+
} catch {
|
|
8162
|
+
strategicContext = "_Could not load workspace context \u2014 proceed without it._\n";
|
|
8163
|
+
}
|
|
8164
|
+
try {
|
|
8165
|
+
const tensions = await mcpQuery(
|
|
8166
|
+
"chain.listEntries",
|
|
8167
|
+
{ collectionSlug: "tensions" }
|
|
8168
|
+
);
|
|
8169
|
+
const open = (tensions ?? []).filter((t) => t.status === "draft" || t.status === "active");
|
|
8170
|
+
if (open.length > 0) {
|
|
8171
|
+
tensionsContext = open.slice(0, 10).map((t) => `- \`${t.entryId ?? "?"}\` ${t.name}`).join("\n");
|
|
8172
|
+
}
|
|
8173
|
+
} catch {
|
|
8174
|
+
}
|
|
6379
8175
|
return {
|
|
6380
8176
|
messages: [{
|
|
6381
8177
|
role: "user",
|
|
@@ -6383,56 +8179,98 @@ Description field: ${wf.kbOutputTemplate.descriptionField}
|
|
|
6383
8179
|
type: "text",
|
|
6384
8180
|
text: `# Shape a Bet: ${idea}
|
|
6385
8181
|
|
|
6386
|
-
You are shaping
|
|
8182
|
+
You are a **coached shaping facilitator** powered by the \`facilitate\` tool. You guide the user through structured shaping while the server scores, captures, and coaches.
|
|
6387
8183
|
|
|
6388
|
-
##
|
|
6389
|
-
1. **Start session** (if not already): \`session action=start\`
|
|
6390
|
-
2. **Orient**: \`orient\` \u2014 loads workspace context and recent activity
|
|
8184
|
+
## Your Role (DEC-56 Boundary)
|
|
6391
8185
|
|
|
6392
|
-
|
|
8186
|
+
The **server judges** \u2014 it scores input against 5 rubric dimensions, detects overlap with existing Chain entries, and checks alignment with governance. The server returns structured coaching responses.
|
|
8187
|
+
**You coach** \u2014 you interpret the structured response, synthesize it naturally, decide tone, follow up on weak areas, and push back when the shaping isn't sharp enough.
|
|
8188
|
+
The \`coaching.suggestedQuestion\` is a suggestion, not a script \u2014 rephrase or skip based on conversation flow.
|
|
6393
8189
|
|
|
6394
|
-
|
|
6395
|
-
Discuss the problem with the user. Narrow to a specific, concrete problem statement. Capture the problem in 2\u20133 sentences.
|
|
8190
|
+
## Rubric Dimensions (5 scoring criteria)
|
|
6396
8191
|
|
|
6397
|
-
|
|
6398
|
-
|
|
8192
|
+
Each scored 0-10 by the server:
|
|
8193
|
+
1. **Problem Clarity** \u2014 workaround described, who affected, frequency/severity, differentiated from existing tensions
|
|
8194
|
+
2. **Appetite Definition** \u2014 time constraint explicit, scope bounded, trade-offs acknowledged
|
|
8195
|
+
3. **Element Decomposition** \u2014 breadboard-level pieces identified, independently describable, no implementation leaks
|
|
8196
|
+
4. **Risk Coverage** \u2014 rabbit holes named, mitigations or acceptances, codebase-level risks
|
|
8197
|
+
5. **Boundary Specification** \u2014 explicit no-gos, each prevents scope creep in a specific direction
|
|
6399
8198
|
|
|
6400
|
-
|
|
6401
|
-
Identify 3\u20135 solution elements. For each element that is a distinct feature, capture it:
|
|
6402
|
-
\`\`\`
|
|
6403
|
-
capture collection="features" name="<Element Name>" description="<What this element does>"
|
|
6404
|
-
\`\`\`
|
|
6405
|
-
Note the entryIds for relation linking.
|
|
8199
|
+
## How to Use the Facilitate Tool
|
|
6406
8200
|
|
|
6407
|
-
###
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
capture collection="tensions" name="<Risk>" description="<Risk, patch, mitigation>"
|
|
6411
|
-
\`\`\`
|
|
6412
|
-
Or link existing tensions.
|
|
8201
|
+
### Prerequisites
|
|
8202
|
+
1. \`session action=start\` (if not already active)
|
|
8203
|
+
2. \`orient\` (loads context and unlocks writes)
|
|
6413
8204
|
|
|
6414
|
-
###
|
|
6415
|
-
|
|
8205
|
+
### Session Flow
|
|
8206
|
+
1. **Start:** \`facilitate action=start betName="${idea}"\`
|
|
8207
|
+
Creates a draft bet on the Chain. Returns Studio URL and initial coaching prompt.
|
|
6416
8208
|
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
8209
|
+
2. **Respond (repeat):** \`facilitate action=respond betEntryId="..." userInput="<user's input>"\`
|
|
8210
|
+
Optionally add \`dimension="problem_clarity"\` to target a specific dimension.
|
|
8211
|
+
|
|
8212
|
+
**CRITICAL \u2014 Capture is REQUIRED for structured items:**
|
|
8213
|
+
Elements, risks, and no-gos MUST include the \`capture\` parameter. Without it, nothing is saved to the bet \u2014 the data is silently lost and you will receive a WARNING.
|
|
8214
|
+
- Elements: \`capture={type:"element", name:"...", description:"..."}\`
|
|
8215
|
+
- Risks: \`capture={type:"risk", name:"...", description:"...", theme:"implementation"}\`
|
|
8216
|
+
- No-gos: \`capture={type:"noGo", name:"...", description:"..."}\`
|
|
8217
|
+
Each element, risk, and no-go needs its own \`respond\` call with \`capture\`.
|
|
8218
|
+
|
|
8219
|
+
3. **Score (anytime):** \`facilitate action=score betEntryId="..."\`
|
|
8220
|
+
Check the scorecard without advancing.
|
|
8221
|
+
|
|
8222
|
+
4. **Resume:** \`facilitate action=resume betEntryId="..."\`
|
|
8223
|
+
Reconstruct session from Chain state if returning to an existing bet.
|
|
8224
|
+
|
|
8225
|
+
### Reading the Response
|
|
8226
|
+
The \`respond\` action returns JSON with:
|
|
8227
|
+
- \`scorecard\` \u2014 current scores per dimension (0-10)
|
|
8228
|
+
- \`overlap\` \u2014 existing Chain entries that may duplicate this work
|
|
8229
|
+
- \`chainSurfaced\` \u2014 related entries the user might not know about (weave into coaching)
|
|
8230
|
+
- \`coaching.observation\` \u2014 what the server observed
|
|
8231
|
+
- \`coaching.missing\` \u2014 what's still needed
|
|
8232
|
+
- \`coaching.pushback\` \u2014 challenge to present to the user
|
|
8233
|
+
- \`coaching.suggestedQuestion\` \u2014 next question to ask
|
|
8234
|
+
- \`coaching.captureReady\` \u2014 true when 3+ dimensions are complete
|
|
8235
|
+
- \`navigation.activeDimension\` \u2014 the dimension being coached
|
|
8236
|
+
- \`captured\` \u2014 entries created this turn, Studio URL
|
|
8237
|
+
- \`captureErrors\` \u2014 partial failures (non-blocking)
|
|
8238
|
+
|
|
8239
|
+
### Coaching Style
|
|
8240
|
+
- One dimension at a time. Don't dump all dimensions at once.
|
|
8241
|
+
- Push back on vague problems. "Who's affected?" is always a valid question.
|
|
8242
|
+
- When \`chainSurfaced\` returns related entries, weave them in: "There's a decision on the Chain about this \u2014 DEC-56 says..."
|
|
8243
|
+
- When \`overlap\` finds matches, challenge: "This tension already exists. How is your bet different?"
|
|
8244
|
+
- When \`captureReady\` is true, offer to finalize. Don't force completion.
|
|
8245
|
+
- Keep chat messages short (5 lines or fewer). The tool does the heavy lifting.
|
|
8246
|
+
|
|
8247
|
+
## Workspace Context
|
|
6431
8248
|
|
|
6432
|
-
|
|
6433
|
-
|
|
8249
|
+
` + (strategicContext ? `### Strategic Context
|
|
8250
|
+
${strategicContext}
|
|
8251
|
+
` : "") + (activeBetsContext ? `### Active Bets
|
|
8252
|
+
${activeBetsContext}
|
|
8253
|
+
|
|
8254
|
+
` : "") + (tensionsContext ? `### Open Tensions
|
|
8255
|
+
${tensionsContext}
|
|
8256
|
+
|
|
8257
|
+
` : "") + (governanceContext ? `### Governance Constraints
|
|
8258
|
+
_The shaping must honor these:_
|
|
8259
|
+
|
|
8260
|
+
${governanceContext}
|
|
8261
|
+
|
|
8262
|
+
` : "") + `## Constellation Capture (STA-1)
|
|
8263
|
+
The facilitate tool captures in real-time:
|
|
8264
|
+
- Elements \u2192 \`features\` collection, \`part_of\` \u2192 bet
|
|
8265
|
+
- Risks \u2192 \`tensions\` collection, \`constrains\` \u2192 bet
|
|
8266
|
+
- Decisions \u2192 \`decisions\` collection, \`informs\` \u2192 bet
|
|
8267
|
+
- No-gos \u2192 updated on the bet entry directly
|
|
8268
|
+
|
|
8269
|
+
Walk away mid-session and everything captured so far exists as drafts. \`facilitate action=resume\` picks it up.
|
|
8270
|
+
|
|
8271
|
+
---
|
|
6434
8272
|
|
|
6435
|
-
**Begin
|
|
8273
|
+
**Begin now.** Call \`facilitate action=start betName="${idea}"\` and then ask the user to describe the problem.`
|
|
6436
8274
|
}
|
|
6437
8275
|
}]
|
|
6438
8276
|
};
|
|
@@ -6442,9 +8280,9 @@ Show the user the captured bet and constellation. The capture response includes
|
|
|
6442
8280
|
"capture-and-connect",
|
|
6443
8281
|
"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.",
|
|
6444
8282
|
{
|
|
6445
|
-
collection:
|
|
6446
|
-
name:
|
|
6447
|
-
description:
|
|
8283
|
+
collection: z20.string().describe("Collection slug (e.g. 'glossary', 'business-rules', 'decisions', 'tensions')"),
|
|
8284
|
+
name: z20.string().describe("Entry name"),
|
|
8285
|
+
description: z20.string().describe("Entry description")
|
|
6448
8286
|
},
|
|
6449
8287
|
async ({ collection, name, description }) => {
|
|
6450
8288
|
const collections = await mcpQuery("chain.listCollections");
|
|
@@ -6498,7 +8336,7 @@ Only call \`commit-entry\` when the user explicitly confirms.
|
|
|
6498
8336
|
"deep-dive",
|
|
6499
8337
|
"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.",
|
|
6500
8338
|
{
|
|
6501
|
-
topic:
|
|
8339
|
+
topic: z20.string().describe("Entry ID (e.g. 'BR-001') or topic to explore (e.g. 'authentication')")
|
|
6502
8340
|
},
|
|
6503
8341
|
async ({ topic }) => {
|
|
6504
8342
|
const isEntryId = /^[A-Z]+-\d+$/i.test(topic) || /^[A-Z]+-[a-z0-9]+$/i.test(topic);
|
|
@@ -6581,7 +8419,7 @@ ${contextInstructions}`
|
|
|
6581
8419
|
"pre-commit-check",
|
|
6582
8420
|
"Run a readiness check before committing an entry to the Chain. Validates quality score, required relations, and business rule compliance.",
|
|
6583
8421
|
{
|
|
6584
|
-
entryId:
|
|
8422
|
+
entryId: z20.string().describe("Entry ID to check (e.g. 'GT-019', 'DEC-005')")
|
|
6585
8423
|
},
|
|
6586
8424
|
async ({ entryId }) => {
|
|
6587
8425
|
return {
|
|
@@ -6638,7 +8476,7 @@ If ready, ask the user to confirm. If not, suggest specific improvements.
|
|
|
6638
8476
|
}
|
|
6639
8477
|
|
|
6640
8478
|
// src/server.ts
|
|
6641
|
-
var SERVER_VERSION = "0.7.
|
|
8479
|
+
var SERVER_VERSION = "0.7.2";
|
|
6642
8480
|
var INSTRUCTIONS = [
|
|
6643
8481
|
"Product Brain \u2014 the single source of truth for product knowledge.",
|
|
6644
8482
|
"Terminology, standards, and core data all live here \u2014 no need to check external docs.",
|
|
@@ -6739,6 +8577,7 @@ function createProductBrainServer() {
|
|
|
6739
8577
|
if (enabledModules.has("arch")) registerArchitectureTools(server);
|
|
6740
8578
|
registerStartTools(server);
|
|
6741
8579
|
registerUsageTools(server);
|
|
8580
|
+
registerFacilitateTools(server);
|
|
6742
8581
|
registerResources(server);
|
|
6743
8582
|
registerPrompts(server);
|
|
6744
8583
|
return server;
|
|
@@ -6748,4 +8587,4 @@ export {
|
|
|
6748
8587
|
SERVER_VERSION,
|
|
6749
8588
|
createProductBrainServer
|
|
6750
8589
|
};
|
|
6751
|
-
//# sourceMappingURL=chunk-
|
|
8590
|
+
//# sourceMappingURL=chunk-HTL6Y2AO.js.map
|