@productbrain/mcp 0.0.1-beta.76 → 0.0.1-beta.78
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-BWK4N5YU.js → chunk-2TAMQOP7.js} +160 -2281
- package/dist/chunk-2TAMQOP7.js.map +1 -0
- package/dist/{chunk-KWQSXRIH.js → chunk-ZY3ORYC3.js} +158 -86
- package/dist/chunk-ZY3ORYC3.js.map +1 -0
- package/dist/http.js +2 -2
- package/dist/index.js +2 -2
- package/dist/{smart-capture-HSQTA5JW.js → smart-capture-VFCEUDDW.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-BWK4N5YU.js.map +0 -1
- package/dist/chunk-KWQSXRIH.js.map +0 -1
- /package/dist/{smart-capture-HSQTA5JW.js.map → smart-capture-VFCEUDDW.js.map} +0 -0
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
unknownAction,
|
|
44
44
|
validationResult,
|
|
45
45
|
withEnvelope
|
|
46
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-ZY3ORYC3.js";
|
|
47
47
|
import {
|
|
48
48
|
trackKnowledgeGap,
|
|
49
49
|
trackQualityCheck,
|
|
@@ -238,7 +238,7 @@ ${formatted}` }],
|
|
|
238
238
|
},
|
|
239
239
|
withEnvelope(async ({ entryId }) => {
|
|
240
240
|
requireWriteAccess();
|
|
241
|
-
const { runContradictionCheck } = await import("./smart-capture-
|
|
241
|
+
const { runContradictionCheck } = await import("./smart-capture-VFCEUDDW.js");
|
|
242
242
|
const entry = await mcpQuery("chain.getEntry", { entryId });
|
|
243
243
|
if (!entry) {
|
|
244
244
|
return notFoundResult(entryId, `Entry '${entryId}' not found. Try search to find the right ID.`);
|
|
@@ -1155,7 +1155,26 @@ async function handleBatchCreate(relations) {
|
|
|
1155
1155
|
requireWriteAccess();
|
|
1156
1156
|
const agentSessionId = getAgentSessionId();
|
|
1157
1157
|
const results = [];
|
|
1158
|
-
|
|
1158
|
+
const governsRels = relations.filter((r) => r.type === "governs");
|
|
1159
|
+
const batchRels = relations.filter((r) => r.type !== "governs");
|
|
1160
|
+
if (batchRels.length > 0) {
|
|
1161
|
+
try {
|
|
1162
|
+
const batchResult = await mcpMutation("chain.createEntryRelations", {
|
|
1163
|
+
relations: batchRels.map((r) => ({ fromEntryId: r.from, toEntryId: r.to, type: r.type })),
|
|
1164
|
+
sessionId: agentSessionId ?? void 0
|
|
1165
|
+
});
|
|
1166
|
+
for (let i = 0; i < batchRels.length; i++) {
|
|
1167
|
+
const rel = batchRels[i];
|
|
1168
|
+
const res = batchResult.results[i];
|
|
1169
|
+
results.push({ ...rel, ok: res?.ok ?? false, error: res?.error });
|
|
1170
|
+
}
|
|
1171
|
+
} catch (e) {
|
|
1172
|
+
for (const rel of batchRels) {
|
|
1173
|
+
results.push({ ...rel, ok: false, error: e instanceof Error ? e.message : "Batch failed" });
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
for (const rel of governsRels) {
|
|
1159
1178
|
try {
|
|
1160
1179
|
const result = await mcpMutation("chain.createEntryRelation", {
|
|
1161
1180
|
fromEntryId: rel.from,
|
|
@@ -3850,64 +3869,17 @@ The retro must never fail silently. Always communicate state.`
|
|
|
3850
3869
|
var SHAPE_WORKFLOW_DESCRIPTOR = {
|
|
3851
3870
|
id: "shape",
|
|
3852
3871
|
name: "Shape a Bet",
|
|
3853
|
-
shortDescription: "
|
|
3872
|
+
shortDescription: "Integration-aware shaping: maps the system before designing, traces five paths per element, surfaces governance conflicts at design time. Uses `facilitate` for scoring and Chain capture.",
|
|
3854
3873
|
template: {
|
|
3855
3874
|
slug: "shape-a-bet",
|
|
3856
3875
|
name: "Shape a Bet"
|
|
3857
3876
|
},
|
|
3858
3877
|
icon: "\u25C6",
|
|
3859
|
-
facilitatorPreamble: `You are now in **Shaping Mode**.
|
|
3860
|
-
|
|
3861
|
-
## Your Behavior
|
|
3862
|
-
|
|
3863
|
-
1. **Start with \`facilitate action=start\`.** This creates a draft bet on the Chain and returns the Studio URL, initial coaching, and an investigationBrief.
|
|
3864
|
-
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.
|
|
3865
|
-
3. **Use \`facilitate action=respond\` for every input.** Pass the user's text, betEntryId, dimension, and the \`source\` field:
|
|
3866
|
-
- \`source="user"\` \u2014 user typed this directly (default, full score weight)
|
|
3867
|
-
- \`source="agent_proposal"\` \u2014 you generated this proposal text (discounted score)
|
|
3868
|
-
- \`source="user_reaction"\` \u2014 user reacting to your proposal (full score weight)
|
|
3869
|
-
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.
|
|
3870
|
-
5. **One dimension at a time.** Follow the round progression below. Don't dump all dimensions at once.
|
|
3871
|
-
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."
|
|
3872
|
-
7. **Capture elements and risks inline** using the \`capture\` parameter on respond calls. Each becomes a Chain entry with a relation to the bet.
|
|
3873
|
-
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\`.
|
|
3874
|
-
9. **When captureReady is true, offer to finalize** \u2014 but don't force it. The user decides when the shape is complete.
|
|
3875
|
-
|
|
3876
|
-
## Investigation Pattern (ENT-59)
|
|
3877
|
-
|
|
3878
|
-
At phase transitions, follow this pattern:
|
|
3879
|
-
1. Read the \`investigationBrief\` from the structured response
|
|
3880
|
-
2. Spawn sub-agents in parallel (max 2, 30s budget each):
|
|
3881
|
-
- \`explore\` sub-agent: codebase searches (files, patterns, architecture)
|
|
3882
|
-
- \`generalPurpose\` sub-agent: Chain searches (entries, governance, context)
|
|
3883
|
-
3. Synthesize findings into a concrete proposal (cite files and entry IDs)
|
|
3884
|
-
4. Present the proposal for user reaction \u2014 "Based on the codebase, I'd suggest..."
|
|
3885
|
-
5. Score the user's reaction with \`source="user_reaction"\` (full weight)
|
|
3886
|
-
|
|
3887
|
-
If investigation fails or times out, continue with the conversation \u2014 the user's knowledge is always the primary input.
|
|
3888
|
-
|
|
3889
|
-
## DEC-56 Boundary
|
|
3890
|
-
|
|
3891
|
-
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).
|
|
3892
|
-
**You coach** \u2014 you interpret the structured response, synthesize naturally, investigate proactively, propose grounded solutions, and push back when shaping isn't sharp.
|
|
3893
|
-
The \`coaching.suggestedQuestion\` is a suggestion, not a script \u2014 rephrase or skip based on conversation flow.
|
|
3894
|
-
|
|
3895
|
-
## Error Recovery
|
|
3896
|
-
|
|
3897
|
-
If any facilitate call fails:
|
|
3898
|
-
1. Tell the user what you were trying to do
|
|
3899
|
-
2. Explain what went wrong (one sentence)
|
|
3900
|
-
3. Continue shaping \u2014 the conversation IS the backup
|
|
3901
|
-
4. Suggest retrying or capturing manually later
|
|
3902
|
-
|
|
3903
|
-
## Communication Style
|
|
3878
|
+
facilitatorPreamble: `You are now in **Shaping Mode**. The shaping skill owns the facilitation loop end-to-end \u2014 read it for scoring rubrics, investigation patterns, and the capture flow.
|
|
3904
3879
|
|
|
3905
|
-
|
|
3906
|
-
-
|
|
3907
|
-
|
|
3908
|
-
- Synthesize between phases \u2014 reflect what was decided before moving on
|
|
3909
|
-
- When proposing, cite evidence: "I found X in the codebase" or "The Chain has DEC-Y about this"
|
|
3910
|
-
- If the user seems stuck, investigate and propose rather than just asking`,
|
|
3880
|
+
Use \`capture\` to create constellation entries (elements, risks, decisions, glossary terms) linked to the bet.
|
|
3881
|
+
Use \`facilitate action=commit-constellation\` to atomically commit the bet and all linked entries when the shape is complete.
|
|
3882
|
+
Use \`workflows action=checkpoint\` after each round to persist progress to the durable run.`,
|
|
3911
3883
|
rounds: [
|
|
3912
3884
|
{
|
|
3913
3885
|
id: "context",
|
|
@@ -3915,13 +3887,13 @@ If any facilitate call fails:
|
|
|
3915
3887
|
label: "Gather Context",
|
|
3916
3888
|
type: "open",
|
|
3917
3889
|
instruction: "Before shaping, let's see what the Chain already knows. Search for related entries, business rules, and existing decisions.",
|
|
3918
|
-
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?'",
|
|
3890
|
+
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. Build a system map: what modules, data flows, and contracts exist in this area today? Check governance: do any principles, standards, or business rules constrain this design space? Then transition: 'Here's what I found. Does this match the problem you're seeing?'",
|
|
3919
3891
|
outputSchema: {
|
|
3920
3892
|
field: "context",
|
|
3921
|
-
description: "Chain context
|
|
3893
|
+
description: "System map, Chain context, governance constraints, related entries",
|
|
3922
3894
|
format: "structured"
|
|
3923
3895
|
},
|
|
3924
|
-
maxDurationHint: "
|
|
3896
|
+
maxDurationHint: "5 min"
|
|
3925
3897
|
},
|
|
3926
3898
|
{
|
|
3927
3899
|
id: "framing",
|
|
@@ -3929,7 +3901,7 @@ If any facilitate call fails:
|
|
|
3929
3901
|
label: "Frame the Problem",
|
|
3930
3902
|
type: "open",
|
|
3931
3903
|
instruction: "What's the problem? Who's affected? What's the workaround today? How much time is this worth?",
|
|
3932
|
-
facilitatorGuidance: "This round covers
|
|
3904
|
+
facilitatorGuidance: "This round covers problem clarity AND appetite. Search the codebase for workarounds, TODOs, and hacks related to the problem \u2014 cite what you find as evidence. 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 are well-covered, 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.",
|
|
3933
3905
|
questions: [
|
|
3934
3906
|
{
|
|
3935
3907
|
id: "appetite",
|
|
@@ -3962,7 +3934,7 @@ If any facilitate call fails:
|
|
|
3962
3934
|
label: "Find the Elements",
|
|
3963
3935
|
type: "open",
|
|
3964
3936
|
instruction: "What are the solution elements? Think breadboard level \u2014 places, affordances, connections. What's the architecture?",
|
|
3965
|
-
facilitatorGuidance: "**Investigate first.**
|
|
3937
|
+
facilitatorGuidance: "**Investigate first.** 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. **Five-path trace per element:** For each element, trace: (1) what data flows through it, (2) what contracts it relies on, (3) what contracts it alters, (4) what second-order effects downstream, (5) what happens at boundaries (batch, error, empty, scale). Present proposals for user reaction: 'Based on the codebase, here are the elements I'd suggest.' For each confirmed element, use `capture` with collection='features' to create 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. 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 and architecture are well-covered, present for confirmation.",
|
|
3966
3938
|
outputSchema: {
|
|
3967
3939
|
field: "elements",
|
|
3968
3940
|
description: "Core solution elements and architecture",
|
|
@@ -3977,7 +3949,7 @@ If any facilitate call fails:
|
|
|
3977
3949
|
label: "De-risk \u2014 Rabbit Holes & No-Gos",
|
|
3978
3950
|
type: "open",
|
|
3979
3951
|
instruction: "What could go wrong? What are we NOT building? Walk through the solution slowly and find the risks.",
|
|
3980
|
-
facilitatorGuidance: "**Investigate first.**
|
|
3952
|
+
facilitatorGuidance: "**Investigate first.** 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 using `capture` with collection='tensions' \u2014 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 also be captured. **Counter-Metric Mandate (STD-19):** If the derisking references any quantitative gates or metrics (accuracy targets, performance budgets, adoption thresholds), stress-test them: 'What's the denominator? What gets excluded? What counter-metric would expose a false positive?' A metric gate that can be gamed by abstaining or cherry-picking the population is not a gate \u2014 name the compound metric that can't be gamed. When risks and boundaries are well-covered, present Shape Go gate: 'Is this buildable within the appetite?'",
|
|
3981
3953
|
questions: [
|
|
3982
3954
|
{
|
|
3983
3955
|
id: "shape-go",
|
|
@@ -4003,7 +3975,7 @@ If any facilitate call fails:
|
|
|
4003
3975
|
label: "Validate & Build Contract",
|
|
4004
3976
|
type: "synthesis",
|
|
4005
3977
|
instruction: "Check completeness. Generate the build contract. Review the full scorecard.",
|
|
4006
|
-
facilitatorGuidance: "
|
|
3978
|
+
facilitatorGuidance: "Review the scorecard from the shaping skill's scoring rubric. Present it as a table. 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. **System coherence confirmation:** Were all five paths traced for each element? State which interaction risks remain unverified \u2014 these become explicit risks in the bet, not surprises during implementation. **Vision narrative (TEN-281):** Write one paragraph \u2014 how does this bet serve the product vision? **Test plan (TEN-281):** How will we know this is done? Concrete scenarios, not 'when tests pass.' **Counter-Metric Mandate (STD-19):** If the scorecard or build contract contains metrics or gates, verify each has a denominator, a coverage rate, and a counter-metric. Flag any gate that can be satisfied by abstaining or excluding the hard cases. Present the build contract. Ask: 'Anything to adjust before we capture to the Chain?'",
|
|
4007
3979
|
outputSchema: {
|
|
4008
3980
|
field: "validation",
|
|
4009
3981
|
description: "Completeness check result and build contract",
|
|
@@ -4017,7 +3989,7 @@ If any facilitate call fails:
|
|
|
4017
3989
|
label: "Capture & Commit",
|
|
4018
3990
|
type: "close",
|
|
4019
3991
|
instruction: "Capture the pitch to the Chain. Commit draft entries. Connect the constellation.",
|
|
4020
|
-
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?'
|
|
3992
|
+
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?' Call `facilitate action=commit-constellation` to atomically commit the bet and all linked entries. Then `graph action=suggest entryId='<betId>'` to discover connections. Create the top 3 suggested relations via `relations action=batch-create`. Thank the user. State: what was shaped, how many constellation entries captured, what the implementer inherits.",
|
|
4021
3993
|
outputSchema: {
|
|
4022
3994
|
field: "capture",
|
|
4023
3995
|
description: "Final capture summary \u2014 entries committed, relations created",
|
|
@@ -4063,14 +4035,13 @@ If any facilitate call fails:
|
|
|
4063
4035
|
]
|
|
4064
4036
|
},
|
|
4065
4037
|
runtime: {
|
|
4066
|
-
kind: "
|
|
4067
|
-
primaryTool: "facilitate"
|
|
4038
|
+
kind: "guided"
|
|
4068
4039
|
},
|
|
4069
4040
|
errorRecovery: `If anything goes wrong during shaping:
|
|
4070
4041
|
|
|
4071
4042
|
1. **facilitate tool failure**: Continue the conversation. The shaping knowledge is in the chat. Suggest retrying the tool call or capturing manually later.
|
|
4072
4043
|
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.
|
|
4073
|
-
3. **Score seems wrong**:
|
|
4044
|
+
3. **Score seems wrong**: Scoring is local to the shaping skill \u2014 re-apply the scoring rubric from the shaping SKILL.md against the conversation context. Scores are heuristic; the agent's judgment matters more than the number.
|
|
4074
4045
|
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.
|
|
4075
4046
|
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."
|
|
4076
4047
|
|
|
@@ -4079,7 +4050,7 @@ The shaping must never fail silently. Always communicate state.`
|
|
|
4079
4050
|
var IMPLEMENTATION_REVIEW_WORKFLOW_DESCRIPTOR = {
|
|
4080
4051
|
id: "implementation-review",
|
|
4081
4052
|
name: "Implementation Review",
|
|
4082
|
-
shortDescription: "
|
|
4053
|
+
shortDescription: "Fix-as-you-go implementation review: finds issues, fixes them immediately, verifies, then proceeds. Three expert lenses (Staff+ Engineer, CTO, Chain Coherence). Spawns sub-agents for code and test review. Extracts learnings into standards/rules. Ends with BET/chain IDs. Commits an insight to the Chain.",
|
|
4083
4054
|
template: {
|
|
4084
4055
|
slug: "implementation-review",
|
|
4085
4056
|
name: "Implementation Review"
|
|
@@ -4099,11 +4070,13 @@ var IMPLEMENTATION_REVIEW_WORKFLOW_DESCRIPTOR = {
|
|
|
4099
4070
|
## Your Behavior
|
|
4100
4071
|
|
|
4101
4072
|
1. **Tool-heavy, not chat-heavy.** Each round calls specific tools: orient, quality, verify, chain-review, context action=gather, review-against-rules. Use Context7 (query-docs) for relevant testing and framework docs \u2014 not for Chain truth.
|
|
4102
|
-
2. **Spawn sub-agents** for parallel review: (a) code/architecture review, (b) test honesty (did we edit tests to pass or do they validate real behavior?)
|
|
4103
|
-
3. **
|
|
4104
|
-
4. **
|
|
4105
|
-
5. **
|
|
4106
|
-
6. **
|
|
4073
|
+
2. **Spawn sub-agents** for parallel review: (a) code/architecture review, (b) test honesty (did we edit tests to pass or do they validate real behavior?). Max 2 agents in parallel, 30s budget each. **Constrain each sub-agent to the stated review scope only.**
|
|
4074
|
+
3. **Fix-as-you-go (FEAT-172).** When you find a HIGH or MEDIUM issue: fix it immediately, verify (lints, types), then proceed. The user sees clean work, not a list of issues. Only defer if estimated >5 min \u2014 capture deferred items as tensions. If user says "skip", capture as tension and continue.
|
|
4075
|
+
4. **Chain is SSOT.** Call orient or start first. Use entries action=search and context action=gather to load relevant BETs, DECs, BRs. If findings conflict with Chain entries, report the conflict and cite the entry ID.
|
|
4076
|
+
5. **One round at a time.** Complete each round before moving to the next. Checkpoint progress.
|
|
4077
|
+
6. **Extract learnings (FEAT-174).** After fixing issues, ask: is this a recurring pattern? Offer to capture structural patterns as standards or business rules so future reviews check automatically.
|
|
4078
|
+
7. **CRITICAL: End with BET/chain IDs.** The final output MUST include a single sentence: "BET/feature IDs reviewed: [IDs]. [One-line summary of findings]."
|
|
4079
|
+
8. **Never go silent.** If a tool fails, explain and continue. The conversation IS the backup.
|
|
4107
4080
|
|
|
4108
4081
|
## Tool Usage
|
|
4109
4082
|
|
|
@@ -4137,13 +4110,13 @@ This block is the final deliverable of the review. Everything else is supporting
|
|
|
4137
4110
|
label: "Orient & Scope",
|
|
4138
4111
|
type: "open",
|
|
4139
4112
|
instruction: "Apply the Review scope rules: infer scope from this conversation, git diff, and recent edits (default = work in this conversation only). Enumerate every file touched (created, modified, deleted). Call orient or start, then context action=gather for the inferred BET/feature. State scope in one explicit sentence and list all files in scope. Only ask the participant if scope is ambiguous. The review will assess: holistic coherence with our system, whether this brings us closer to our vision, and whether it makes our code and architecture cleaner.",
|
|
4140
|
-
facilitatorGuidance: "Infer scope from conversation context, git status/diff, and recent edits \u2014 default is work in this conversation only. List every file touched (created, modified, or deleted) in the work under review; scope is not complete without this list. Call orient or start to load governance and active bets. Use context action=gather task='implementation review for [inferred-BET-or-feature]' to load related entries. State scope explicitly in one sentence (e.g. 'Scope: changes in this conversation for BET-72'). Present: stated scope, the full list of files touched, relevant BETs/DECs/BRs, and any stale entries orient surfaces. Frame the review: we will assess holistic coherence with our system, whether this brings us closer to our vision, and whether it makes our code and architecture cleaner. Ask the participant only if scope is ambiguous (e.g. 'Only the files we changed, or the whole feature?').",
|
|
4113
|
+
facilitatorGuidance: "Infer scope from conversation context, git status/diff, and recent edits \u2014 default is work in this conversation only. List every file touched (created, modified, or deleted) in the work under review; scope is not complete without this list. Call orient or start to load governance and active bets. Use context action=gather task='implementation review for [inferred-BET-or-feature]' to load related entries. State scope explicitly in one sentence (e.g. 'Scope: changes in this conversation for BET-72'). Present: stated scope, the full list of files touched, relevant BETs/DECs/BRs, and any stale entries orient surfaces. Frame the review: we will assess holistic coherence with our system, whether this brings us closer to our vision, and whether it makes our code and architecture cleaner. Ask the participant only if scope is ambiguous (e.g. 'Only the files we changed, or the whole feature?'). Note vision anchors (principles, strategy, active bet constraints) \u2014 you will reference these in Rounds 02\u201305.",
|
|
4141
4114
|
outputSchema: {
|
|
4142
4115
|
field: "scope",
|
|
4143
|
-
description: "Explicit scope statement, every file touched, BET/feature IDs
|
|
4116
|
+
description: "Explicit scope statement, every file touched, BET/feature IDs, related Chain context, vision anchors",
|
|
4144
4117
|
format: "structured"
|
|
4145
4118
|
},
|
|
4146
|
-
maxDurationHint: "
|
|
4119
|
+
maxDurationHint: "2 min"
|
|
4147
4120
|
},
|
|
4148
4121
|
{
|
|
4149
4122
|
id: "standards-review",
|
|
@@ -4164,8 +4137,8 @@ This block is the final deliverable of the review. Everything else is supporting
|
|
|
4164
4137
|
num: "03",
|
|
4165
4138
|
label: "Code & Test Honesty",
|
|
4166
4139
|
type: "open",
|
|
4167
|
-
instruction: "Spawn sub-agents to review implementation and tests for the stated scope only. Did we meet high standards? Are tests validating real behavior or did we edit them just to pass?",
|
|
4168
|
-
facilitatorGuidance: "Restrict all review to the scope stated in Round 01 (by default, work in this conversation only). Spawn 1\u20132 sub-agents in parallel: (1) explore agent \u2014 code review, architecture boundaries, type-safety for scoped files only. (2)
|
|
4140
|
+
instruction: "Spawn sub-agents to review implementation and tests for the stated scope only. Fix every HIGH/MEDIUM finding before proceeding. Did we meet high standards? Are tests validating real behavior or did we edit them just to pass?",
|
|
4141
|
+
facilitatorGuidance: "Restrict all review to the scope stated in Round 01 (by default, work in this conversation only). Spawn 1\u20132 sub-agents in parallel: (1) explore agent \u2014 code review, architecture boundaries, type-safety for scoped files only. (2) explore agent \u2014 test review for scoped code: are tests asserting real behavior or were they edited to pass? Pass the scope (files/BET) explicitly to sub-agents so they do not touch other work. Use Context7 (query-docs) for relevant testing and framework best practices if needed. Classify any test failures: staleness, regression, or flaky. **Fix-as-you-go (FEAT-172):** For every HIGH/MEDIUM finding: fix immediately, run lints, verify types. Only defer fixes estimated >5 min \u2014 capture deferred items as tensions on the Chain. After fixing, do a boy-scout pass on each scoped file: clean imports, improve naming, remove dead code. **Counter-Metric Mandate (STD-19):** When reporting test coverage, accuracy, or any quantitative result, include the denominator, what was excluded, and at least one counter-metric. Never report accuracy without recall. Never report pass rate without coverage. Synthesize: findings, fixes applied, implementation grade, test honesty verdict.",
|
|
4169
4142
|
outputSchema: {
|
|
4170
4143
|
field: "codeAndTests",
|
|
4171
4144
|
description: "Implementation grade, test honesty, refactor suggestions",
|
|
@@ -4176,41 +4149,41 @@ This block is the final deliverable of the review. Everything else is supporting
|
|
|
4176
4149
|
{
|
|
4177
4150
|
id: "chain-validation",
|
|
4178
4151
|
num: "04",
|
|
4179
|
-
label: "
|
|
4152
|
+
label: "Chain Validation & Learning Extraction",
|
|
4180
4153
|
type: "synthesis",
|
|
4181
|
-
instruction: "Run verify. Check orient for stale entries. Is the Chain current with the codebase?",
|
|
4182
|
-
facilitatorGuidance: "Call verify (report mode) \u2014 checks glossary code mappings and cross-references. Orient surfaces stale entries (per workspace governance). Present trust score and any drift. If drift found:
|
|
4154
|
+
instruction: "Run verify. Check orient for stale entries. Is the Chain current with the codebase? Then extract learnings: did this review surface structural patterns that should become standards or rules?",
|
|
4155
|
+
facilitatorGuidance: "**Chain validation:** Call verify (report mode) \u2014 checks glossary code mappings and cross-references. Orient surfaces stale entries (per workspace governance). Present trust score and any drift. If drift found: fix immediately via update-entry (with changeNote). **Learning extraction (FEAT-174, TEN-272):** Review all findings from Rounds 02 and 03. Ask: did this review surface a structural pattern that could recur? For each pattern: could it be a standard ('We always do X this way'), a business rule ('The system must do X'), or a lint rule (automated check)? Offer to capture. Also offer to capture any decisions, tensions, or new terms surfaced during the review. The goal: today's bug becomes tomorrow's automated check (TEN-272).",
|
|
4183
4156
|
outputSchema: {
|
|
4184
4157
|
field: "chainValidation",
|
|
4185
|
-
description: "Verify trust score,
|
|
4158
|
+
description: "Verify trust score, drift fixes, extracted learnings (patterns \u2192 standards/rules)",
|
|
4186
4159
|
format: "structured"
|
|
4187
4160
|
},
|
|
4188
|
-
maxDurationHint: "
|
|
4161
|
+
maxDurationHint: "5 min"
|
|
4189
4162
|
},
|
|
4190
4163
|
{
|
|
4191
4164
|
id: "synthesis",
|
|
4192
4165
|
num: "05",
|
|
4193
4166
|
label: "Synthesis & Sign-Off",
|
|
4194
4167
|
type: "commit",
|
|
4195
|
-
instruction: "
|
|
4196
|
-
facilitatorGuidance: "Present the full synthesis: standards pass/fail, code grade, test honesty, chain validation.
|
|
4168
|
+
instruction: "Synthesize the review. Answer: Does this bring us closer to our vision and make our code and architecture cleaner? With fix-as-you-go, most issues are already resolved \u2014 summarize what was fixed, not what's left. End with BET/chain IDs.",
|
|
4169
|
+
facilitatorGuidance: "Present the full synthesis: standards pass/fail, code grade, test honesty, chain validation, fixes applied. **Vision delta:** Cite specific principles/strategy entries from Round 01. Answer: does this bring us closer to [cited vision]? **Counter-Metric Mandate (STD-19):** For EVERY quantitative result you present, you MUST include: (1) the denominator \u2014 what is the total population measured, (2) the coverage/abstention rate \u2014 what was excluded, (3) at least one counter-metric that tells the opposite story (recall vs precision, error rate vs success rate), (4) the 'student exam' reframe \u2014 score on the full exam, not just questions answered. **Red-team your own synthesis:** Before presenting, ask yourself: 'What's the worst honest interpretation of these numbers? What metric looks good but hides a real problem? What would a skeptic challenge?' Present that alongside the headline. Recommend: ship, ship with conditions, or do not ship. CRITICAL: The output MUST end with one sentence: 'BET/feature IDs reviewed: [IDs]. [One-line summary].' Example: 'BET-xxx, STD-xxx reviewed. Conditional ship \u2014 fix [specific issues] and add [missing tests].' **Structured Verdict Block (REQUIRED):** After the full synthesis, present the verdict block with exactly 5 items: (1) Verdict: Y or N \u2014 one word. (2) What shipped: one sentence. (3) Why it matters: one sentence on the problem solved. (4) User benefit: one sentence on value for users. (5) How to validate: one sentence on verification. This block is the final deliverable. Everything else is supporting evidence.",
|
|
4197
4170
|
outputSchema: {
|
|
4198
4171
|
field: "synthesis",
|
|
4199
|
-
description: "Full review synthesis with BET/chain IDs and structured verdict block
|
|
4172
|
+
description: "Full review synthesis with vision delta, fixes applied, BET/chain IDs, and structured verdict block",
|
|
4200
4173
|
format: "structured"
|
|
4201
4174
|
},
|
|
4202
|
-
maxDurationHint: "
|
|
4175
|
+
maxDurationHint: "3 min"
|
|
4203
4176
|
},
|
|
4204
4177
|
{
|
|
4205
4178
|
id: "capture",
|
|
4206
4179
|
num: "06",
|
|
4207
4180
|
label: "Capture & Close",
|
|
4208
4181
|
type: "close",
|
|
4209
|
-
instruction: "Finalize the review as an insight draft. Use session-wrapup. Thank the participant.",
|
|
4210
|
-
facilitatorGuidance: "Summarize the review for finalization. Complete the terminal round through `workflows action=checkpoint` with `isFinal=true` so the workflow substrate creates the draft insight record. Include: BET/feature IDs, verdict, key findings, and the structured verdict block from Round 05. The insight description MUST include the 5-item verdict block (verdict Y/N, what shipped, why it matters, user benefit, how to validate). Run session-wrapup before closing. Thank the participant.
|
|
4182
|
+
instruction: "Finalize the review as an insight draft. Connect new entries from learning extraction. Use session-wrapup. Thank the participant.",
|
|
4183
|
+
facilitatorGuidance: "Summarize the review for finalization. Complete the terminal round through `workflows action=checkpoint` with `isFinal=true` so the workflow substrate creates the draft insight record. Include: BET/feature IDs, verdict, key findings, fixes applied, and the structured verdict block from Round 05. The insight description MUST include the 5-item verdict block (verdict Y/N, what shipped, why it matters, user benefit, how to validate). If learnings were captured in Round 04: run graph action=suggest for each new entry, then relations action=batch-create. Run session-wrapup before closing. Thank the participant. State what was fixed, what was captured, what was deferred.",
|
|
4211
4184
|
outputSchema: {
|
|
4212
4185
|
field: "capture",
|
|
4213
|
-
description: "Insight entry created, BET IDs referenced",
|
|
4186
|
+
description: "Insight entry created, BET IDs referenced, learnings connected",
|
|
4214
4187
|
format: "structured"
|
|
4215
4188
|
},
|
|
4216
4189
|
kbCollection: "insights",
|
|
@@ -5101,1128 +5074,18 @@ function parseListOutput(output) {
|
|
|
5101
5074
|
|
|
5102
5075
|
// src/tools/facilitate.ts
|
|
5103
5076
|
import { z as z12 } from "zod";
|
|
5104
|
-
|
|
5105
|
-
// src/tools/facilitate-rubrics.ts
|
|
5106
|
-
var AGENT_DISCOUNT_FACTOR = 0.6;
|
|
5107
|
-
var DIMENSIONS = [
|
|
5108
|
-
"problem_clarity",
|
|
5109
|
-
"appetite",
|
|
5110
|
-
"elements",
|
|
5111
|
-
"architecture",
|
|
5112
|
-
"risks",
|
|
5113
|
-
"boundaries",
|
|
5114
|
-
"done_when"
|
|
5115
|
-
];
|
|
5116
|
-
var DIMENSION_LABELS = {
|
|
5117
|
-
problem_clarity: "Problem Clarity",
|
|
5118
|
-
appetite: "Appetite Definition",
|
|
5119
|
-
elements: "Element Decomposition",
|
|
5120
|
-
architecture: "Architecture Grounding",
|
|
5121
|
-
risks: "Risk Coverage",
|
|
5122
|
-
boundaries: "Boundary Specification",
|
|
5123
|
-
done_when: "Done-When Quality"
|
|
5124
|
-
};
|
|
5125
|
-
var SUGGESTED_ORDER = [
|
|
5126
|
-
"problem_clarity",
|
|
5127
|
-
"appetite",
|
|
5128
|
-
"elements",
|
|
5129
|
-
"architecture",
|
|
5130
|
-
"risks",
|
|
5131
|
-
"boundaries",
|
|
5132
|
-
"done_when"
|
|
5133
|
-
];
|
|
5134
|
-
var PHASE_ORDER = [
|
|
5135
|
-
"context",
|
|
5136
|
-
"framing",
|
|
5137
|
-
"elements",
|
|
5138
|
-
"derisking",
|
|
5139
|
-
"validation",
|
|
5140
|
-
"capture"
|
|
5141
|
-
];
|
|
5142
|
-
var PHASE_LABELS = {
|
|
5143
|
-
context: "Phase 0: Gather Context",
|
|
5144
|
-
framing: "Phase 1: Frame the Problem",
|
|
5145
|
-
elements: "Phase 2: Find the Elements",
|
|
5146
|
-
derisking: "Phase 3: De-risk",
|
|
5147
|
-
validation: "Phase 4: Validate & Contract",
|
|
5148
|
-
capture: "Phase 5: Capture & Commit"
|
|
5149
|
-
};
|
|
5150
|
-
function contentFloor(content, isSmallBatch) {
|
|
5151
|
-
const hasElements = content.elementCount >= 1;
|
|
5152
|
-
const hasDerisking = content.riskCount >= 1 || content.noGoCount >= 1;
|
|
5153
|
-
const hasArchOrSkipped = isSmallBatch || !!content.hasArchitectureText;
|
|
5154
|
-
const wellShaped = content.elementCount >= 2 && hasDerisking && hasArchOrSkipped;
|
|
5155
|
-
if (wellShaped && content.riskCount >= 1 && content.noGoCount >= 2) return "validation";
|
|
5156
|
-
if (hasDerisking) return "derisking";
|
|
5157
|
-
if (hasElements) return "elements";
|
|
5158
|
-
return "context";
|
|
5159
|
-
}
|
|
5160
|
-
function laterPhase(a, b) {
|
|
5161
|
-
return PHASE_ORDER.indexOf(a) >= PHASE_ORDER.indexOf(b) ? a : b;
|
|
5162
|
-
}
|
|
5163
|
-
function inferPhase(scorecard, isSmallBatch = false, content) {
|
|
5164
|
-
let scorePhase;
|
|
5165
|
-
if (scorecard.problem_clarity === 0 && scorecard.appetite === 0) scorePhase = "context";
|
|
5166
|
-
else if (scorecard.problem_clarity < 6 || scorecard.appetite < 6) scorePhase = "framing";
|
|
5167
|
-
else {
|
|
5168
|
-
const archGate = isSmallBatch ? true : scorecard.architecture >= 4;
|
|
5169
|
-
if (scorecard.elements < 6 || !archGate) scorePhase = "elements";
|
|
5170
|
-
else if (scorecard.risks < 6 || scorecard.boundaries < 4 || scorecard.done_when < 4) scorePhase = "derisking";
|
|
5171
|
-
else {
|
|
5172
|
-
const activeDims = activeDimensions(isSmallBatch);
|
|
5173
|
-
const allAboveThreshold = activeDims.every((d) => scorecard[d] >= 4);
|
|
5174
|
-
if (!allAboveThreshold) scorePhase = "derisking";
|
|
5175
|
-
else {
|
|
5176
|
-
const highQuality = activeDims.every((d) => scorecard[d] >= 6);
|
|
5177
|
-
scorePhase = highQuality ? "capture" : "validation";
|
|
5178
|
-
}
|
|
5179
|
-
}
|
|
5180
|
-
}
|
|
5181
|
-
if (!content) return scorePhase;
|
|
5182
|
-
return laterPhase(scorePhase, contentFloor(content, isSmallBatch));
|
|
5183
|
-
}
|
|
5184
|
-
function activeDimensions(isSmallBatch) {
|
|
5185
|
-
return isSmallBatch ? SUGGESTED_ORDER.filter((d) => d !== "architecture") : SUGGESTED_ORDER;
|
|
5186
|
-
}
|
|
5187
|
-
var WORKAROUND_SIGNALS = [
|
|
5188
|
-
"workaround",
|
|
5189
|
-
"currently",
|
|
5190
|
-
"today",
|
|
5191
|
-
"right now",
|
|
5192
|
-
"manually",
|
|
5193
|
-
"hack",
|
|
5194
|
-
"instead",
|
|
5195
|
-
"rather than",
|
|
5196
|
-
"in practice",
|
|
5197
|
-
"as-is"
|
|
5198
|
-
];
|
|
5199
|
-
var AFFECTED_SIGNALS = [
|
|
5200
|
-
"user",
|
|
5201
|
-
"team",
|
|
5202
|
-
"developer",
|
|
5203
|
-
"engineer",
|
|
5204
|
-
"customer",
|
|
5205
|
-
"everyone",
|
|
5206
|
-
"nobody",
|
|
5207
|
-
"people",
|
|
5208
|
-
"stakeholder",
|
|
5209
|
-
"builder"
|
|
5210
|
-
];
|
|
5211
|
-
var FREQUENCY_SIGNALS = [
|
|
5212
|
-
"every time",
|
|
5213
|
-
"always",
|
|
5214
|
-
"often",
|
|
5215
|
-
"rarely",
|
|
5216
|
-
"daily",
|
|
5217
|
-
"weekly",
|
|
5218
|
-
"never",
|
|
5219
|
-
"sometimes",
|
|
5220
|
-
"constantly",
|
|
5221
|
-
"repeatedly",
|
|
5222
|
-
"each session",
|
|
5223
|
-
"per session"
|
|
5224
|
-
];
|
|
5225
|
-
var APPETITE_SIGNALS = [
|
|
5226
|
-
"week",
|
|
5227
|
-
"weeks",
|
|
5228
|
-
"day",
|
|
5229
|
-
"days",
|
|
5230
|
-
"month",
|
|
5231
|
-
"sprint",
|
|
5232
|
-
"small batch",
|
|
5233
|
-
"big batch",
|
|
5234
|
-
"appetite",
|
|
5235
|
-
"time box",
|
|
5236
|
-
"timebox",
|
|
5237
|
-
"budget",
|
|
5238
|
-
"phased"
|
|
5239
|
-
];
|
|
5240
|
-
var SCOPE_SIGNALS = [
|
|
5241
|
-
"trade-off",
|
|
5242
|
-
"tradeoff",
|
|
5243
|
-
"trade off",
|
|
5244
|
-
"not including",
|
|
5245
|
-
"out of scope",
|
|
5246
|
-
"defer",
|
|
5247
|
-
"skip",
|
|
5248
|
-
"cut",
|
|
5249
|
-
"80%",
|
|
5250
|
-
"good enough",
|
|
5251
|
-
"mvp",
|
|
5252
|
-
"v1",
|
|
5253
|
-
"phase"
|
|
5254
|
-
];
|
|
5255
|
-
var RISK_SIGNALS = [
|
|
5256
|
-
"risk",
|
|
5257
|
-
"rabbit hole",
|
|
5258
|
-
"unknown",
|
|
5259
|
-
"complexity",
|
|
5260
|
-
"might",
|
|
5261
|
-
"could fail",
|
|
5262
|
-
"latency",
|
|
5263
|
-
"performance",
|
|
5264
|
-
"coupling",
|
|
5265
|
-
"dependency",
|
|
5266
|
-
"fragile",
|
|
5267
|
-
"brittle",
|
|
5268
|
-
"unclear",
|
|
5269
|
-
"uncertain",
|
|
5270
|
-
"tricky"
|
|
5271
|
-
];
|
|
5272
|
-
var MITIGATION_SIGNALS = [
|
|
5273
|
-
"patch",
|
|
5274
|
-
"mitigate",
|
|
5275
|
-
"accept",
|
|
5276
|
-
"workaround",
|
|
5277
|
-
"fallback",
|
|
5278
|
-
"circuit breaker",
|
|
5279
|
-
"degrade",
|
|
5280
|
-
"graceful",
|
|
5281
|
-
"monitor",
|
|
5282
|
-
"benchmark"
|
|
5283
|
-
];
|
|
5284
|
-
var NOGO_SIGNALS = [
|
|
5285
|
-
"no-go",
|
|
5286
|
-
"nogo",
|
|
5287
|
-
"won't",
|
|
5288
|
-
"will not",
|
|
5289
|
-
"must not",
|
|
5290
|
-
"not building",
|
|
5291
|
-
"out of scope",
|
|
5292
|
-
"explicitly excluded",
|
|
5293
|
-
"defer",
|
|
5294
|
-
"not in v1",
|
|
5295
|
-
"not in this bet"
|
|
5296
|
-
];
|
|
5297
|
-
var ARCHITECTURE_SIGNALS = [
|
|
5298
|
-
"layer",
|
|
5299
|
-
"boundary",
|
|
5300
|
-
"dependency",
|
|
5301
|
-
"api",
|
|
5302
|
-
"contract",
|
|
5303
|
-
"interface",
|
|
5304
|
-
"module",
|
|
5305
|
-
"component",
|
|
5306
|
-
"service",
|
|
5307
|
-
"schema",
|
|
5308
|
-
"mermaid",
|
|
5309
|
-
"diagram",
|
|
5310
|
-
"infra",
|
|
5311
|
-
"core",
|
|
5312
|
-
"feature"
|
|
5313
|
-
];
|
|
5314
|
-
var ARCH_QUALITY_SIGNALS = [
|
|
5315
|
-
"direction",
|
|
5316
|
-
"imports",
|
|
5317
|
-
"never imports",
|
|
5318
|
-
"http only",
|
|
5319
|
-
"separation",
|
|
5320
|
-
"encapsulat",
|
|
5321
|
-
"decouple",
|
|
5322
|
-
"isolat"
|
|
5323
|
-
];
|
|
5324
|
-
var DONE_WHEN_SIGNALS = [
|
|
5325
|
-
"done when",
|
|
5326
|
-
"acceptance criteria",
|
|
5327
|
-
"verified",
|
|
5328
|
-
"test plan",
|
|
5329
|
-
"validation",
|
|
5330
|
-
"rollback",
|
|
5331
|
-
"gate",
|
|
5332
|
-
"threshold",
|
|
5333
|
-
"metric",
|
|
5334
|
-
"success",
|
|
5335
|
-
"p95",
|
|
5336
|
-
"p99"
|
|
5337
|
-
];
|
|
5338
|
-
function countMatches(text, signals) {
|
|
5339
|
-
const lower = text.toLowerCase();
|
|
5340
|
-
return signals.filter((s) => lower.includes(s)).length;
|
|
5341
|
-
}
|
|
5342
|
-
function clamp(n, min, max) {
|
|
5343
|
-
return Math.max(min, Math.min(max, n));
|
|
5344
|
-
}
|
|
5345
|
-
function scoreProblemClarity(ctx) {
|
|
5346
|
-
const text = ctx.dimensionTexts.problem_clarity;
|
|
5347
|
-
const missing = [];
|
|
5348
|
-
const satisfied = [];
|
|
5349
|
-
const criteria = [];
|
|
5350
|
-
let score = 0;
|
|
5351
|
-
const hasWorkaround = countMatches(text, WORKAROUND_SIGNALS) > 0;
|
|
5352
|
-
criteria.push({ label: "Current workaround described", met: hasWorkaround, weight: 3 });
|
|
5353
|
-
if (hasWorkaround) {
|
|
5354
|
-
satisfied.push("Current workaround described");
|
|
5355
|
-
score += 3;
|
|
5356
|
-
} else {
|
|
5357
|
-
missing.push("Describe the current workaround \u2014 how do people deal with this today?");
|
|
5358
|
-
}
|
|
5359
|
-
const hasAffected = countMatches(text, AFFECTED_SIGNALS) > 0;
|
|
5360
|
-
criteria.push({ label: "Affected people identified", met: hasAffected, weight: 2 });
|
|
5361
|
-
if (hasAffected) {
|
|
5362
|
-
satisfied.push("Affected people identified");
|
|
5363
|
-
score += 2;
|
|
5364
|
-
} else {
|
|
5365
|
-
missing.push("Who experiences this problem? Be specific about the role or persona.");
|
|
5366
|
-
}
|
|
5367
|
-
const hasFrequency = countMatches(text, FREQUENCY_SIGNALS) > 0;
|
|
5368
|
-
criteria.push({ label: "Frequency or severity stated", met: hasFrequency, weight: 2 });
|
|
5369
|
-
if (hasFrequency) {
|
|
5370
|
-
satisfied.push("Frequency or severity stated");
|
|
5371
|
-
score += 2;
|
|
5372
|
-
} else {
|
|
5373
|
-
missing.push("How often does this happen, or how severe is it when it does?");
|
|
5374
|
-
}
|
|
5375
|
-
if (ctx.existingEntryIds.length > 0) {
|
|
5376
|
-
satisfied.push(`Differentiated from ${ctx.existingEntryIds.length} existing entries`);
|
|
5377
|
-
criteria.push({ label: "Differentiated from existing entries", met: true, weight: 2 });
|
|
5378
|
-
score += 2;
|
|
5379
|
-
} else if (/\b(TEN|DEC|ENT|FEAT|BET)-\w+/i.test(text)) {
|
|
5380
|
-
satisfied.push("References existing Chain entries");
|
|
5381
|
-
criteria.push({ label: "References existing Chain entries", met: true, weight: 1 });
|
|
5382
|
-
score += 1;
|
|
5383
|
-
} else {
|
|
5384
|
-
missing.push("How does this differ from existing tensions or bets on the Chain?");
|
|
5385
|
-
criteria.push({ label: "Differentiated from existing entries", met: false, weight: 2 });
|
|
5386
|
-
}
|
|
5387
|
-
const isSubstantive = text.length > 200;
|
|
5388
|
-
criteria.push({ label: "Substantive description provided", met: isSubstantive, weight: 1 });
|
|
5389
|
-
if (isSubstantive) {
|
|
5390
|
-
satisfied.push("Substantive description provided");
|
|
5391
|
-
score += 1;
|
|
5392
|
-
}
|
|
5393
|
-
return { score: clamp(score, 0, 10), missing, satisfied, criteria };
|
|
5394
|
-
}
|
|
5395
|
-
function scoreAppetite(ctx) {
|
|
5396
|
-
const text = ctx.dimensionTexts.appetite;
|
|
5397
|
-
const missing = [];
|
|
5398
|
-
const satisfied = [];
|
|
5399
|
-
const criteria = [];
|
|
5400
|
-
let score = 0;
|
|
5401
|
-
const hasSizeDecl = /small\s*batch|big\s*batch|[1-6]\s*week/i.test(text);
|
|
5402
|
-
const hasTimeSignals = countMatches(text, APPETITE_SIGNALS) > 0;
|
|
5403
|
-
if (hasSizeDecl) {
|
|
5404
|
-
satisfied.push("Size and timeframe declared");
|
|
5405
|
-
criteria.push({ label: "Size and timeframe declared", met: true, weight: 4 });
|
|
5406
|
-
score += 4;
|
|
5407
|
-
} else if (hasTimeSignals) {
|
|
5408
|
-
satisfied.push("Time constraint mentioned");
|
|
5409
|
-
criteria.push({ label: "Size and timeframe declared", met: false, weight: 4 });
|
|
5410
|
-
criteria.push({ label: "Time constraint mentioned", met: true, weight: 2 });
|
|
5411
|
-
score += 2;
|
|
5412
|
-
missing.push("Declare the batch size explicitly \u2014 Small Batch (1-2 weeks) or Big Batch (6 weeks).");
|
|
5413
|
-
} else {
|
|
5414
|
-
criteria.push({ label: "Size and timeframe declared", met: false, weight: 4 });
|
|
5415
|
-
missing.push("Set a time constraint \u2014 how long is this bet allowed to take?");
|
|
5416
|
-
}
|
|
5417
|
-
const hasTradeoffs = countMatches(text, SCOPE_SIGNALS) > 0;
|
|
5418
|
-
criteria.push({ label: "Scope bounded with trade-offs", met: hasTradeoffs, weight: 2 });
|
|
5419
|
-
if (hasTradeoffs) {
|
|
5420
|
-
satisfied.push("Scope bounded with trade-offs");
|
|
5421
|
-
score += 2;
|
|
5422
|
-
} else {
|
|
5423
|
-
missing.push("What trade-offs are you making? What's the 80% cut?");
|
|
5424
|
-
}
|
|
5425
|
-
const hasPhased = /phase\s*[a-c1-3]|phased|milestone/i.test(text);
|
|
5426
|
-
criteria.push({ label: "Phased delivery strategy", met: hasPhased, weight: 2 });
|
|
5427
|
-
if (hasPhased) {
|
|
5428
|
-
satisfied.push("Phased delivery strategy");
|
|
5429
|
-
score += 2;
|
|
5430
|
-
}
|
|
5431
|
-
const hasGate = /gate|validate|dogfood|benchmark|circuit.?breaker/i.test(text);
|
|
5432
|
-
criteria.push({ label: "Validation gate defined", met: hasGate, weight: 2 });
|
|
5433
|
-
if (hasGate) {
|
|
5434
|
-
satisfied.push("Validation gate defined");
|
|
5435
|
-
score += 2;
|
|
5436
|
-
} else {
|
|
5437
|
-
missing.push("How will you know if this bet is working before full commitment?");
|
|
5438
|
-
}
|
|
5439
|
-
return { score: clamp(score, 0, 10), missing, satisfied, criteria };
|
|
5440
|
-
}
|
|
5441
|
-
function scoreElements(ctx) {
|
|
5442
|
-
const text = ctx.dimensionTexts.elements;
|
|
5443
|
-
const missing = [];
|
|
5444
|
-
const satisfied = [];
|
|
5445
|
-
const criteria = [];
|
|
5446
|
-
let score = 0;
|
|
5447
|
-
const headingCount = (text.match(/###\s*Element\s*\d/gi) ?? []).length;
|
|
5448
|
-
const inlineCount = (text.match(/element\s*\d|component\s*\d|piece\s*\d/gi) ?? []).length;
|
|
5449
|
-
const ordinalCount = (text.match(/\b(first|second|third|fourth|fifth)\b[,:]?\s/gi) ?? []).length;
|
|
5450
|
-
const numberedCount = (text.match(/^\s*\d+[.)]\s/gm) ?? []).length;
|
|
5451
|
-
const namedCount = (text.match(/\belement\s+\d|[A-Z][a-z]+ (?:Engine|Store|Gate|Manager|Service|Module|Handler|Layer|Registry|Pipeline)\b/g) ?? []).length;
|
|
5452
|
-
const textDescribed = Math.max(headingCount, inlineCount, ordinalCount, numberedCount, namedCount);
|
|
5453
|
-
let elemMet = true;
|
|
5454
|
-
let elemWeight = 0;
|
|
5455
|
-
if (ctx.elementCount >= 3) {
|
|
5456
|
-
satisfied.push(`${ctx.elementCount} elements captured on Chain`);
|
|
5457
|
-
elemWeight = 5;
|
|
5458
|
-
score += 5;
|
|
5459
|
-
} else if (ctx.elementCount > 0) {
|
|
5460
|
-
satisfied.push(`${ctx.elementCount} element(s) captured`);
|
|
5461
|
-
elemWeight = ctx.elementCount * 2;
|
|
5462
|
-
score += ctx.elementCount * 2;
|
|
5463
|
-
if (ctx.elementCount < 3) {
|
|
5464
|
-
missing.push(`Identify ${3 - ctx.elementCount} more solution elements for adequate decomposition.`);
|
|
5465
|
-
}
|
|
5466
|
-
} else if (textDescribed >= 3) {
|
|
5467
|
-
satisfied.push(`${textDescribed} elements described in text`);
|
|
5468
|
-
elemWeight = 4;
|
|
5469
|
-
score += 4;
|
|
5470
|
-
missing.push("Capture these elements as typed entries on the Chain.");
|
|
5471
|
-
} else if (textDescribed > 0) {
|
|
5472
|
-
satisfied.push(`${textDescribed} element(s) described in text`);
|
|
5473
|
-
elemWeight = textDescribed * 2;
|
|
5474
|
-
score += textDescribed * 2;
|
|
5475
|
-
missing.push("Identify more solution elements \u2014 aim for 3+ independently describable pieces.");
|
|
5476
|
-
} else {
|
|
5477
|
-
elemMet = false;
|
|
5478
|
-
elemWeight = 5;
|
|
5479
|
-
missing.push("Identify solution elements \u2014 breadboard-level pieces, each independently describable.");
|
|
5480
|
-
}
|
|
5481
|
-
criteria.push({ label: "Solution elements identified", met: elemMet, weight: elemWeight });
|
|
5482
|
-
const totalElements = Math.max(ctx.elementCount, textDescribed);
|
|
5483
|
-
const decompMet = totalElements >= 2;
|
|
5484
|
-
let decompWeight;
|
|
5485
|
-
if (totalElements >= 3) {
|
|
5486
|
-
satisfied.push("Multiple independently describable pieces");
|
|
5487
|
-
decompWeight = 3;
|
|
5488
|
-
score += 3;
|
|
5489
|
-
} else if (totalElements >= 2) {
|
|
5490
|
-
satisfied.push("Multiple independently describable pieces");
|
|
5491
|
-
decompWeight = 2;
|
|
5492
|
-
score += 2;
|
|
5493
|
-
} else {
|
|
5494
|
-
decompWeight = 3;
|
|
5495
|
-
}
|
|
5496
|
-
criteria.push({ label: "Adequate decomposition (3+ pieces)", met: decompMet, weight: decompWeight });
|
|
5497
|
-
const govMet = ctx.governanceCount > 0;
|
|
5498
|
-
criteria.push({ label: "Governance constraints applied", met: govMet, weight: 1 });
|
|
5499
|
-
if (govMet) {
|
|
5500
|
-
satisfied.push(`${ctx.governanceCount} governance entries constrain the solution`);
|
|
5501
|
-
score += 1;
|
|
5502
|
-
}
|
|
5503
|
-
return { score: clamp(score, 0, 10), missing, satisfied, criteria };
|
|
5504
|
-
}
|
|
5505
|
-
function scoreArchitecture(ctx) {
|
|
5506
|
-
const text = ctx.dimensionTexts.architecture;
|
|
5507
|
-
const missing = [];
|
|
5508
|
-
const satisfied = [];
|
|
5509
|
-
const criteria = [];
|
|
5510
|
-
let score = 0;
|
|
5511
|
-
const hasArchSection = ctx.hasArchitectureText;
|
|
5512
|
-
criteria.push({ label: "Architecture section present", met: hasArchSection, weight: 3 });
|
|
5513
|
-
if (hasArchSection) {
|
|
5514
|
-
satisfied.push("Architecture section present in bet");
|
|
5515
|
-
score += 3;
|
|
5516
|
-
} else {
|
|
5517
|
-
missing.push("Where does this live in the architecture? Name each layer and what belongs where.");
|
|
5518
|
-
}
|
|
5519
|
-
const archSignals = countMatches(text, ARCHITECTURE_SIGNALS);
|
|
5520
|
-
const archVocabMet = archSignals > 0;
|
|
5521
|
-
const archVocabWeight = archSignals >= 3 ? 2 : archSignals > 0 ? 1 : 2;
|
|
5522
|
-
criteria.push({ label: "Architecture vocabulary used", met: archVocabMet, weight: archVocabWeight });
|
|
5523
|
-
if (archSignals >= 3) {
|
|
5524
|
-
satisfied.push("Architecture vocabulary used");
|
|
5525
|
-
score += 2;
|
|
5526
|
-
} else if (archSignals > 0) {
|
|
5527
|
-
score += 1;
|
|
5528
|
-
} else {
|
|
5529
|
-
missing.push("Describe the API boundary, modules, and services involved.");
|
|
5530
|
-
}
|
|
5531
|
-
const hasDependencyDir = countMatches(text, ARCH_QUALITY_SIGNALS) > 0;
|
|
5532
|
-
criteria.push({ label: "Dependency direction specified", met: hasDependencyDir, weight: 2 });
|
|
5533
|
-
if (hasDependencyDir) {
|
|
5534
|
-
satisfied.push("Dependency direction / boundaries specified");
|
|
5535
|
-
score += 2;
|
|
5536
|
-
} else {
|
|
5537
|
-
missing.push("What dependencies does this create or remove? Specify direction.");
|
|
5538
|
-
}
|
|
5539
|
-
const hasDiagram = /mermaid|diagram|flowchart|graph/i.test(text);
|
|
5540
|
-
criteria.push({ label: "Visual architecture diagram", met: hasDiagram, weight: 2 });
|
|
5541
|
-
if (hasDiagram) {
|
|
5542
|
-
satisfied.push("Visual architecture representation");
|
|
5543
|
-
score += 2;
|
|
5544
|
-
} else {
|
|
5545
|
-
missing.push("Add an architecture diagram (Mermaid) showing layers and data flow.");
|
|
5546
|
-
}
|
|
5547
|
-
const govMet = ctx.governanceCount > 0;
|
|
5548
|
-
criteria.push({ label: "Checked against governance", met: govMet, weight: 1 });
|
|
5549
|
-
if (govMet) {
|
|
5550
|
-
satisfied.push("Architecture checked against governance");
|
|
5551
|
-
score += 1;
|
|
5552
|
-
}
|
|
5553
|
-
return { score: clamp(score, 0, 10), missing, satisfied, criteria };
|
|
5554
|
-
}
|
|
5555
|
-
function scoreRisks(ctx) {
|
|
5556
|
-
const text = ctx.dimensionTexts.risks;
|
|
5557
|
-
const missing = [];
|
|
5558
|
-
const satisfied = [];
|
|
5559
|
-
const criteria = [];
|
|
5560
|
-
let score = 0;
|
|
5561
|
-
const riskSignalCount = countMatches(text, RISK_SIGNALS);
|
|
5562
|
-
const textRiskCount = (text.match(/rabbit hole|risk:|risk \d|R\d:/gi) ?? []).length;
|
|
5563
|
-
const totalRisks = Math.max(ctx.riskCount, textRiskCount, Math.min(riskSignalCount, 3));
|
|
5564
|
-
let riskMet = true;
|
|
5565
|
-
let riskWeight = 0;
|
|
5566
|
-
if (ctx.riskCount >= 2) {
|
|
5567
|
-
satisfied.push(`${ctx.riskCount} risks captured on Chain`);
|
|
5568
|
-
riskWeight = 4;
|
|
5569
|
-
score += 4;
|
|
5570
|
-
} else if (totalRisks >= 2) {
|
|
5571
|
-
satisfied.push(`${totalRisks} risks identified`);
|
|
5572
|
-
riskWeight = 3;
|
|
5573
|
-
score += 3;
|
|
5574
|
-
if (ctx.riskCount === 0) missing.push("Capture identified risks as entries on the Chain.");
|
|
5575
|
-
} else if (totalRisks > 0 || riskSignalCount > 0) {
|
|
5576
|
-
satisfied.push("Risk language present");
|
|
5577
|
-
riskWeight = 2;
|
|
5578
|
-
score += 2;
|
|
5579
|
-
missing.push("Identify more risks \u2014 aim for 2+ rabbit holes with mitigations.");
|
|
5580
|
-
} else {
|
|
5581
|
-
riskMet = false;
|
|
5582
|
-
riskWeight = 4;
|
|
5583
|
-
missing.push("Name the rabbit holes \u2014 what could go wrong, what's unknown?");
|
|
5584
|
-
}
|
|
5585
|
-
criteria.push({ label: "Risks identified", met: riskMet, weight: riskWeight });
|
|
5586
|
-
const mitigationCount = countMatches(text, MITIGATION_SIGNALS);
|
|
5587
|
-
let mitigMet = mitigationCount > 0;
|
|
5588
|
-
let mitigWeight;
|
|
5589
|
-
if (mitigationCount >= 2) {
|
|
5590
|
-
satisfied.push("Multiple mitigations specified");
|
|
5591
|
-
mitigWeight = 3;
|
|
5592
|
-
score += 3;
|
|
5593
|
-
} else if (mitigationCount > 0) {
|
|
5594
|
-
satisfied.push("Mitigation present");
|
|
5595
|
-
mitigWeight = 2;
|
|
5596
|
-
score += 2;
|
|
5597
|
-
missing.push("Each risk should have a mitigation or explicit acceptance.");
|
|
5598
|
-
} else {
|
|
5599
|
-
mitigMet = false;
|
|
5600
|
-
mitigWeight = 3;
|
|
5601
|
-
missing.push("Each risk needs a mitigation or explicit acceptance.");
|
|
5602
|
-
}
|
|
5603
|
-
criteria.push({ label: "Mitigations specified", met: mitigMet, weight: mitigWeight });
|
|
5604
|
-
const hasCodebaseRisks = /codebase|architecture|coupling|dependency|schema|migration/i.test(text);
|
|
5605
|
-
criteria.push({ label: "Codebase-level risks identified", met: hasCodebaseRisks, weight: 2 });
|
|
5606
|
-
if (hasCodebaseRisks) {
|
|
5607
|
-
satisfied.push("Codebase-level risks identified");
|
|
5608
|
-
score += 2;
|
|
5609
|
-
} else {
|
|
5610
|
-
missing.push("Are there high-risk areas in the codebase? Check architecture and dependencies.");
|
|
5611
|
-
}
|
|
5612
|
-
const thorough = totalRisks >= 3;
|
|
5613
|
-
criteria.push({ label: "Thorough risk coverage", met: thorough, weight: 1 });
|
|
5614
|
-
if (thorough) {
|
|
5615
|
-
satisfied.push("Thorough risk coverage");
|
|
5616
|
-
score += 1;
|
|
5617
|
-
}
|
|
5618
|
-
return { score: clamp(score, 0, 10), missing, satisfied, criteria };
|
|
5619
|
-
}
|
|
5620
|
-
function scoreBoundaries(ctx) {
|
|
5621
|
-
const text = ctx.dimensionTexts.boundaries;
|
|
5622
|
-
const missing = [];
|
|
5623
|
-
const satisfied = [];
|
|
5624
|
-
const criteria = [];
|
|
5625
|
-
let score = 0;
|
|
5626
|
-
const textNoGos = Math.max(countMatches(text, NOGO_SIGNALS), (text.match(/\bwon'?t\b|\bwill not\b/gi) ?? []).length);
|
|
5627
|
-
const totalNoGos = Math.max(ctx.noGoCount, textNoGos);
|
|
5628
|
-
let nogoMet = totalNoGos > 0;
|
|
5629
|
-
let nogoWeight;
|
|
5630
|
-
if (totalNoGos >= 3) {
|
|
5631
|
-
satisfied.push(`${totalNoGos} explicit no-gos declared`);
|
|
5632
|
-
nogoWeight = 5;
|
|
5633
|
-
score += 5;
|
|
5634
|
-
} else if (totalNoGos > 0) {
|
|
5635
|
-
satisfied.push(`${totalNoGos} no-go(s) declared`);
|
|
5636
|
-
nogoWeight = totalNoGos * 2;
|
|
5637
|
-
score += totalNoGos * 2;
|
|
5638
|
-
missing.push("Add more explicit no-gos to prevent scope creep.");
|
|
5639
|
-
} else {
|
|
5640
|
-
nogoMet = false;
|
|
5641
|
-
nogoWeight = 5;
|
|
5642
|
-
missing.push("Declare what you're NOT building \u2014 explicit no-gos prevent scope creep.");
|
|
5643
|
-
}
|
|
5644
|
-
criteria.push({ label: "No-gos declared", met: nogoMet, weight: nogoWeight });
|
|
5645
|
-
const hasScopeCreep = /scope creep|prevent|boundary|limit|constrain|excluded/i.test(text);
|
|
5646
|
-
criteria.push({ label: "Scope creep prevention language", met: hasScopeCreep, weight: 2 });
|
|
5647
|
-
if (hasScopeCreep) {
|
|
5648
|
-
satisfied.push("Scope creep prevention language");
|
|
5649
|
-
score += 2;
|
|
5650
|
-
}
|
|
5651
|
-
const hasDirectional = /direction|instead.*will|rather.*than|not.*but/i.test(text);
|
|
5652
|
-
criteria.push({ label: "Directional no-gos", met: hasDirectional, weight: 2 });
|
|
5653
|
-
if (hasDirectional) {
|
|
5654
|
-
satisfied.push("Each no-go prevents scope creep in a specific direction");
|
|
5655
|
-
score += 2;
|
|
5656
|
-
} else if (totalNoGos > 0) {
|
|
5657
|
-
missing.push("Each no-go should prevent scope creep in a specific direction.");
|
|
5658
|
-
}
|
|
5659
|
-
const comprehensive = totalNoGos >= 5;
|
|
5660
|
-
criteria.push({ label: "Comprehensive boundary specification", met: comprehensive, weight: 1 });
|
|
5661
|
-
if (comprehensive) {
|
|
5662
|
-
satisfied.push("Comprehensive boundary specification");
|
|
5663
|
-
score += 1;
|
|
5664
|
-
}
|
|
5665
|
-
return { score: clamp(score, 0, 10), missing, satisfied, criteria };
|
|
5666
|
-
}
|
|
5667
|
-
function scoreDoneWhen(ctx) {
|
|
5668
|
-
const text = ctx.dimensionTexts.done_when ?? "";
|
|
5669
|
-
const missing = [];
|
|
5670
|
-
const satisfied = [];
|
|
5671
|
-
const criteria = [];
|
|
5672
|
-
let score = 0;
|
|
5673
|
-
const hasSection = text.trim().length > 0;
|
|
5674
|
-
criteria.push({ label: "Done-when section present", met: hasSection, weight: 3 });
|
|
5675
|
-
if (hasSection) {
|
|
5676
|
-
satisfied.push("Done-when section present");
|
|
5677
|
-
score += 3;
|
|
5678
|
-
} else {
|
|
5679
|
-
missing.push("Define explicit done-when criteria.");
|
|
5680
|
-
}
|
|
5681
|
-
const hasMeasurableTargets = /\b\d+%|\bp\d{2}|\b>=?\s*\d+|\b<=?\s*\d+|target|threshold|SLO|SLA|median/i.test(text);
|
|
5682
|
-
criteria.push({ label: "Measurable targets defined", met: hasMeasurableTargets, weight: 3 });
|
|
5683
|
-
if (hasMeasurableTargets) {
|
|
5684
|
-
satisfied.push("Measurable targets defined");
|
|
5685
|
-
score += 3;
|
|
5686
|
-
} else if (hasSection) {
|
|
5687
|
-
missing.push("Add measurable targets (thresholds, percentages, latency targets, or numeric criteria).");
|
|
5688
|
-
}
|
|
5689
|
-
const hasValidationPlan = /\btest|verify|validation|contract test|rollback|drill|gate/i.test(text);
|
|
5690
|
-
criteria.push({ label: "Validation plan specified", met: hasValidationPlan, weight: 2 });
|
|
5691
|
-
if (hasValidationPlan) {
|
|
5692
|
-
satisfied.push("Validation plan specified");
|
|
5693
|
-
score += 2;
|
|
5694
|
-
} else if (hasSection) {
|
|
5695
|
-
missing.push("Specify how success will be validated (tests, gates, rollback drills).");
|
|
5696
|
-
}
|
|
5697
|
-
const hasSignals = countMatches(text, DONE_WHEN_SIGNALS) >= 3;
|
|
5698
|
-
criteria.push({ label: "Operational signal coverage", met: hasSignals, weight: 2 });
|
|
5699
|
-
if (hasSignals) {
|
|
5700
|
-
satisfied.push("Operational signal coverage");
|
|
5701
|
-
score += 2;
|
|
5702
|
-
} else if (hasSection) {
|
|
5703
|
-
missing.push("Include operational signals (metrics, gates, and release thresholds).");
|
|
5704
|
-
}
|
|
5705
|
-
return { score: clamp(score, 0, 10), missing, satisfied, criteria };
|
|
5706
|
-
}
|
|
5707
|
-
var SCORERS = {
|
|
5708
|
-
problem_clarity: scoreProblemClarity,
|
|
5709
|
-
appetite: scoreAppetite,
|
|
5710
|
-
elements: scoreElements,
|
|
5711
|
-
architecture: scoreArchitecture,
|
|
5712
|
-
risks: scoreRisks,
|
|
5713
|
-
boundaries: scoreBoundaries,
|
|
5714
|
-
done_when: scoreDoneWhen
|
|
5715
|
-
};
|
|
5716
|
-
function scoreDimension(dimension, ctx) {
|
|
5717
|
-
const raw = SCORERS[dimension](ctx);
|
|
5718
|
-
if (ctx.source === "agent_proposal") {
|
|
5719
|
-
return {
|
|
5720
|
-
...raw,
|
|
5721
|
-
score: clamp(Math.round(raw.score * AGENT_DISCOUNT_FACTOR), 0, 10)
|
|
5722
|
-
};
|
|
5723
|
-
}
|
|
5724
|
-
return raw;
|
|
5725
|
-
}
|
|
5726
|
-
function scoreAll(ctx) {
|
|
5727
|
-
const results = {};
|
|
5728
|
-
for (const dim of DIMENSIONS) {
|
|
5729
|
-
results[dim] = scoreDimension(dim, ctx);
|
|
5730
|
-
}
|
|
5731
|
-
return results;
|
|
5732
|
-
}
|
|
5733
|
-
function buildScorecard(ctx) {
|
|
5734
|
-
const results = scoreAll(ctx);
|
|
5735
|
-
return {
|
|
5736
|
-
problem_clarity: results.problem_clarity.score,
|
|
5737
|
-
appetite: results.appetite.score,
|
|
5738
|
-
elements: results.elements.score,
|
|
5739
|
-
architecture: results.architecture.score,
|
|
5740
|
-
risks: results.risks.score,
|
|
5741
|
-
boundaries: results.boundaries.score,
|
|
5742
|
-
done_when: results.done_when.score
|
|
5743
|
-
};
|
|
5744
|
-
}
|
|
5745
|
-
function buildDetailedScorecard(ctx, opts) {
|
|
5746
|
-
const results = scoreAll(ctx);
|
|
5747
|
-
const scorecard = {};
|
|
5748
|
-
const criteria = {};
|
|
5749
|
-
for (const dim of DIMENSIONS) {
|
|
5750
|
-
scorecard[dim] = results[dim].score;
|
|
5751
|
-
criteria[dim] = results[dim].criteria;
|
|
5752
|
-
}
|
|
5753
|
-
if (opts?.isSmallBatch) scorecard.architecture = -1;
|
|
5754
|
-
return { scorecard, criteria };
|
|
5755
|
-
}
|
|
5756
|
-
function isCaptureReady(scorecard, isSmallBatch) {
|
|
5757
|
-
const completed = completedDimensions(scorecard, 6, isSmallBatch);
|
|
5758
|
-
const dims = activeDimensions(isSmallBatch);
|
|
5759
|
-
const ready = completed.length >= (isSmallBatch ? 5 : 6) && dims.every((d) => scorecard[d] >= 4);
|
|
5760
|
-
return { ready, completed };
|
|
5761
|
-
}
|
|
5762
|
-
function inferActiveDimension(scorecard, isSmallBatch = false) {
|
|
5763
|
-
const order = activeDimensions(isSmallBatch);
|
|
5764
|
-
const firstZero = order.find((d) => scorecard[d] === 0);
|
|
5765
|
-
if (firstZero) return firstZero;
|
|
5766
|
-
const firstWeak = order.find((d) => scorecard[d] < 6);
|
|
5767
|
-
if (firstWeak) return firstWeak;
|
|
5768
|
-
return order[order.length - 1];
|
|
5769
|
-
}
|
|
5770
|
-
function completedDimensions(scorecard, threshold = 6, isSmallBatch = false) {
|
|
5771
|
-
return activeDimensions(isSmallBatch).filter((d) => scorecard[d] >= threshold);
|
|
5772
|
-
}
|
|
5773
|
-
function suggestCaptures(ctx, activeDimension) {
|
|
5774
|
-
const suggestions = [];
|
|
5775
|
-
const text = ctx.dimensionTexts[activeDimension];
|
|
5776
|
-
if (text.length < 30) return suggestions;
|
|
5777
|
-
if (activeDimension === "problem_clarity" || activeDimension === "appetite") {
|
|
5778
|
-
if (countMatches(text, WORKAROUND_SIGNALS) > 0 && ctx.riskCount === 0) {
|
|
5779
|
-
const match = text.match(/(?:workaround|currently|today|right now)[^.]{0,120}\./i);
|
|
5780
|
-
if (match && isBalanced(match[0])) {
|
|
5781
|
-
suggestions.push({
|
|
5782
|
-
type: "tension",
|
|
5783
|
-
name: extractPhrase(match[0], 60),
|
|
5784
|
-
reason: "Workaround described \u2014 captures the pain point as a trackable tension",
|
|
5785
|
-
confidence: 0.7
|
|
5786
|
-
});
|
|
5787
|
-
}
|
|
5788
|
-
}
|
|
5789
|
-
}
|
|
5790
|
-
if (activeDimension === "elements") {
|
|
5791
|
-
if (ctx.elementCount === 0 && /(?:service|component|module|layer|store|engine|kernel)/i.test(text)) {
|
|
5792
|
-
const match = text.match(/(?:a |the )?(\w[\w\s]{2,30}(?:service|component|module|layer|store|engine|kernel))/i);
|
|
5793
|
-
if (match && isBalanced(match[1])) {
|
|
5794
|
-
suggestions.push({
|
|
5795
|
-
type: "element",
|
|
5796
|
-
name: capitalize(match[1].trim()),
|
|
5797
|
-
reason: "Solution component identified \u2014 capture as a feature entry",
|
|
5798
|
-
confidence: 0.8
|
|
5799
|
-
});
|
|
5800
|
-
}
|
|
5801
|
-
}
|
|
5802
|
-
}
|
|
5803
|
-
if (activeDimension === "risks") {
|
|
5804
|
-
if (ctx.riskCount === 0 && countMatches(text, RISK_SIGNALS) >= 2) {
|
|
5805
|
-
const match = text.match(/(?:risk|rabbit hole|unknown|might|could fail)[^.]{0,120}\./i);
|
|
5806
|
-
if (match && isBalanced(match[0])) {
|
|
5807
|
-
suggestions.push({
|
|
5808
|
-
type: "risk",
|
|
5809
|
-
name: extractPhrase(match[0], 60),
|
|
5810
|
-
reason: "Risk language detected \u2014 capture as a tension with mitigation",
|
|
5811
|
-
confidence: 0.7
|
|
5812
|
-
});
|
|
5813
|
-
}
|
|
5814
|
-
}
|
|
5815
|
-
}
|
|
5816
|
-
if (/(?:we(?:'ll| will) (?:use|go with|pick|choose)|decided to|decision:|instead of)/i.test(text)) {
|
|
5817
|
-
const match = text.match(/(?:we(?:'ll| will) (?:use|go with|pick|choose)|decided to|decision:)[^.]{0,120}\./i);
|
|
5818
|
-
if (match && isBalanced(match[0])) {
|
|
5819
|
-
suggestions.push({
|
|
5820
|
-
type: "decision",
|
|
5821
|
-
name: extractPhrase(match[0], 60),
|
|
5822
|
-
reason: "Decision language detected \u2014 capture rationale while it's fresh",
|
|
5823
|
-
confidence: 0.6
|
|
5824
|
-
});
|
|
5825
|
-
}
|
|
5826
|
-
}
|
|
5827
|
-
return suggestions.slice(0, 3);
|
|
5828
|
-
}
|
|
5829
|
-
function extractPhrase(text, maxLen) {
|
|
5830
|
-
const cleaned = text.replace(/^(?:the |a |an )/i, "").trim();
|
|
5831
|
-
return cleaned.length > maxLen ? cleaned.slice(0, maxLen - 3) + "..." : cleaned;
|
|
5832
|
-
}
|
|
5833
|
-
function capitalize(s) {
|
|
5834
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
5835
|
-
}
|
|
5836
|
-
function isBalanced(text) {
|
|
5837
|
-
const opens = (text.match(/[("']/g) ?? []).length;
|
|
5838
|
-
const closes = (text.match(/[)"']/g) ?? []).length;
|
|
5839
|
-
return Math.abs(opens - closes) <= 1;
|
|
5840
|
-
}
|
|
5841
|
-
var PHASE_INVESTIGATIONS = {
|
|
5842
|
-
context: (searchTerm) => ({
|
|
5843
|
-
phase: "context",
|
|
5844
|
-
dimension: "problem_clarity",
|
|
5845
|
-
tasks: [
|
|
5846
|
-
{ target: "chain", query: `Search for entries related to: ${searchTerm}`, purpose: "Find existing tensions, decisions, and bets that overlap" },
|
|
5847
|
-
{ target: "chain", query: "List active governance: principles, standards, business-rules", purpose: "Surface constraints the solution must honor" },
|
|
5848
|
-
{ target: "codebase", query: `Search for code related to: ${searchTerm}`, purpose: "Understand current implementation and pain points" }
|
|
5849
|
-
],
|
|
5850
|
-
proposalGuidance: "Synthesize findings into a 3-5 line context brief: what the Chain knows, what the codebase reveals, what governance applies.",
|
|
5851
|
-
reactionPrompt: "Here's what I found. Does this match what you're seeing, or is the problem different from what the Chain suggests?"
|
|
5852
|
-
}),
|
|
5853
|
-
framing: (searchTerm) => ({
|
|
5854
|
-
phase: "framing",
|
|
5855
|
-
dimension: "problem_clarity",
|
|
5856
|
-
tasks: [
|
|
5857
|
-
{ target: "codebase", query: `Find workarounds, TODOs, or hacks related to: ${searchTerm}`, purpose: "Surface concrete evidence of the problem" },
|
|
5858
|
-
{ target: "chain", query: `Search for tensions related to: ${searchTerm}`, purpose: "Quantify how often this problem occurs" }
|
|
5859
|
-
],
|
|
5860
|
-
proposalGuidance: "Propose a problem statement based on codebase evidence. Include who's affected and what the workaround looks like in the code.",
|
|
5861
|
-
reactionPrompt: "Based on the codebase, here's a draft problem statement. Does this capture it, or is the real problem different?"
|
|
5862
|
-
}),
|
|
5863
|
-
elements: (searchTerm, _betEntryId, elementNames) => ({
|
|
5864
|
-
phase: "elements",
|
|
5865
|
-
dimension: "elements",
|
|
5866
|
-
tasks: [
|
|
5867
|
-
{ target: "codebase", query: `Find modules, services, and components that would be affected by: ${searchTerm}`, purpose: "Map the solution to existing architecture" },
|
|
5868
|
-
...elementNames?.length ? [{ target: "codebase", query: `Locate existing code paths for these elements: ${elementNames.join(", ")}`, purpose: "Ground each proposed element in current implementation" }] : [],
|
|
5869
|
-
{ target: "architecture", query: "Identify layer boundaries, API contracts, and dependency directions", purpose: "Ground elements in real architecture" },
|
|
5870
|
-
{ target: "chain", query: "Check DEC-31, STA-3, and other architecture standards", purpose: "Ensure elements respect existing decisions" }
|
|
5871
|
-
],
|
|
5872
|
-
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.",
|
|
5873
|
-
reactionPrompt: "I've mapped out these solution elements based on the codebase. Which feel right? Which need adjustment?"
|
|
5874
|
-
}),
|
|
5875
|
-
derisking: (searchTerm, _betEntryId, elementNames) => ({
|
|
5876
|
-
phase: "derisking",
|
|
5877
|
-
dimension: "risks",
|
|
5878
|
-
tasks: [
|
|
5879
|
-
{ target: "codebase", query: `Find fragile code, tight coupling, or missing tests near: ${searchTerm}`, purpose: "Surface technical risks from code" },
|
|
5880
|
-
...elementNames?.length ? [{ target: "codebase", query: `Check implementation complexity for: ${elementNames.join(", ")}`, purpose: "Assess element-specific risks" }] : [{ target: "codebase", query: "Check for performance-sensitive paths, database queries, and external dependencies", purpose: "Identify performance and reliability risks" }],
|
|
5881
|
-
{ target: "chain", query: "Search for related tensions and past decisions that constrain this solution", purpose: "Surface risks from existing constraints" }
|
|
5882
|
-
],
|
|
5883
|
-
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.",
|
|
5884
|
-
reactionPrompt: "These are the risks I found in the codebase. Are these real concerns? What am I missing?"
|
|
5885
|
-
}),
|
|
5886
|
-
validation: () => null,
|
|
5887
|
-
capture: () => null
|
|
5888
|
-
};
|
|
5889
|
-
function buildInvestigationBrief(phase, betName, betEntryId, betProblem, elementNames) {
|
|
5890
|
-
const searchTerm = betProblem?.slice(0, 200) || betName;
|
|
5891
|
-
const builder = PHASE_INVESTIGATIONS[phase];
|
|
5892
|
-
return builder(searchTerm, betEntryId, elementNames);
|
|
5893
|
-
}
|
|
5894
|
-
function generateBuildContract(ctx) {
|
|
5895
|
-
const lines = ["## Build Contract"];
|
|
5896
|
-
const consult = [
|
|
5897
|
-
...ctx.governanceEntries.map((e) => `- \`${e.entryId}\` ${e.name} [${e.collection}]`),
|
|
5898
|
-
...ctx.relatedDecisions.map((e) => `- \`${e.entryId}\` ${e.name} [decision]`),
|
|
5899
|
-
...ctx.relatedTensions.map((e) => `- \`${e.entryId}\` ${e.name} [tension]`)
|
|
5900
|
-
];
|
|
5901
|
-
if (consult.length > 0) {
|
|
5902
|
-
lines.push("", "**Chain entries to consult before building:**");
|
|
5903
|
-
lines.push(...consult);
|
|
5904
|
-
} else {
|
|
5905
|
-
lines.push("", "**Chain entries to consult:** Run `orient` to discover applicable rules.");
|
|
5906
|
-
}
|
|
5907
|
-
lines.push(
|
|
5908
|
-
"",
|
|
5909
|
-
"**Capture obligations during build:**",
|
|
5910
|
-
"- New glossary terms that emerge from implementation",
|
|
5911
|
-
"- Implementation decisions not covered in shaping",
|
|
5912
|
-
"- Bugs or tensions discovered during build",
|
|
5913
|
-
"- Any deviation from the shaped solution (capture as decision with rationale)",
|
|
5914
|
-
"",
|
|
5915
|
-
"**Post-build Chain update:**",
|
|
5916
|
-
`- Update \`${ctx.betEntryId}\` with actual outcome vs. shaped intent`,
|
|
5917
|
-
"- Commit any draft entries created during the build",
|
|
5918
|
-
"- Capture a retrospective if the build deviated significantly"
|
|
5919
|
-
);
|
|
5920
|
-
return lines.join("\n");
|
|
5921
|
-
}
|
|
5922
|
-
|
|
5923
|
-
// src/tools/facilitate-validation.ts
|
|
5924
|
-
function computeCommitBlockers(opts) {
|
|
5925
|
-
const { betEntryId, betDocId, relations, hasStrategyLink, betData, sessionDrafts } = opts;
|
|
5926
|
-
const blockers = [];
|
|
5927
|
-
const links = betData.links ?? {};
|
|
5928
|
-
const str = (key) => (links[key] ?? "").trim();
|
|
5929
|
-
if (!hasStrategyLink) {
|
|
5930
|
-
blockers.push({
|
|
5931
|
-
entryId: betEntryId,
|
|
5932
|
-
blocker: "Missing strategy link",
|
|
5933
|
-
fix: `relations action=create from=${betEntryId} to=<strategy> type=related_to`
|
|
5934
|
-
});
|
|
5935
|
-
}
|
|
5936
|
-
const MIN_FIELD_LENGTH = 20;
|
|
5937
|
-
if (str("problem").length < MIN_FIELD_LENGTH) {
|
|
5938
|
-
blockers.push({
|
|
5939
|
-
entryId: betEntryId,
|
|
5940
|
-
blocker: str("problem") ? "Problem statement too short (< 20 chars)" : "Missing problem statement",
|
|
5941
|
-
fix: `update-entry entryId="${betEntryId}" data.problem="<problem description>"`
|
|
5942
|
-
});
|
|
5943
|
-
}
|
|
5944
|
-
if (!str("appetite")) {
|
|
5945
|
-
blockers.push({
|
|
5946
|
-
entryId: betEntryId,
|
|
5947
|
-
blocker: "Missing appetite",
|
|
5948
|
-
fix: `update-entry entryId="${betEntryId}" data.appetite="<appetite declaration>"`
|
|
5949
|
-
});
|
|
5950
|
-
}
|
|
5951
|
-
if (str("elements").length < MIN_FIELD_LENGTH) {
|
|
5952
|
-
blockers.push({
|
|
5953
|
-
entryId: betEntryId,
|
|
5954
|
-
blocker: str("elements") ? "Solution elements too short (< 20 chars)" : "Missing solution elements",
|
|
5955
|
-
fix: `facilitate action=respond betEntryId="${betEntryId}" dimension="elements"`
|
|
5956
|
-
});
|
|
5957
|
-
}
|
|
5958
|
-
const hasFeatures = sessionDrafts.some((d) => d.collection === "features");
|
|
5959
|
-
if (!hasFeatures && str("elements").length >= MIN_FIELD_LENGTH) {
|
|
5960
|
-
blockers.push({
|
|
5961
|
-
entryId: betEntryId,
|
|
5962
|
-
blocker: "Elements described but no feature entries linked in constellation",
|
|
5963
|
-
fix: `facilitate action=respond betEntryId="${betEntryId}" capture={type:"element", name:"...", description:"..."}`
|
|
5964
|
-
});
|
|
5965
|
-
}
|
|
5966
|
-
for (const draft of sessionDrafts) {
|
|
5967
|
-
if (!draft.name || draft.name.trim().length === 0) {
|
|
5968
|
-
const draftRef = draft.entryId && draft.entryId.trim().length > 0 ? draft.entryId : "<draftId>";
|
|
5969
|
-
blockers.push({
|
|
5970
|
-
entryId: betEntryId,
|
|
5971
|
-
blocker: `Draft constellation entry is missing a name`,
|
|
5972
|
-
fix: `update-entry entryId="${draftRef}" name="<name>"`
|
|
5973
|
-
});
|
|
5974
|
-
}
|
|
5975
|
-
}
|
|
5976
|
-
return blockers;
|
|
5977
|
-
}
|
|
5978
|
-
|
|
5979
|
-
// src/tools/facilitate-format.ts
|
|
5980
|
-
function formatCriteriaLine(criteria) {
|
|
5981
|
-
return criteria.map((c) => `${c.met ? "\u2713" : "\u25CB"} ${c.label} (${c.weight}pt)`).join(" \xB7 ");
|
|
5982
|
-
}
|
|
5983
|
-
function appendElement(existing, element) {
|
|
5984
|
-
const existingCount = (existing?.match(/###\s*Element\s*\d/gi) ?? []).length;
|
|
5985
|
-
const num = existingCount + 1;
|
|
5986
|
-
const id = element.entryId ? ` (${element.entryId})` : "";
|
|
5987
|
-
const newBlock = `### Element ${num}: ${element.name}${id}
|
|
5988
|
-
|
|
5989
|
-
${element.description}`;
|
|
5990
|
-
return existing ? `${existing}
|
|
5991
|
-
|
|
5992
|
-
${newBlock}` : newBlock;
|
|
5993
|
-
}
|
|
5994
|
-
function appendRabbitHole(existing, risk) {
|
|
5995
|
-
const existingCount = (existing?.match(/###\s*RH\d/gi) ?? []).length;
|
|
5996
|
-
const num = existingCount + 1;
|
|
5997
|
-
const theme = risk.theme ? ` (${risk.theme})` : "";
|
|
5998
|
-
const status = risk.status ? ` \u2014 ${risk.status}` : "";
|
|
5999
|
-
const id = risk.entryId ? ` [${risk.entryId}]` : "";
|
|
6000
|
-
const newBlock = `### RH${num}: ${risk.name}${theme}${status}${id}
|
|
6001
|
-
|
|
6002
|
-
${risk.description}`;
|
|
6003
|
-
return existing ? `${existing}
|
|
6004
|
-
|
|
6005
|
-
${newBlock}` : newBlock;
|
|
6006
|
-
}
|
|
6007
|
-
function appendNoGo(existing, item) {
|
|
6008
|
-
const line = `- **${item.title}.** ${item.explanation}`;
|
|
6009
|
-
return existing ? `${existing}
|
|
6010
|
-
${line}` : line;
|
|
6011
|
-
}
|
|
6012
|
-
|
|
6013
|
-
// src/tools/facilitate.ts
|
|
6014
|
-
var FACILITATE_ACTIONS = ["start", "respond", "score", "resume", "commit-constellation"];
|
|
6015
|
-
var captureItemSchema = z12.object({
|
|
6016
|
-
type: z12.enum(["element", "risk", "noGo", "decision"]).describe("What to capture"),
|
|
6017
|
-
name: z12.string().describe("Entry name"),
|
|
6018
|
-
description: z12.string().describe("Entry description"),
|
|
6019
|
-
theme: z12.string().optional().describe("Risk theme (for risk type)")
|
|
6020
|
-
});
|
|
5077
|
+
var FACILITATE_ACTIONS = ["resume", "commit-constellation"];
|
|
6021
5078
|
var facilitateSchema = z12.object({
|
|
6022
5079
|
action: z12.enum(FACILITATE_ACTIONS).describe(
|
|
6023
|
-
"'
|
|
5080
|
+
"'resume': load session state from an existing bet entry. 'commit-constellation': atomically commit a bet and all its linked draft entries in one call. Requires betEntryId."
|
|
6024
5081
|
),
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
operationId: z12.string().optional().describe("Optional idempotency key for commit-constellation retries."),
|
|
6028
|
-
userInput: z12.string().optional().describe("User's input text for respond action."),
|
|
6029
|
-
dimension: z12.string().optional().describe("Explicit dimension to score against (e.g. 'problem_clarity'). If omitted, inferred from scorecard."),
|
|
6030
|
-
betName: z12.string().optional().describe("Name for the bet (used in start action)."),
|
|
6031
|
-
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)."),
|
|
6032
|
-
capture: z12.union([
|
|
6033
|
-
captureItemSchema,
|
|
6034
|
-
z12.array(captureItemSchema).max(15)
|
|
6035
|
-
]).optional().describe("Entry or entries to capture alongside the respond action. Accepts a single object or an array (max 15) for batch capture.")
|
|
5082
|
+
betEntryId: z12.string().optional().describe("Bet entry ID. Required for both actions."),
|
|
5083
|
+
operationId: z12.string().optional().describe("Optional idempotency key for commit-constellation retries.")
|
|
6036
5084
|
});
|
|
6037
|
-
async function hasStrategyLinkForEntry(entryDocId, relations) {
|
|
6038
|
-
const relatedDocIds = /* @__PURE__ */ new Set();
|
|
6039
|
-
for (const rel of relations) {
|
|
6040
|
-
if (rel.fromId === entryDocId && rel.toId) relatedDocIds.add(rel.toId);
|
|
6041
|
-
if (rel.toId === entryDocId && rel.fromId) relatedDocIds.add(rel.fromId);
|
|
6042
|
-
}
|
|
6043
|
-
for (const docId of relatedDocIds) {
|
|
6044
|
-
try {
|
|
6045
|
-
const related = await mcpQuery("chain.getEntry", { id: docId });
|
|
6046
|
-
if (related?.collectionSlug === "strategy") return true;
|
|
6047
|
-
} catch {
|
|
6048
|
-
}
|
|
6049
|
-
}
|
|
6050
|
-
return false;
|
|
6051
|
-
}
|
|
6052
|
-
function registerFacilitateTools(server) {
|
|
6053
|
-
server.registerTool(
|
|
6054
|
-
"facilitate",
|
|
6055
|
-
{
|
|
6056
|
-
title: "Facilitate \u2014 Coached Shaping",
|
|
6057
|
-
description: "Server-controlled coached shaping session with real-time Chain capture.\n\n- **start**: Begin a coached session context only. No bet entry is created yet.\n- **first respond**: Call respond with `betName` to create the draft bet and capture initial problem context.\n- **respond**: Process user input \u2014 scores against 7 shaping rubrics (problem, appetite, elements, architecture, risks, boundaries, done-when), 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- **commit-constellation**: Atomically commit a bet and all its linked draft entries (features, tensions, decisions) in one call. Validates strategy link and required fields before committing anything. Replaces 9-13 sequential commit-entry calls.\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.",
|
|
6058
|
-
inputSchema: facilitateSchema,
|
|
6059
|
-
annotations: {
|
|
6060
|
-
readOnlyHint: false,
|
|
6061
|
-
destructiveHint: false,
|
|
6062
|
-
idempotentHint: false,
|
|
6063
|
-
openWorldHint: false
|
|
6064
|
-
}
|
|
6065
|
-
},
|
|
6066
|
-
withEnvelope(async (args) => {
|
|
6067
|
-
const parsed = facilitateSchema.safeParse(args);
|
|
6068
|
-
if (!parsed.success) {
|
|
6069
|
-
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
6070
|
-
return validationResult(`Invalid arguments: ${issues}`);
|
|
6071
|
-
}
|
|
6072
|
-
const { action } = parsed.data;
|
|
6073
|
-
return runWithToolContext({ tool: "facilitate", action }, async () => {
|
|
6074
|
-
switch (action) {
|
|
6075
|
-
case "start":
|
|
6076
|
-
return handleStart2(parsed.data);
|
|
6077
|
-
case "respond":
|
|
6078
|
-
return handleRespond(parsed.data);
|
|
6079
|
-
case "score":
|
|
6080
|
-
return handleScore(parsed.data);
|
|
6081
|
-
case "resume":
|
|
6082
|
-
return handleResume(parsed.data);
|
|
6083
|
-
case "commit-constellation":
|
|
6084
|
-
return handleCommitConstellation(parsed.data);
|
|
6085
|
-
default:
|
|
6086
|
-
return validationResult(`Unknown action '${action}'. Valid: ${FACILITATE_ACTIONS.join(", ")}.`);
|
|
6087
|
-
}
|
|
6088
|
-
});
|
|
6089
|
-
})
|
|
6090
|
-
);
|
|
6091
|
-
}
|
|
6092
5085
|
function buildStudioUrl(workspaceSlug, entryId) {
|
|
6093
5086
|
const appUrl = process.env.PRODUCTBRAIN_APP_URL ?? "https://productbrain.io";
|
|
6094
5087
|
return `${appUrl}/${workspaceSlug}/studio/entries/${entryId}`;
|
|
6095
5088
|
}
|
|
6096
|
-
function buildDirective(scorecard, phase, betEntryId, captureReady, activeDimension, nextDimension) {
|
|
6097
|
-
const b = betEntryId;
|
|
6098
|
-
if (scorecard.problem_clarity >= 6 && scorecard.appetite === 0) {
|
|
6099
|
-
return [
|
|
6100
|
-
`Present this to the user exactly as formatted:`,
|
|
6101
|
-
``,
|
|
6102
|
-
`**Appetite** \u2014 How much time is this idea worth? (Not how long it takes \u2014 how much you're willing to invest.)`,
|
|
6103
|
-
`1. **Small Batch** \u2014 1-2 weeks, focused scope`,
|
|
6104
|
-
`2. **Big Batch** \u2014 6 weeks, full treatment`,
|
|
6105
|
-
``,
|
|
6106
|
-
`Wait for their answer. Then call \`facilitate action=respond betEntryId="${b}" dimension="appetite"\` with their choice.`
|
|
6107
|
-
].join("\n");
|
|
6108
|
-
}
|
|
6109
|
-
if (scorecard.problem_clarity >= 6 && scorecard.appetite >= 6 && scorecard.elements === 0) {
|
|
6110
|
-
return [
|
|
6111
|
-
`Synthesize the problem and appetite back to the user in 2-3 lines, then present:`,
|
|
6112
|
-
``,
|
|
6113
|
-
`**Frame Go** \u2014 The problem is framed and appetite is set.`,
|
|
6114
|
-
`1. **Go** \u2014 move to solution elements`,
|
|
6115
|
-
`2. **Refine** \u2014 something's not right, let's adjust`,
|
|
6116
|
-
``,
|
|
6117
|
-
`If Go: **investigate first** \u2014 use the investigationBrief in the structured response to search the codebase and Chain.`,
|
|
6118
|
-
`Spawn sub-agents to explore the codebase for affected modules, existing architecture, and related patterns.`,
|
|
6119
|
-
`Then propose 3-5 solution elements grounded in what you found.`,
|
|
6120
|
-
`Present proposals for user reaction: "Based on the codebase, here are the elements I'd suggest. Which feel right?"`,
|
|
6121
|
-
`For each confirmed element, call \`facilitate action=respond betEntryId="${b}" dimension="elements" source="user_reaction"\` with the user's reaction.`,
|
|
6122
|
-
`If Refine: ask what needs adjusting, then call respond with dimension="problem_clarity" or "appetite".`
|
|
6123
|
-
].join("\n");
|
|
6124
|
-
}
|
|
6125
|
-
if (phase === "derisking" && scorecard.elements >= 6 && scorecard.risks >= 4 && scorecard.boundaries >= 4) {
|
|
6126
|
-
return [
|
|
6127
|
-
`Synthesize the shape so far in 3-4 lines, then present:`,
|
|
6128
|
-
``,
|
|
6129
|
-
`**Shape Go** \u2014 Elements defined, risks covered.`,
|
|
6130
|
-
`1. **Go** \u2014 validate and capture the pitch`,
|
|
6131
|
-
`2. **Refine** \u2014 there's more to cover`,
|
|
6132
|
-
``,
|
|
6133
|
-
`If Go: call \`facilitate action=score betEntryId="${b}"\` and present the full scorecard.`,
|
|
6134
|
-
`If Refine: ask what needs work, continue with respond.`
|
|
6135
|
-
].join("\n");
|
|
6136
|
-
}
|
|
6137
|
-
if (captureReady) {
|
|
6138
|
-
return [
|
|
6139
|
-
`Present the full scorecard, then:`,
|
|
6140
|
-
``,
|
|
6141
|
-
`**Ready to commit?**`,
|
|
6142
|
-
`1. **Commit** \u2014 publish to the Chain as source of truth`,
|
|
6143
|
-
`2. **Review** \u2014 show the full pitch first`,
|
|
6144
|
-
`3. **Keep as draft** \u2014 not ready yet`,
|
|
6145
|
-
``,
|
|
6146
|
-
`If Commit: call \`facilitate action=commit-constellation betEntryId="${b}"\` to publish the bet and all linked entries in one call.`,
|
|
6147
|
-
`If Review: call \`facilitate action=score betEntryId="${b}"\` for the detailed view.`,
|
|
6148
|
-
`If Draft: acknowledge and end the session.`
|
|
6149
|
-
].join("\n");
|
|
6150
|
-
}
|
|
6151
|
-
if (nextDimension && nextDimension !== activeDimension) {
|
|
6152
|
-
const nextLabel = DIMENSION_LABELS[nextDimension];
|
|
6153
|
-
const investigationPhases = ["elements", "architecture", "risks", "boundaries"];
|
|
6154
|
-
const shouldInvestigate = investigationPhases.includes(nextDimension);
|
|
6155
|
-
if (shouldInvestigate) {
|
|
6156
|
-
return [
|
|
6157
|
-
`Briefly synthesize what was just covered (2 lines max), then move to **${nextLabel}**.`,
|
|
6158
|
-
`**Investigate first:** Use the investigationBrief to search the codebase and Chain before asking the user.`,
|
|
6159
|
-
`Spawn sub-agents to gather evidence, then propose findings for user reaction.`,
|
|
6160
|
-
`After the user reacts, call \`facilitate action=respond betEntryId="${b}" dimension="${nextDimension}" source="user_reaction"\` with their reaction.`,
|
|
6161
|
-
`If the user provides their own input instead, call with source="user".`
|
|
6162
|
-
].join("\n");
|
|
6163
|
-
}
|
|
6164
|
-
return [
|
|
6165
|
-
`Briefly synthesize what was just covered (2 lines max), then move to **${nextLabel}**.`,
|
|
6166
|
-
`Ask ONE open question about ${nextLabel.toLowerCase()}. Keep it to 3 lines.`,
|
|
6167
|
-
`After the user answers, call \`facilitate action=respond betEntryId="${b}" dimension="${nextDimension}"\` with their input.`
|
|
6168
|
-
].join("\n");
|
|
6169
|
-
}
|
|
6170
|
-
return null;
|
|
6171
|
-
}
|
|
6172
|
-
function emptyScorecard() {
|
|
6173
|
-
return { problem_clarity: 0, appetite: 0, elements: 0, architecture: 0, risks: 0, boundaries: 0, done_when: 0 };
|
|
6174
|
-
}
|
|
6175
|
-
function detectSmallBatch(appetiteText) {
|
|
6176
|
-
if (!appetiteText) return false;
|
|
6177
|
-
return /small\s*batch|1[-–]2\s*week|2[-–]week|one.?week|two.?week/i.test(appetiteText);
|
|
6178
|
-
}
|
|
6179
|
-
function buildCachedScoringContext(betData, constellation) {
|
|
6180
|
-
const cachedOverlapIds = typeof betData._overlapIds === "string" ? betData._overlapIds.split(",").filter((id) => id && id !== "_checked") : [];
|
|
6181
|
-
const cachedGovCount = typeof betData._governanceCount === "number" ? betData._governanceCount : 0;
|
|
6182
|
-
const syntheticChainSurfaced = Array.from({ length: cachedGovCount }, () => ({ collection: "principles" }));
|
|
6183
|
-
return buildScoringContext("", betData, constellation, cachedOverlapIds, syntheticChainSurfaced);
|
|
6184
|
-
}
|
|
6185
|
-
function extractContentEvidence(ctx) {
|
|
6186
|
-
return {
|
|
6187
|
-
elementCount: ctx.elementCount,
|
|
6188
|
-
riskCount: ctx.riskCount,
|
|
6189
|
-
noGoCount: ctx.noGoCount,
|
|
6190
|
-
hasArchitectureText: ctx.hasArchitectureText
|
|
6191
|
-
};
|
|
6192
|
-
}
|
|
6193
|
-
function emptyResponse(betEntryId, studioUrl) {
|
|
6194
|
-
return {
|
|
6195
|
-
version: 2,
|
|
6196
|
-
phase: "context",
|
|
6197
|
-
phaseLabel: PHASE_LABELS.context,
|
|
6198
|
-
scorecard: emptyScorecard(),
|
|
6199
|
-
overlap: [],
|
|
6200
|
-
alignment: [],
|
|
6201
|
-
chainSurfaced: [],
|
|
6202
|
-
suggestedCaptures: [],
|
|
6203
|
-
sessionDrafts: [],
|
|
6204
|
-
coaching: {
|
|
6205
|
-
observation: "New shaping session started",
|
|
6206
|
-
missing: ["Problem statement", "Appetite", "Solution elements", "Architecture", "Risks", "Boundaries", "Done-when criteria"],
|
|
6207
|
-
pushback: "",
|
|
6208
|
-
suggestedQuestion: "Describe the problem you want to solve. Who's affected? What's the workaround today?",
|
|
6209
|
-
captureReady: false,
|
|
6210
|
-
nextDimension: "problem_clarity"
|
|
6211
|
-
},
|
|
6212
|
-
captured: {
|
|
6213
|
-
betEntryId,
|
|
6214
|
-
studioUrl,
|
|
6215
|
-
entriesCreated: [],
|
|
6216
|
-
relationsCreated: 0
|
|
6217
|
-
},
|
|
6218
|
-
captureErrors: [],
|
|
6219
|
-
navigation: {
|
|
6220
|
-
completedDimensions: [],
|
|
6221
|
-
activeDimension: "problem_clarity",
|
|
6222
|
-
suggestedOrder: [...SUGGESTED_ORDER]
|
|
6223
|
-
}
|
|
6224
|
-
};
|
|
6225
|
-
}
|
|
6226
5089
|
async function loadBetEntry(entryId) {
|
|
6227
5090
|
try {
|
|
6228
5091
|
return await mcpQuery("chain.getEntry", { entryId });
|
|
@@ -6288,801 +5151,39 @@ async function loadSessionDrafts(betEntryId, betInternalId) {
|
|
|
6288
5151
|
return [];
|
|
6289
5152
|
}
|
|
6290
5153
|
}
|
|
6291
|
-
function
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
str("solution"),
|
|
6304
|
-
userInput
|
|
6305
|
-
];
|
|
6306
|
-
const noGoText = str("noGos");
|
|
6307
|
-
const noGoCount = (noGoText.match(/^- \*\*/gm) ?? []).length;
|
|
6308
|
-
const elementsText = str("elements");
|
|
6309
|
-
const elementHeaderCount = (elementsText.match(/###\s*Element\s*\d/gi) ?? []).length;
|
|
6310
|
-
const governanceCollections = /* @__PURE__ */ new Set(["principles", "standards", "business-rules"]);
|
|
6311
|
-
const governanceCount = chainResults.filter((e) => governanceCollections.has(e.collection)).length;
|
|
6312
|
-
const hasArchitectureText = str("architecture").length > 20;
|
|
6313
|
-
const join2 = (...parts) => parts.filter(Boolean).join("\n\n");
|
|
6314
|
-
const inputFor = (dim) => activeDimension === dim ? userInput : "";
|
|
6315
|
-
const dimensionTexts = {
|
|
6316
|
-
problem_clarity: join2(str("problem"), inputFor("problem_clarity")),
|
|
6317
|
-
appetite: join2(str("appetite"), inputFor("appetite")),
|
|
6318
|
-
elements: join2(str("elements"), str("solution"), inputFor("elements")),
|
|
6319
|
-
architecture: join2(str("architecture"), inputFor("architecture")),
|
|
6320
|
-
risks: join2(str("rabbitHoles"), inputFor("risks")),
|
|
6321
|
-
boundaries: join2(str("noGos"), inputFor("boundaries")),
|
|
6322
|
-
done_when: join2(str("done_when"), str("doneWhen"), inputFor("done_when"))
|
|
6323
|
-
};
|
|
6324
|
-
return {
|
|
6325
|
-
userInput,
|
|
6326
|
-
accumulatedText: accParts.filter(Boolean).join("\n\n"),
|
|
6327
|
-
dimensionTexts,
|
|
6328
|
-
existingEntryIds: overlapIds,
|
|
6329
|
-
elementCount: Math.max(constellation.elementCount, elementHeaderCount),
|
|
6330
|
-
riskCount: constellation.riskCount,
|
|
6331
|
-
noGoCount,
|
|
6332
|
-
governanceCount,
|
|
6333
|
-
decisionCount: constellation.decisionCount,
|
|
6334
|
-
hasArchitectureText,
|
|
6335
|
-
source
|
|
6336
|
-
};
|
|
6337
|
-
}
|
|
6338
|
-
async function searchChain(text, opts = {}) {
|
|
6339
|
-
const { maxResults = 8, excludeIds = [] } = opts;
|
|
6340
|
-
if (text.length < 10) return [];
|
|
6341
|
-
const query = text.slice(0, 300);
|
|
6342
|
-
try {
|
|
6343
|
-
const results = await mcpQuery(
|
|
6344
|
-
"chain.searchEntries",
|
|
6345
|
-
{ query }
|
|
6346
|
-
);
|
|
6347
|
-
const excluded = new Set(excludeIds);
|
|
6348
|
-
return (results ?? []).filter((e) => e.entryId && !excluded.has(e.entryId)).slice(0, maxResults).map((e) => ({
|
|
6349
|
-
entryId: e.entryId,
|
|
6350
|
-
name: e.name,
|
|
6351
|
-
collection: e.collectionSlug ?? "unknown"
|
|
6352
|
-
}));
|
|
6353
|
-
} catch {
|
|
6354
|
-
return [];
|
|
6355
|
-
}
|
|
6356
|
-
}
|
|
6357
|
-
async function findAndLinkExisting(name, collectionSlug, betEntryId, relationType, agentSessionId) {
|
|
6358
|
-
try {
|
|
6359
|
-
const results = await mcpQuery(
|
|
6360
|
-
"chain.searchEntries",
|
|
6361
|
-
{ query: name }
|
|
6362
|
-
);
|
|
6363
|
-
const match = (results ?? []).find(
|
|
6364
|
-
(r) => r.entryId && r.name.toLowerCase() === name.toLowerCase() && (r.collectionSlug === collectionSlug || !r.collectionSlug)
|
|
6365
|
-
);
|
|
6366
|
-
if (!match?.entryId) return null;
|
|
6367
|
-
try {
|
|
6368
|
-
await mcpMutation("chain.createEntryRelation", {
|
|
6369
|
-
fromEntryId: match.entryId,
|
|
6370
|
-
toEntryId: betEntryId,
|
|
6371
|
-
type: relationType,
|
|
6372
|
-
sessionId: agentSessionId ?? void 0
|
|
6373
|
-
});
|
|
6374
|
-
return { entryId: match.entryId, linked: true };
|
|
6375
|
-
} catch {
|
|
6376
|
-
return { entryId: match.entryId, linked: false };
|
|
6377
|
-
}
|
|
6378
|
-
} catch {
|
|
6379
|
-
return null;
|
|
6380
|
-
}
|
|
6381
|
-
}
|
|
6382
|
-
async function handleStart2(args) {
|
|
6383
|
-
requireWriteAccess();
|
|
6384
|
-
const betName = args.betName ?? "Untitled Bet (Shaping)";
|
|
6385
|
-
let orientContext = "";
|
|
6386
|
-
try {
|
|
6387
|
-
const orient = await mcpQuery("chain.getOrientEntries", {});
|
|
6388
|
-
const sc = orient?.strategicContext;
|
|
6389
|
-
const parts = [];
|
|
6390
|
-
if (sc?.vision) parts.push(`Vision: ${sc.vision}`);
|
|
6391
|
-
if (sc?.purpose) parts.push(`Purpose: ${sc.purpose}`);
|
|
6392
|
-
if (sc?.activeBetCount) parts.push(`${sc.activeBetCount} active bet(s)`);
|
|
6393
|
-
if (sc?.activeTensionCount) parts.push(`${sc.activeTensionCount} open tension(s)`);
|
|
6394
|
-
if (orient?.activeBets?.length) {
|
|
6395
|
-
parts.push("Active bets: " + orient.activeBets.map((b) => `${b.entryId ?? "?"} ${b.name}`).join(", "));
|
|
6396
|
-
}
|
|
6397
|
-
orientContext = parts.join(". ");
|
|
6398
|
-
} catch {
|
|
6399
|
-
orientContext = "Could not load workspace context.";
|
|
6400
|
-
}
|
|
6401
|
-
const investigationBrief = buildInvestigationBrief("context", betName, "pending");
|
|
6402
|
-
const response = emptyResponse("pending", "");
|
|
6403
|
-
if (investigationBrief) {
|
|
6404
|
-
response.investigationBrief = investigationBrief;
|
|
6405
|
-
}
|
|
6406
|
-
const output = [
|
|
6407
|
-
`# Shaping Session Started`,
|
|
6408
|
-
"",
|
|
6409
|
-
`**Bet:** ${betName} (draft entry will be created when you describe the problem)`,
|
|
6410
|
-
`**Phase:** ${PHASE_LABELS.context}`,
|
|
6411
|
-
"",
|
|
6412
|
-
orientContext ? `**Workspace context:** ${orientContext}` : "",
|
|
6413
|
-
"",
|
|
6414
|
-
"No entry created yet \u2014 the bet will be saved to the Chain once you describe the problem.",
|
|
6415
|
-
"",
|
|
6416
|
-
"---",
|
|
6417
|
-
"## Agent Directive",
|
|
6418
|
-
"**Investigate first** \u2014 before asking the user, use the investigationBrief to search the codebase and Chain for context related to this bet.",
|
|
6419
|
-
"Spawn sub-agents: one `explore` agent to search the codebase, one `generalPurpose` agent to search the Chain.",
|
|
6420
|
-
"Synthesize findings into a 3-5 line context brief, then present to the user.",
|
|
6421
|
-
"After presenting context, ask: **What's not working well? Describe the problem \u2014 who's affected, and what's the workaround today?**",
|
|
6422
|
-
"Keep your message to 5 lines or fewer. Heavy context degrades the shaping experience.",
|
|
6423
|
-
`After the user responds, call \`facilitate action=respond betName="${betName}" dimension="problem_clarity"\` with their answer as userInput.`,
|
|
6424
|
-
`Note: pass betName (not betEntryId) \u2014 the entry will be created on the first respond call.`
|
|
6425
|
-
].filter(Boolean).join("\n");
|
|
6426
|
-
return {
|
|
6427
|
-
content: [{ type: "text", text: output }],
|
|
6428
|
-
structuredContent: success(
|
|
6429
|
-
`Shaping session started for "${betName}". Phase: context.`,
|
|
6430
|
-
response,
|
|
6431
|
-
[{ tool: "facilitate", description: "Describe the problem", parameters: { action: "respond", betName, dimension: "problem_clarity" } }]
|
|
6432
|
-
)
|
|
6433
|
-
};
|
|
6434
|
-
}
|
|
6435
|
-
async function processCaptures(opts) {
|
|
6436
|
-
const { captureItems, betEntryId, betDocId, betData } = opts;
|
|
6437
|
-
const captureErrors = [];
|
|
6438
|
-
const entriesCreated = [];
|
|
6439
|
-
let relationsCreated = 0;
|
|
6440
|
-
const capAgentId = getAgentSessionId();
|
|
6441
|
-
const CAPTURE_DEFAULTS = {
|
|
6442
|
-
features: { dataField: "description", relationType: "part_of" },
|
|
6443
|
-
tensions: { dataField: "description", relationType: "constrains" },
|
|
6444
|
-
decisions: { dataField: "rationale", relationType: "informs" }
|
|
6445
|
-
};
|
|
6446
|
-
const FALLBACK_DEFAULTS = { dataField: "description", relationType: "related_to" };
|
|
6447
|
-
let runningBetData = { ...betData };
|
|
6448
|
-
const runningLinks = () => ({ ...runningBetData.links ?? {} });
|
|
6449
|
-
for (const item of captureItems) {
|
|
6450
|
-
if (item.type === "noGo") {
|
|
6451
|
-
const curLinks = runningLinks();
|
|
6452
|
-
const updatedNoGos = appendNoGo(
|
|
6453
|
-
curLinks.noGos,
|
|
6454
|
-
{ title: item.name, explanation: item.description }
|
|
6455
|
-
);
|
|
6456
|
-
const newLinks = { ...curLinks, noGos: updatedNoGos };
|
|
6457
|
-
runningBetData.links = newLinks;
|
|
6458
|
-
try {
|
|
6459
|
-
await mcpMutation("chain.updateEntry", {
|
|
6460
|
-
entryId: betEntryId,
|
|
6461
|
-
data: { links: newLinks },
|
|
6462
|
-
changeNote: `Added no-go: ${item.name}`
|
|
6463
|
-
});
|
|
6464
|
-
await recordSessionActivity({ entryModified: betDocId });
|
|
6465
|
-
} catch (updErr) {
|
|
6466
|
-
captureErrors.push({
|
|
6467
|
-
operation: "update",
|
|
6468
|
-
detail: `noGos field: ${updErr instanceof Error ? updErr.message : String(updErr)}`
|
|
6469
|
-
});
|
|
5154
|
+
function registerFacilitateTools(server) {
|
|
5155
|
+
server.registerTool(
|
|
5156
|
+
"facilitate",
|
|
5157
|
+
{
|
|
5158
|
+
title: "Facilitate \u2014 Session Resume & Constellation Commit",
|
|
5159
|
+
description: "Session state loading and atomic constellation commit for shaped bets.\n\n- **resume**: Load session state (bet data, constellation, drafts) from an existing bet entry.\n- **commit-constellation**: Atomically commit a bet and all its linked draft entries (features, tensions, decisions) in one call. Validates strategy link and required fields before committing anything. Replaces sequential commit-entry calls.\n\nScoring, coaching, and the facilitation loop are handled by the agent-side shaping skill.",
|
|
5160
|
+
inputSchema: facilitateSchema,
|
|
5161
|
+
annotations: {
|
|
5162
|
+
readOnlyHint: false,
|
|
5163
|
+
destructiveHint: false,
|
|
5164
|
+
idempotentHint: false,
|
|
5165
|
+
openWorldHint: false
|
|
6470
5166
|
}
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
});
|
|
6478
|
-
if (!resolved) {
|
|
6479
|
-
captureErrors.push({
|
|
6480
|
-
operation: "classify",
|
|
6481
|
-
detail: `Could not resolve collection for ${item.type} "${item.name}" \u2014 skipped.`
|
|
6482
|
-
});
|
|
6483
|
-
continue;
|
|
6484
|
-
}
|
|
6485
|
-
const { dataField, relationType } = CAPTURE_DEFAULTS[resolved.collection] ?? FALLBACK_DEFAULTS;
|
|
6486
|
-
let capturedEntryId = null;
|
|
6487
|
-
try {
|
|
6488
|
-
const result = await mcpMutation(
|
|
6489
|
-
"chain.createEntry",
|
|
6490
|
-
{
|
|
6491
|
-
collectionSlug: resolved.collection,
|
|
6492
|
-
name: item.name,
|
|
6493
|
-
status: "draft",
|
|
6494
|
-
data: { [dataField]: item.description },
|
|
6495
|
-
createdBy: capAgentId ? `agent:${capAgentId}` : "facilitate",
|
|
6496
|
-
sessionId: capAgentId ?? void 0
|
|
6497
|
-
}
|
|
6498
|
-
);
|
|
6499
|
-
capturedEntryId = result.entryId;
|
|
6500
|
-
entriesCreated.push(result.entryId);
|
|
6501
|
-
} catch (createErr) {
|
|
6502
|
-
const msg = createErr instanceof Error ? createErr.message : String(createErr);
|
|
6503
|
-
if (msg.includes("Duplicate entry") || msg.includes("already exists")) {
|
|
6504
|
-
const fallback = await findAndLinkExisting(
|
|
6505
|
-
item.name,
|
|
6506
|
-
resolved.collection,
|
|
6507
|
-
betEntryId,
|
|
6508
|
-
relationType,
|
|
6509
|
-
capAgentId
|
|
6510
|
-
);
|
|
6511
|
-
if (fallback) {
|
|
6512
|
-
capturedEntryId = fallback.entryId;
|
|
6513
|
-
entriesCreated.push(fallback.entryId);
|
|
6514
|
-
if (fallback.linked) relationsCreated++;
|
|
6515
|
-
captureErrors.push({
|
|
6516
|
-
operation: "info",
|
|
6517
|
-
detail: `Linked existing ${resolved.collection} entry \`${fallback.entryId}\` instead of creating duplicate.`
|
|
6518
|
-
});
|
|
6519
|
-
} else {
|
|
6520
|
-
captureErrors.push({ operation: "capture", detail: `${item.type}: ${msg}` });
|
|
6521
|
-
}
|
|
6522
|
-
} else {
|
|
6523
|
-
captureErrors.push({ operation: "capture", detail: `${item.type}: ${msg}` });
|
|
5167
|
+
},
|
|
5168
|
+
withEnvelope(async (args) => {
|
|
5169
|
+
const parsed = facilitateSchema.safeParse(args);
|
|
5170
|
+
if (!parsed.success) {
|
|
5171
|
+
const issues = parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
5172
|
+
return validationResult(`Invalid arguments: ${issues}`);
|
|
6524
5173
|
}
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
5174
|
+
const { action } = parsed.data;
|
|
5175
|
+
return runWithToolContext({ tool: "facilitate", action }, async () => {
|
|
5176
|
+
switch (action) {
|
|
5177
|
+
case "resume":
|
|
5178
|
+
return handleResume(parsed.data);
|
|
5179
|
+
case "commit-constellation":
|
|
5180
|
+
return handleCommitConstellation(parsed.data);
|
|
5181
|
+
default:
|
|
5182
|
+
return validationResult(`Unknown action '${action}'. Valid: ${FACILITATE_ACTIONS.join(", ")}.`);
|
|
5183
|
+
}
|
|
6533
5184
|
});
|
|
6534
|
-
|
|
6535
|
-
} catch (relErr) {
|
|
6536
|
-
const msg = relErr instanceof Error ? relErr.message : String(relErr);
|
|
6537
|
-
if (!msg.includes("already exists") && !msg.includes("Duplicate")) {
|
|
6538
|
-
captureErrors.push({
|
|
6539
|
-
operation: "relation",
|
|
6540
|
-
detail: `${relationType} from ${capturedEntryId} to ${betEntryId}: ${msg}`
|
|
6541
|
-
});
|
|
6542
|
-
}
|
|
6543
|
-
}
|
|
6544
|
-
if (item.type === "element") {
|
|
6545
|
-
const curLinks = runningLinks();
|
|
6546
|
-
const updatedElements = appendElement(
|
|
6547
|
-
curLinks.elements,
|
|
6548
|
-
{ name: item.name, description: item.description, entryId: capturedEntryId }
|
|
6549
|
-
);
|
|
6550
|
-
const newLinks = { ...curLinks, elements: updatedElements };
|
|
6551
|
-
runningBetData.links = newLinks;
|
|
6552
|
-
try {
|
|
6553
|
-
await mcpMutation("chain.updateEntry", {
|
|
6554
|
-
entryId: betEntryId,
|
|
6555
|
-
data: { links: newLinks },
|
|
6556
|
-
changeNote: `Added element: ${item.name}`
|
|
6557
|
-
});
|
|
6558
|
-
await recordSessionActivity({ entryModified: betDocId });
|
|
6559
|
-
} catch (updErr) {
|
|
6560
|
-
captureErrors.push({
|
|
6561
|
-
operation: "update",
|
|
6562
|
-
detail: `elements field: ${updErr instanceof Error ? updErr.message : String(updErr)}`
|
|
6563
|
-
});
|
|
6564
|
-
}
|
|
6565
|
-
} else if (item.type === "risk") {
|
|
6566
|
-
const curLinks = runningLinks();
|
|
6567
|
-
const updatedRisks = appendRabbitHole(
|
|
6568
|
-
curLinks.rabbitHoles,
|
|
6569
|
-
{ name: item.name, description: item.description, theme: item.theme, entryId: capturedEntryId }
|
|
6570
|
-
);
|
|
6571
|
-
const newLinks = { ...curLinks, rabbitHoles: updatedRisks };
|
|
6572
|
-
runningBetData.links = newLinks;
|
|
6573
|
-
try {
|
|
6574
|
-
await mcpMutation("chain.updateEntry", {
|
|
6575
|
-
entryId: betEntryId,
|
|
6576
|
-
data: { links: newLinks },
|
|
6577
|
-
changeNote: `Added risk: ${item.name}`
|
|
6578
|
-
});
|
|
6579
|
-
await recordSessionActivity({ entryModified: betDocId });
|
|
6580
|
-
} catch (updErr) {
|
|
6581
|
-
captureErrors.push({
|
|
6582
|
-
operation: "update",
|
|
6583
|
-
detail: `rabbitHoles field: ${updErr instanceof Error ? updErr.message : String(updErr)}`
|
|
6584
|
-
});
|
|
6585
|
-
}
|
|
6586
|
-
}
|
|
6587
|
-
}
|
|
6588
|
-
return { entriesCreated, relationsCreated, captureErrors };
|
|
6589
|
-
}
|
|
6590
|
-
async function computeAndUpdateScores(opts) {
|
|
6591
|
-
const {
|
|
6592
|
-
betEntryId,
|
|
6593
|
-
betDocId,
|
|
6594
|
-
betData,
|
|
6595
|
-
betEntry,
|
|
6596
|
-
userInput,
|
|
6597
|
-
argDimension,
|
|
6598
|
-
source,
|
|
6599
|
-
captureItems,
|
|
6600
|
-
chainSurfaced,
|
|
6601
|
-
constellation
|
|
6602
|
-
} = opts;
|
|
6603
|
-
const captureErrors = [];
|
|
6604
|
-
const refreshedBet = await loadBetEntry(betEntryId);
|
|
6605
|
-
const refreshedData = refreshedBet?.data ?? betData;
|
|
6606
|
-
const refreshedConstellation = captureItems.length > 0 ? await loadConstellationState(betEntryId, refreshedBet?._id ?? betEntry._id) : constellation;
|
|
6607
|
-
const sessionDrafts = await loadSessionDrafts(betEntryId, refreshedBet?._id ?? betEntry._id);
|
|
6608
|
-
const constellationEntryIds = sessionDrafts.map((d) => d.entryId);
|
|
6609
|
-
const alreadyCheckedOverlap = typeof refreshedData._overlapIds === "string";
|
|
6610
|
-
let overlap = [];
|
|
6611
|
-
if (!alreadyCheckedOverlap && captureItems.length === 0) {
|
|
6612
|
-
const refreshedLinks = refreshedData.links ?? {};
|
|
6613
|
-
const problemText = refreshedLinks.problem ?? userInput;
|
|
6614
|
-
const overlapResults = await searchChain(problemText, { maxResults: 5, excludeIds: [betEntryId, ...constellationEntryIds] });
|
|
6615
|
-
overlap = overlapResults.map((r) => ({
|
|
6616
|
-
entryId: r.entryId,
|
|
6617
|
-
similarity: "text_match",
|
|
6618
|
-
summary: `${r.name} [${r.collection}]`
|
|
6619
|
-
}));
|
|
6620
|
-
}
|
|
6621
|
-
const overlapIds = overlap.map((o) => o.entryId);
|
|
6622
|
-
const refreshedLinksForAppetite = refreshedData.links ?? {};
|
|
6623
|
-
const appetiteText = refreshedLinksForAppetite.appetite ?? "";
|
|
6624
|
-
const isSmallBatch = detectSmallBatch(
|
|
6625
|
-
argDimension === "appetite" ? userInput : appetiteText
|
|
6626
|
-
);
|
|
6627
|
-
let activeDimension;
|
|
6628
|
-
if (argDimension && DIMENSIONS.includes(argDimension)) {
|
|
6629
|
-
activeDimension = argDimension;
|
|
6630
|
-
} else {
|
|
6631
|
-
const prelim = buildScoringContext(userInput, refreshedData, refreshedConstellation, overlapIds, chainSurfaced, void 0, source);
|
|
6632
|
-
activeDimension = inferActiveDimension(buildScorecard(prelim), isSmallBatch);
|
|
6633
|
-
}
|
|
6634
|
-
const scoringCtx = buildScoringContext(
|
|
6635
|
-
userInput,
|
|
6636
|
-
refreshedData,
|
|
6637
|
-
refreshedConstellation,
|
|
6638
|
-
overlapIds,
|
|
6639
|
-
chainSurfaced,
|
|
6640
|
-
activeDimension,
|
|
6641
|
-
source
|
|
5185
|
+
})
|
|
6642
5186
|
);
|
|
6643
|
-
const dimensionResult = scoreDimension(activeDimension, scoringCtx);
|
|
6644
|
-
const { scorecard, criteria: scorecardCriteria } = buildDetailedScorecard(scoringCtx, { isSmallBatch });
|
|
6645
|
-
const nextDimension = inferActiveDimension(scorecard, isSmallBatch);
|
|
6646
|
-
const phase = inferPhase(scorecard, isSmallBatch, extractContentEvidence(scoringCtx));
|
|
6647
|
-
try {
|
|
6648
|
-
const fieldUpdates = {};
|
|
6649
|
-
const linksUpdates = {};
|
|
6650
|
-
const currentLinks = refreshedData.links ?? {};
|
|
6651
|
-
const persist = (dim, field, minLen) => {
|
|
6652
|
-
if (activeDimension === dim && !currentLinks[field] && userInput.length > minLen) {
|
|
6653
|
-
linksUpdates[field] = userInput;
|
|
6654
|
-
}
|
|
6655
|
-
};
|
|
6656
|
-
persist("problem_clarity", "problem", 50);
|
|
6657
|
-
persist("appetite", "appetite", 20);
|
|
6658
|
-
persist("architecture", "architecture", 50);
|
|
6659
|
-
persist("done_when", "doneWhen", 30);
|
|
6660
|
-
if (Object.keys(linksUpdates).length > 0) {
|
|
6661
|
-
fieldUpdates.links = { ...currentLinks, ...linksUpdates };
|
|
6662
|
-
}
|
|
6663
|
-
if (!refreshedData._overlapIds) {
|
|
6664
|
-
fieldUpdates._overlapIds = overlapIds.length > 0 ? overlapIds.join(",") : "_checked";
|
|
6665
|
-
}
|
|
6666
|
-
const govCount = chainSurfaced.filter(
|
|
6667
|
-
(e) => ["principles", "standards", "business-rules"].includes(e.collection)
|
|
6668
|
-
).length;
|
|
6669
|
-
if (govCount > 0) {
|
|
6670
|
-
fieldUpdates._governanceCount = govCount;
|
|
6671
|
-
}
|
|
6672
|
-
if (Object.keys(fieldUpdates).length > 0) {
|
|
6673
|
-
await mcpMutation("chain.updateEntry", {
|
|
6674
|
-
entryId: betEntryId,
|
|
6675
|
-
data: fieldUpdates,
|
|
6676
|
-
changeNote: `Updated ${Object.keys(fieldUpdates).join(", ")} from shaping session`
|
|
6677
|
-
});
|
|
6678
|
-
await recordSessionActivity({ entryModified: refreshedBet?._id ?? betEntry._id });
|
|
6679
|
-
}
|
|
6680
|
-
} catch (updErr) {
|
|
6681
|
-
captureErrors.push({
|
|
6682
|
-
operation: "update",
|
|
6683
|
-
detail: `bet fields: ${updErr instanceof Error ? updErr.message : String(updErr)}`
|
|
6684
|
-
});
|
|
6685
|
-
}
|
|
6686
|
-
const alignment = [];
|
|
6687
|
-
const governanceCollections = /* @__PURE__ */ new Set(["principles", "standards", "business-rules", "strategy", "chains"]);
|
|
6688
|
-
for (const result of chainSurfaced) {
|
|
6689
|
-
if (governanceCollections.has(result.collection)) {
|
|
6690
|
-
alignment.push({ entryId: result.entryId, relationship: `governs [${result.collection}]` });
|
|
6691
|
-
}
|
|
6692
|
-
}
|
|
6693
|
-
const { ready: captureReady, completed } = isCaptureReady(scorecard, isSmallBatch);
|
|
6694
|
-
const observation = dimensionResult.satisfied.length > 0 ? `${DIMENSION_LABELS[activeDimension]}: ${dimensionResult.satisfied.join("; ")}` : `${DIMENSION_LABELS[activeDimension]}: needs more detail`;
|
|
6695
|
-
const pushback = captureItems.length === 0 && overlap.length > 0 ? `${overlap[0].summary} already exists on the Chain. How does your bet differ?` : dimensionResult.missing.length > 0 ? dimensionResult.missing[0] : "";
|
|
6696
|
-
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?`;
|
|
6697
|
-
let buildContract;
|
|
6698
|
-
if (captureReady) {
|
|
6699
|
-
const contractCtx = {
|
|
6700
|
-
betEntryId,
|
|
6701
|
-
governanceEntries: chainSurfaced.filter((e) => ["principles", "standards", "business-rules"].includes(e.collection)).map((e) => ({ entryId: e.entryId, name: e.name, collection: e.collection })),
|
|
6702
|
-
relatedDecisions: chainSurfaced.filter((e) => e.collection === "decisions").map((e) => ({ entryId: e.entryId, name: e.name })),
|
|
6703
|
-
relatedTensions: chainSurfaced.filter((e) => e.collection === "tensions").map((e) => ({ entryId: e.entryId, name: e.name }))
|
|
6704
|
-
};
|
|
6705
|
-
buildContract = generateBuildContract(contractCtx);
|
|
6706
|
-
const currentBCLinks = refreshedData.links ?? {};
|
|
6707
|
-
if (currentBCLinks.buildContract !== buildContract) {
|
|
6708
|
-
try {
|
|
6709
|
-
const bcLinks = { ...refreshedData.links ?? {}, buildContract };
|
|
6710
|
-
await mcpMutation("chain.updateEntry", {
|
|
6711
|
-
entryId: betEntryId,
|
|
6712
|
-
data: { links: bcLinks },
|
|
6713
|
-
changeNote: "Updated build contract from shaping session"
|
|
6714
|
-
});
|
|
6715
|
-
await recordSessionActivity({ entryModified: refreshedBet?._id ?? betEntry._id });
|
|
6716
|
-
} catch (updErr) {
|
|
6717
|
-
captureErrors.push({
|
|
6718
|
-
operation: "update",
|
|
6719
|
-
detail: `buildContract field: ${updErr instanceof Error ? updErr.message : String(updErr)}`
|
|
6720
|
-
});
|
|
6721
|
-
}
|
|
6722
|
-
}
|
|
6723
|
-
}
|
|
6724
|
-
let commitBlockers;
|
|
6725
|
-
if (captureReady) {
|
|
6726
|
-
const hasStrategyLink = await hasStrategyLinkForEntry(
|
|
6727
|
-
refreshedBet?._id ?? betEntry._id,
|
|
6728
|
-
refreshedConstellation.relations
|
|
6729
|
-
);
|
|
6730
|
-
commitBlockers = computeCommitBlockers({
|
|
6731
|
-
betEntryId,
|
|
6732
|
-
betDocId: refreshedBet?._id ?? betEntry._id,
|
|
6733
|
-
relations: refreshedConstellation.relations,
|
|
6734
|
-
hasStrategyLink,
|
|
6735
|
-
betData: refreshedData,
|
|
6736
|
-
sessionDrafts
|
|
6737
|
-
});
|
|
6738
|
-
if (commitBlockers.length === 0) commitBlockers = void 0;
|
|
6739
|
-
}
|
|
6740
|
-
return {
|
|
6741
|
-
scorecard,
|
|
6742
|
-
scorecardCriteria,
|
|
6743
|
-
phase,
|
|
6744
|
-
activeDimension,
|
|
6745
|
-
nextDimension,
|
|
6746
|
-
dimensionResult,
|
|
6747
|
-
overlap,
|
|
6748
|
-
sessionDrafts,
|
|
6749
|
-
alignment,
|
|
6750
|
-
coaching: { observation, pushback, suggestedQuestion, captureReady },
|
|
6751
|
-
completed,
|
|
6752
|
-
isSmallBatch,
|
|
6753
|
-
refreshedData,
|
|
6754
|
-
scoringCtx,
|
|
6755
|
-
captureErrors,
|
|
6756
|
-
buildContract,
|
|
6757
|
-
commitBlockers
|
|
6758
|
-
};
|
|
6759
|
-
}
|
|
6760
|
-
function assembleResponse(opts) {
|
|
6761
|
-
const {
|
|
6762
|
-
betEntryId,
|
|
6763
|
-
betName,
|
|
6764
|
-
workspaceSlug,
|
|
6765
|
-
scorecard,
|
|
6766
|
-
scorecardCriteria,
|
|
6767
|
-
phase,
|
|
6768
|
-
activeDimension,
|
|
6769
|
-
nextDimension,
|
|
6770
|
-
dimensionResult,
|
|
6771
|
-
coaching,
|
|
6772
|
-
overlap,
|
|
6773
|
-
sessionDrafts,
|
|
6774
|
-
alignment,
|
|
6775
|
-
chainSurfaced,
|
|
6776
|
-
scoringCtx,
|
|
6777
|
-
isSmallBatch,
|
|
6778
|
-
completed,
|
|
6779
|
-
refreshedData,
|
|
6780
|
-
buildContract,
|
|
6781
|
-
commitBlockers,
|
|
6782
|
-
entriesCreated,
|
|
6783
|
-
relationsCreated,
|
|
6784
|
-
captureErrors,
|
|
6785
|
-
captureItems
|
|
6786
|
-
} = opts;
|
|
6787
|
-
const studioUrl = buildStudioUrl(workspaceSlug, betEntryId);
|
|
6788
|
-
const suggested = suggestCaptures(scoringCtx, activeDimension);
|
|
6789
|
-
const assembleLinks = refreshedData.links ?? {};
|
|
6790
|
-
const betProblem = assembleLinks.problem ?? "";
|
|
6791
|
-
const responseBetName = refreshedData.description ?? betName;
|
|
6792
|
-
const elementNames = (assembleLinks.elements ?? "").match(/###\s*Element\s*\d+:\s*(.+)/gi)?.map((h) => h.replace(/###\s*Element\s*\d+:\s*/i, "").trim()) ?? [];
|
|
6793
|
-
const investigationBrief = buildInvestigationBrief(phase, responseBetName, betEntryId, betProblem, elementNames) ?? void 0;
|
|
6794
|
-
const response = {
|
|
6795
|
-
version: 2,
|
|
6796
|
-
phase,
|
|
6797
|
-
phaseLabel: PHASE_LABELS[phase],
|
|
6798
|
-
scorecard,
|
|
6799
|
-
overlap,
|
|
6800
|
-
alignment,
|
|
6801
|
-
chainSurfaced: chainSurfaced.map((r) => ({ entryId: r.entryId, relevance: r.name, collection: r.collection })),
|
|
6802
|
-
suggestedCaptures: suggested,
|
|
6803
|
-
sessionDrafts,
|
|
6804
|
-
coaching: {
|
|
6805
|
-
observation: coaching.observation,
|
|
6806
|
-
missing: dimensionResult.missing,
|
|
6807
|
-
pushback: coaching.pushback,
|
|
6808
|
-
suggestedQuestion: coaching.suggestedQuestion,
|
|
6809
|
-
captureReady: coaching.captureReady,
|
|
6810
|
-
nextDimension
|
|
6811
|
-
},
|
|
6812
|
-
captured: {
|
|
6813
|
-
betEntryId,
|
|
6814
|
-
studioUrl,
|
|
6815
|
-
entriesCreated,
|
|
6816
|
-
relationsCreated
|
|
6817
|
-
},
|
|
6818
|
-
captureErrors,
|
|
6819
|
-
navigation: {
|
|
6820
|
-
completedDimensions: completed,
|
|
6821
|
-
activeDimension,
|
|
6822
|
-
suggestedOrder: activeDimensions(isSmallBatch)
|
|
6823
|
-
},
|
|
6824
|
-
buildContract,
|
|
6825
|
-
directive: buildDirective(scorecard, phase, betEntryId, coaching.captureReady, activeDimension, nextDimension) || void 0,
|
|
6826
|
-
investigationBrief,
|
|
6827
|
-
commitBlockers,
|
|
6828
|
-
scorecardCriteria
|
|
6829
|
-
};
|
|
6830
|
-
const STRUCTURED_DIMS = {
|
|
6831
|
-
elements: "element",
|
|
6832
|
-
risks: "risk",
|
|
6833
|
-
boundaries: "noGo"
|
|
6834
|
-
};
|
|
6835
|
-
const expectedCaptureType = STRUCTURED_DIMS[activeDimension];
|
|
6836
|
-
const dataLossWarning = expectedCaptureType && captureItems.length === 0 ? `**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.` : "";
|
|
6837
|
-
const summaryParts = [
|
|
6838
|
-
`# ${PHASE_LABELS[phase]} \u2014 ${DIMENSION_LABELS[activeDimension]}`,
|
|
6839
|
-
"",
|
|
6840
|
-
dataLossWarning,
|
|
6841
|
-
`**Score:** ${DIMENSION_LABELS[activeDimension]}: ${scorecard[activeDimension]}/10 \u2014 ${formatCriteriaLine(dimensionResult.criteria)}`,
|
|
6842
|
-
`**Phase:** ${PHASE_LABELS[phase]}`,
|
|
6843
|
-
coaching.observation,
|
|
6844
|
-
coaching.pushback ? `**Pushback:** ${coaching.pushback}` : "",
|
|
6845
|
-
`**Next:** ${coaching.suggestedQuestion}`,
|
|
6846
|
-
coaching.captureReady ? "\n**Capture ready** \u2014 the bet has enough shape to finalize." : "",
|
|
6847
|
-
commitBlockers?.length ? `
|
|
6848
|
-
\u26A0 **Commit blockers (${commitBlockers.length}):** ${commitBlockers.map((b) => `${b.blocker} \u2192 \`${b.fix}\``).join("; ")}` : "",
|
|
6849
|
-
entriesCreated.length > 0 ? `**Captured:** ${entriesCreated.join(", ")}` : "",
|
|
6850
|
-
suggested.length > 0 ? `**Suggest capturing:** ${suggested.map((s) => `${s.type}: "${s.name}"`).join("; ")}` : "",
|
|
6851
|
-
sessionDrafts.length > 0 ? `**Session drafts (${sessionDrafts.length}):** ${sessionDrafts.map((d) => `\`${d.entryId}\` ${d.name}`).join(", ")}` : "",
|
|
6852
|
-
captureErrors.length > 0 ? `**Warnings:** ${captureErrors.map((e) => e.detail).join("; ")}` : ""
|
|
6853
|
-
];
|
|
6854
|
-
const summaryText = `${PHASE_LABELS[phase]} \u2014 ${DIMENSION_LABELS[activeDimension]}: ${scorecard[activeDimension]}/10.${coaching.captureReady ? " Capture ready." : ""}`;
|
|
6855
|
-
return {
|
|
6856
|
-
content: [{ type: "text", text: summaryParts.filter(Boolean).join("\n") }],
|
|
6857
|
-
structuredContent: success(
|
|
6858
|
-
summaryText,
|
|
6859
|
-
response,
|
|
6860
|
-
[{ tool: "facilitate", description: "Continue shaping", parameters: { action: "respond", betEntryId, dimension: nextDimension } }]
|
|
6861
|
-
)
|
|
6862
|
-
};
|
|
6863
|
-
}
|
|
6864
|
-
async function handleRespond(args) {
|
|
6865
|
-
requireWriteAccess();
|
|
6866
|
-
const { userInput, betEntryId: argBetId, dimension: argDimension, capture: rawCapture, source: argSource, betName: argBetName } = args;
|
|
6867
|
-
const source = argSource ?? "user";
|
|
6868
|
-
const captureItems = rawCapture ? Array.isArray(rawCapture) ? rawCapture : [rawCapture] : [];
|
|
6869
|
-
if (!userInput) {
|
|
6870
|
-
return validationResult("`userInput` is required for respond action.");
|
|
6871
|
-
}
|
|
6872
|
-
let betId = argBetId;
|
|
6873
|
-
if (!betId) {
|
|
6874
|
-
const betName = argBetName ?? "Untitled Bet (Shaping)";
|
|
6875
|
-
const agentId = getAgentSessionId();
|
|
6876
|
-
try {
|
|
6877
|
-
const result = await mcpMutation(
|
|
6878
|
-
"chain.createEntry",
|
|
6879
|
-
{
|
|
6880
|
-
collectionSlug: "chains",
|
|
6881
|
-
name: betName,
|
|
6882
|
-
status: "draft",
|
|
6883
|
-
data: {
|
|
6884
|
-
chainTypeId: "bet",
|
|
6885
|
-
description: `Shaping session for: ${betName}`,
|
|
6886
|
-
status: "shaping",
|
|
6887
|
-
shapingSessionActive: true,
|
|
6888
|
-
links: {
|
|
6889
|
-
problem: userInput,
|
|
6890
|
-
appetite: "",
|
|
6891
|
-
elements: "",
|
|
6892
|
-
rabbitHoles: "",
|
|
6893
|
-
noGos: "",
|
|
6894
|
-
architecture: "",
|
|
6895
|
-
buildContract: ""
|
|
6896
|
-
}
|
|
6897
|
-
},
|
|
6898
|
-
createdBy: agentId ? `agent:${agentId}` : "facilitate",
|
|
6899
|
-
sessionId: agentId ?? void 0
|
|
6900
|
-
}
|
|
6901
|
-
);
|
|
6902
|
-
betId = result.entryId;
|
|
6903
|
-
} catch (err) {
|
|
6904
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
6905
|
-
return {
|
|
6906
|
-
content: [{ type: "text", text: `Failed to create bet entry: ${msg}` }],
|
|
6907
|
-
structuredContent: failure("BACKEND_ERROR", `Failed to create bet entry: ${msg}`, "Check workspace access and try again.")
|
|
6908
|
-
};
|
|
6909
|
-
}
|
|
6910
|
-
}
|
|
6911
|
-
const [betEntry, constellation, chainSurfaced] = await Promise.all([
|
|
6912
|
-
loadBetEntry(betId),
|
|
6913
|
-
loadConstellationState(betId),
|
|
6914
|
-
searchChain(userInput, { maxResults: 8 })
|
|
6915
|
-
]);
|
|
6916
|
-
if (!betEntry) {
|
|
6917
|
-
return {
|
|
6918
|
-
content: [{ type: "text", text: `Bet \`${betId}\` not found. Use start to create a session.` }],
|
|
6919
|
-
structuredContent: failure(
|
|
6920
|
-
"NOT_FOUND",
|
|
6921
|
-
`Bet '${betId}' not found.`,
|
|
6922
|
-
"Use facilitate action=start to begin a new session.",
|
|
6923
|
-
[{ tool: "facilitate", description: "Start session", parameters: { action: "start" } }]
|
|
6924
|
-
)
|
|
6925
|
-
};
|
|
6926
|
-
}
|
|
6927
|
-
const betData = betEntry.data ?? {};
|
|
6928
|
-
let entriesCreated = [];
|
|
6929
|
-
let relationsCreated = 0;
|
|
6930
|
-
const captureErrors = [];
|
|
6931
|
-
if (captureItems.length > 0) {
|
|
6932
|
-
const capResult = await processCaptures({
|
|
6933
|
-
captureItems,
|
|
6934
|
-
betEntryId: betId,
|
|
6935
|
-
betDocId: betEntry._id,
|
|
6936
|
-
betData
|
|
6937
|
-
});
|
|
6938
|
-
entriesCreated = capResult.entriesCreated;
|
|
6939
|
-
relationsCreated = capResult.relationsCreated;
|
|
6940
|
-
captureErrors.push(...capResult.captureErrors);
|
|
6941
|
-
}
|
|
6942
|
-
const scoreResult = await computeAndUpdateScores({
|
|
6943
|
-
betEntryId: betId,
|
|
6944
|
-
betDocId: betEntry._id,
|
|
6945
|
-
betData,
|
|
6946
|
-
betEntry,
|
|
6947
|
-
userInput,
|
|
6948
|
-
argDimension,
|
|
6949
|
-
source,
|
|
6950
|
-
captureItems,
|
|
6951
|
-
chainSurfaced,
|
|
6952
|
-
constellation
|
|
6953
|
-
});
|
|
6954
|
-
captureErrors.push(...scoreResult.captureErrors);
|
|
6955
|
-
const wsCtx = await getWorkspaceContext();
|
|
6956
|
-
return assembleResponse({
|
|
6957
|
-
betEntryId: betId,
|
|
6958
|
-
betName: betEntry.name,
|
|
6959
|
-
workspaceSlug: wsCtx.workspaceSlug,
|
|
6960
|
-
scorecard: scoreResult.scorecard,
|
|
6961
|
-
scorecardCriteria: scoreResult.scorecardCriteria,
|
|
6962
|
-
phase: scoreResult.phase,
|
|
6963
|
-
activeDimension: scoreResult.activeDimension,
|
|
6964
|
-
nextDimension: scoreResult.nextDimension,
|
|
6965
|
-
dimensionResult: scoreResult.dimensionResult,
|
|
6966
|
-
coaching: scoreResult.coaching,
|
|
6967
|
-
overlap: scoreResult.overlap,
|
|
6968
|
-
sessionDrafts: scoreResult.sessionDrafts,
|
|
6969
|
-
alignment: scoreResult.alignment,
|
|
6970
|
-
chainSurfaced,
|
|
6971
|
-
scoringCtx: scoreResult.scoringCtx,
|
|
6972
|
-
isSmallBatch: scoreResult.isSmallBatch,
|
|
6973
|
-
completed: scoreResult.completed,
|
|
6974
|
-
refreshedData: scoreResult.refreshedData,
|
|
6975
|
-
buildContract: scoreResult.buildContract,
|
|
6976
|
-
commitBlockers: scoreResult.commitBlockers,
|
|
6977
|
-
entriesCreated,
|
|
6978
|
-
relationsCreated,
|
|
6979
|
-
captureErrors,
|
|
6980
|
-
captureItems
|
|
6981
|
-
});
|
|
6982
|
-
}
|
|
6983
|
-
async function handleScore(args) {
|
|
6984
|
-
const betId = args.betEntryId;
|
|
6985
|
-
if (!betId) {
|
|
6986
|
-
return validationResult("`betEntryId` is required for score action.");
|
|
6987
|
-
}
|
|
6988
|
-
const betEntry = await loadBetEntry(betId);
|
|
6989
|
-
if (!betEntry) {
|
|
6990
|
-
return {
|
|
6991
|
-
content: [{ type: "text", text: `Bet \`${betId}\` not found.` }],
|
|
6992
|
-
structuredContent: failure(
|
|
6993
|
-
"NOT_FOUND",
|
|
6994
|
-
`Bet '${betId}' not found.`,
|
|
6995
|
-
"Use facilitate action=start to begin a session.",
|
|
6996
|
-
[{ tool: "facilitate", description: "Start session", parameters: { action: "start" } }]
|
|
6997
|
-
)
|
|
6998
|
-
};
|
|
6999
|
-
}
|
|
7000
|
-
const constellation = await loadConstellationState(betId, betEntry._id);
|
|
7001
|
-
const betData = betEntry.data ?? {};
|
|
7002
|
-
const scoreLinks = betData.links ?? {};
|
|
7003
|
-
const isSmallBatch = detectSmallBatch(scoreLinks.appetite ?? "");
|
|
7004
|
-
const scoringCtx = buildCachedScoringContext(betData, constellation);
|
|
7005
|
-
const { scorecard, criteria: scorecardCriteria } = buildDetailedScorecard(scoringCtx, { isSmallBatch });
|
|
7006
|
-
const dims = activeDimensions(isSmallBatch);
|
|
7007
|
-
const { ready: captureReady, completed } = isCaptureReady(scorecard, isSmallBatch);
|
|
7008
|
-
const active = inferActiveDimension(scorecard, isSmallBatch);
|
|
7009
|
-
const phase = inferPhase(scorecard, isSmallBatch, extractContentEvidence(scoringCtx));
|
|
7010
|
-
const lines = [
|
|
7011
|
-
`# Scorecard \u2014 ${betEntry.name}`,
|
|
7012
|
-
`**Phase:** ${PHASE_LABELS[phase]}`,
|
|
7013
|
-
"",
|
|
7014
|
-
"| Dimension | Score | Status |",
|
|
7015
|
-
"|-----------|-------|--------|"
|
|
7016
|
-
];
|
|
7017
|
-
for (const dim of dims) {
|
|
7018
|
-
const label = DIMENSION_LABELS[dim];
|
|
7019
|
-
const score = scorecard[dim];
|
|
7020
|
-
const status = score >= 6 ? "done" : score > 0 ? "in progress" : "not started";
|
|
7021
|
-
const marker = dim === active ? " **<-**" : "";
|
|
7022
|
-
lines.push(`| ${label} | ${score}/10 | ${status}${marker} |`);
|
|
7023
|
-
}
|
|
7024
|
-
lines.push("");
|
|
7025
|
-
lines.push("### Criteria Detail");
|
|
7026
|
-
for (const dim of dims) {
|
|
7027
|
-
const label = DIMENSION_LABELS[dim];
|
|
7028
|
-
lines.push(`**${label} (${scorecard[dim]}/10):** ${formatCriteriaLine(scorecardCriteria[dim])}`);
|
|
7029
|
-
}
|
|
7030
|
-
lines.push("");
|
|
7031
|
-
lines.push(`Completed: ${completed.length}/${dims.length}`);
|
|
7032
|
-
lines.push(`Next dimension: ${DIMENSION_LABELS[active]}`);
|
|
7033
|
-
const sessionDrafts = await loadSessionDrafts(betId, betEntry._id);
|
|
7034
|
-
const constellationDrafts = sessionDrafts.filter((d) => d.collection !== "unknown");
|
|
7035
|
-
if (sessionDrafts.length > 0) {
|
|
7036
|
-
lines.push("");
|
|
7037
|
-
lines.push(`**Session drafts (${sessionDrafts.length}):** ${sessionDrafts.map((d) => `\`${d.entryId}\` ${d.name}`).join(", ")}`);
|
|
7038
|
-
}
|
|
7039
|
-
const hasStrategyLink = await hasStrategyLinkForEntry(betEntry._id, constellation.relations);
|
|
7040
|
-
const commitBlockers = computeCommitBlockers({
|
|
7041
|
-
betEntryId: betId,
|
|
7042
|
-
betDocId: betEntry._id,
|
|
7043
|
-
relations: constellation.relations,
|
|
7044
|
-
hasStrategyLink,
|
|
7045
|
-
betData,
|
|
7046
|
-
sessionDrafts
|
|
7047
|
-
});
|
|
7048
|
-
if (captureReady) {
|
|
7049
|
-
lines.push("");
|
|
7050
|
-
lines.push("**Capture ready** \u2014 the bet has enough shape to finalize.");
|
|
7051
|
-
if (constellationDrafts.length > 0) {
|
|
7052
|
-
lines.push("");
|
|
7053
|
-
lines.push(`**Constellation entries to commit alongside the bet:**`);
|
|
7054
|
-
for (const draft of constellationDrafts) {
|
|
7055
|
-
lines.push(`- \`${draft.entryId}\` ${draft.name} [${draft.collection}]`);
|
|
7056
|
-
}
|
|
7057
|
-
lines.push("");
|
|
7058
|
-
lines.push("When committing, also commit these constellation entries to keep the graph consistent.");
|
|
7059
|
-
}
|
|
7060
|
-
}
|
|
7061
|
-
if (commitBlockers.length > 0) {
|
|
7062
|
-
lines.push("");
|
|
7063
|
-
lines.push(`\u26A0 **Commit blockers (${commitBlockers.length}):**`);
|
|
7064
|
-
for (const b of commitBlockers) {
|
|
7065
|
-
lines.push(`- ${b.blocker} \u2192 \`${b.fix}\``);
|
|
7066
|
-
}
|
|
7067
|
-
}
|
|
7068
|
-
return {
|
|
7069
|
-
content: [{ type: "text", text: lines.join("\n") }],
|
|
7070
|
-
structuredContent: success(
|
|
7071
|
-
`Scorecard for ${betEntry.name}: ${completed.length}/${dims.length} complete. Phase: ${PHASE_LABELS[phase]}.${captureReady ? " Capture ready." : ""}`,
|
|
7072
|
-
{
|
|
7073
|
-
scorecard,
|
|
7074
|
-
phase,
|
|
7075
|
-
phaseLabel: PHASE_LABELS[phase],
|
|
7076
|
-
completedDimensions: completed,
|
|
7077
|
-
nextDimension: active,
|
|
7078
|
-
sessionDrafts,
|
|
7079
|
-
captureReady,
|
|
7080
|
-
constellationDrafts,
|
|
7081
|
-
commitBlockers: commitBlockers.length > 0 ? commitBlockers : void 0,
|
|
7082
|
-
scorecardCriteria
|
|
7083
|
-
}
|
|
7084
|
-
)
|
|
7085
|
-
};
|
|
7086
5187
|
}
|
|
7087
5188
|
async function handleResume(args) {
|
|
7088
5189
|
const betId = args.betEntryId;
|
|
@@ -7096,100 +5197,47 @@ async function handleResume(args) {
|
|
|
7096
5197
|
structuredContent: failure(
|
|
7097
5198
|
"NOT_FOUND",
|
|
7098
5199
|
`Bet '${betId}' not found.`,
|
|
7099
|
-
"Use
|
|
7100
|
-
[{ tool: "facilitate", description: "Start session", parameters: { action: "start" } }]
|
|
5200
|
+
"Use `capture` to create a bet entry, then resume."
|
|
7101
5201
|
)
|
|
7102
5202
|
};
|
|
7103
5203
|
}
|
|
7104
|
-
const constellation = await
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
const { scorecard, criteria: scorecardCriteria } = buildDetailedScorecard(scoringCtx, { isSmallBatch });
|
|
7110
|
-
const dims = activeDimensions(isSmallBatch);
|
|
7111
|
-
const { ready: captureReady, completed } = isCaptureReady(scorecard, isSmallBatch);
|
|
7112
|
-
const active = inferActiveDimension(scorecard, isSmallBatch);
|
|
7113
|
-
const phase = inferPhase(scorecard, isSmallBatch, extractContentEvidence(scoringCtx));
|
|
7114
|
-
const wsCtx = await getWorkspaceContext();
|
|
5204
|
+
const [constellation, sessionDrafts, wsCtx] = await Promise.all([
|
|
5205
|
+
loadConstellationState(betId, betEntry._id),
|
|
5206
|
+
loadSessionDrafts(betId, betEntry._id),
|
|
5207
|
+
getWorkspaceContext()
|
|
5208
|
+
]);
|
|
7115
5209
|
const studioUrl = buildStudioUrl(wsCtx.workspaceSlug, betId);
|
|
5210
|
+
const betData = betEntry.data ?? {};
|
|
7116
5211
|
const constellationSummary = [];
|
|
7117
5212
|
if (constellation.elementCount > 0) constellationSummary.push(`${constellation.elementCount} element(s)`);
|
|
7118
5213
|
if (constellation.riskCount > 0) constellationSummary.push(`${constellation.riskCount} risk(s)`);
|
|
7119
5214
|
if (constellation.decisionCount > 0) constellationSummary.push(`${constellation.decisionCount} decision(s)`);
|
|
7120
|
-
const sessionDrafts = await loadSessionDrafts(betId, betEntry._id);
|
|
7121
|
-
const response = {
|
|
7122
|
-
version: 2,
|
|
7123
|
-
phase,
|
|
7124
|
-
phaseLabel: PHASE_LABELS[phase],
|
|
7125
|
-
scorecard,
|
|
7126
|
-
overlap: [],
|
|
7127
|
-
alignment: [],
|
|
7128
|
-
chainSurfaced: [],
|
|
7129
|
-
suggestedCaptures: [],
|
|
7130
|
-
sessionDrafts,
|
|
7131
|
-
coaching: {
|
|
7132
|
-
observation: `Resuming session for "${betEntry.name}". ${constellationSummary.length > 0 ? `Constellation: ${constellationSummary.join(", ")}.` : "No constellation entries yet."}`,
|
|
7133
|
-
missing: dims.filter((d) => scorecard[d] < 6).map((d) => `${DIMENSION_LABELS[d]} needs work (${scorecard[d]}/10)`),
|
|
7134
|
-
pushback: "",
|
|
7135
|
-
suggestedQuestion: `Continue with ${DIMENSION_LABELS[active]}?`,
|
|
7136
|
-
captureReady,
|
|
7137
|
-
nextDimension: active
|
|
7138
|
-
},
|
|
7139
|
-
captured: {
|
|
7140
|
-
betEntryId: betId,
|
|
7141
|
-
studioUrl,
|
|
7142
|
-
entriesCreated: [],
|
|
7143
|
-
relationsCreated: 0
|
|
7144
|
-
},
|
|
7145
|
-
captureErrors: [],
|
|
7146
|
-
navigation: {
|
|
7147
|
-
completedDimensions: completed,
|
|
7148
|
-
activeDimension: active,
|
|
7149
|
-
suggestedOrder: dims
|
|
7150
|
-
},
|
|
7151
|
-
scorecardCriteria
|
|
7152
|
-
};
|
|
7153
|
-
const directive = buildDirective(scorecard, phase, betId, captureReady, active, active);
|
|
7154
|
-
const scorecardLines = dims.map((dim) => {
|
|
7155
|
-
const s = scorecard[dim];
|
|
7156
|
-
const bar = s > 0 ? "\u2588".repeat(s) + "\u2591".repeat(10 - s) : "\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591";
|
|
7157
|
-
const arrow = dim === active ? " \u25C0 next" : "";
|
|
7158
|
-
return `| ${DIMENSION_LABELS[dim]} | ${bar} ${s}/10${arrow} |`;
|
|
7159
|
-
});
|
|
7160
|
-
const criteriaDetail = dims.map(
|
|
7161
|
-
(dim) => `**${DIMENSION_LABELS[dim]} (${scorecard[dim]}/10):** ${formatCriteriaLine(scorecardCriteria[dim])}`
|
|
7162
|
-
);
|
|
7163
5215
|
const output = [
|
|
7164
|
-
`# Session
|
|
5216
|
+
`# Session State \u2014 ${betEntry.name}`,
|
|
7165
5217
|
"",
|
|
7166
5218
|
`**Bet:** \`${betId}\``,
|
|
7167
5219
|
`**Studio:** ${studioUrl}`,
|
|
7168
5220
|
`**Status:** ${betEntry.status}`,
|
|
7169
|
-
`**Phase:** ${PHASE_LABELS[phase]}`,
|
|
7170
5221
|
constellationSummary.length > 0 ? `**Constellation:** ${constellationSummary.join(", ")}` : "**Constellation:** Empty \u2014 no entries captured yet.",
|
|
7171
|
-
sessionDrafts.length > 0 ? `**Session drafts (${sessionDrafts.length}):** ${sessionDrafts.map((d) => `\`${d.entryId}\` ${d.name}`).join(", ")}` : ""
|
|
7172
|
-
"",
|
|
7173
|
-
"## Scorecard",
|
|
7174
|
-
"| Dimension | Score |",
|
|
7175
|
-
"|---|---|",
|
|
7176
|
-
...scorecardLines,
|
|
7177
|
-
"",
|
|
7178
|
-
"### Criteria Detail",
|
|
7179
|
-
...criteriaDetail,
|
|
7180
|
-
"",
|
|
7181
|
-
`Continue with ${DIMENSION_LABELS[active]}?`,
|
|
7182
|
-
directive ? `
|
|
7183
|
-
---
|
|
7184
|
-
## Agent Directive
|
|
7185
|
-
${directive}` : ""
|
|
5222
|
+
sessionDrafts.length > 0 ? `**Session drafts (${sessionDrafts.length}):** ${sessionDrafts.map((d) => `\`${d.entryId}\` ${d.name}`).join(", ")}` : ""
|
|
7186
5223
|
].filter(Boolean).join("\n");
|
|
7187
5224
|
return {
|
|
7188
5225
|
content: [{ type: "text", text: output }],
|
|
7189
5226
|
structuredContent: success(
|
|
7190
|
-
`
|
|
7191
|
-
|
|
7192
|
-
|
|
5227
|
+
`Loaded session state for "${betEntry.name}".`,
|
|
5228
|
+
{
|
|
5229
|
+
betEntryId: betId,
|
|
5230
|
+
betName: betEntry.name,
|
|
5231
|
+
betStatus: betEntry.status,
|
|
5232
|
+
betData,
|
|
5233
|
+
studioUrl,
|
|
5234
|
+
constellation: {
|
|
5235
|
+
elementCount: constellation.elementCount,
|
|
5236
|
+
riskCount: constellation.riskCount,
|
|
5237
|
+
decisionCount: constellation.decisionCount
|
|
5238
|
+
},
|
|
5239
|
+
sessionDrafts
|
|
5240
|
+
}
|
|
7193
5241
|
)
|
|
7194
5242
|
};
|
|
7195
5243
|
}
|
|
@@ -7206,26 +5254,14 @@ async function handleCommitConstellation(args) {
|
|
|
7206
5254
|
structuredContent: failure(
|
|
7207
5255
|
"NOT_FOUND",
|
|
7208
5256
|
`Bet '${betId}' not found.`,
|
|
7209
|
-
"Use
|
|
7210
|
-
[{ tool: "
|
|
5257
|
+
"Use 'capture' to create a bet entry first, then pass its ID here.",
|
|
5258
|
+
[{ tool: "capture", description: "Create a bet entry", parameters: { name: "<bet name>", description: "<bet description>" } }]
|
|
7211
5259
|
)
|
|
7212
5260
|
};
|
|
7213
5261
|
}
|
|
7214
5262
|
const betData = betEntry.data ?? {};
|
|
7215
5263
|
const operationId = args.operationId ?? `${betId}:${getAgentSessionId() ?? "sessionless"}:${betEntry.status}:${betEntry.currentVersion ?? 0}`;
|
|
7216
|
-
const
|
|
7217
|
-
entryId: betId
|
|
7218
|
-
});
|
|
7219
|
-
const hasStrategyLink = await hasStrategyLinkForEntry(betEntry._id, relations);
|
|
7220
|
-
const sessionDrafts = await loadSessionDrafts(betId, betEntry._id);
|
|
7221
|
-
const commitBlockerItems = computeCommitBlockers({
|
|
7222
|
-
betEntryId: betId,
|
|
7223
|
-
betDocId: betEntry._id,
|
|
7224
|
-
relations,
|
|
7225
|
-
hasStrategyLink,
|
|
7226
|
-
betData,
|
|
7227
|
-
sessionDrafts
|
|
7228
|
-
});
|
|
5264
|
+
const { blockers: commitBlockerItems, totalRelations } = await mcpQuery("chain.validateCommitConstellation", { betEntryId: betId });
|
|
7229
5265
|
if (commitBlockerItems.length > 0) {
|
|
7230
5266
|
return {
|
|
7231
5267
|
content: [{
|
|
@@ -7248,14 +5284,14 @@ async function handleCommitConstellation(args) {
|
|
|
7248
5284
|
operationId,
|
|
7249
5285
|
blockers: commitBlockerItems,
|
|
7250
5286
|
committedIds: [],
|
|
7251
|
-
totalRelations
|
|
5287
|
+
totalRelations
|
|
7252
5288
|
}
|
|
7253
5289
|
)
|
|
7254
5290
|
};
|
|
7255
5291
|
}
|
|
7256
5292
|
let contradictionWarnings = [];
|
|
7257
5293
|
try {
|
|
7258
|
-
const { runContradictionCheck } = await import("./smart-capture-
|
|
5294
|
+
const { runContradictionCheck } = await import("./smart-capture-VFCEUDDW.js");
|
|
7259
5295
|
const commitLinks = betData.links ?? {};
|
|
7260
5296
|
const descField = commitLinks.problem ?? betData.description ?? "";
|
|
7261
5297
|
contradictionWarnings = await runContradictionCheck(
|
|
@@ -7292,7 +5328,7 @@ No constellation entries were committed.`
|
|
|
7292
5328
|
"BACKEND_ERROR",
|
|
7293
5329
|
`Bet commit failed: ${msg}`,
|
|
7294
5330
|
"Retry or check for commit blockers.",
|
|
7295
|
-
[{ tool: "facilitate", description: "
|
|
5331
|
+
[{ tool: "facilitate", description: "Resume session", parameters: { action: "resume", betEntryId: betId } }]
|
|
7296
5332
|
)
|
|
7297
5333
|
};
|
|
7298
5334
|
}
|
|
@@ -8437,10 +6473,12 @@ Available presets: ${listPresets().map((p) => `\`${p.id}\``).join(", ")}`,
|
|
|
8437
6473
|
const skipped = [];
|
|
8438
6474
|
for (const col of preset.collections) {
|
|
8439
6475
|
try {
|
|
6476
|
+
const purpose = col.purpose ?? col.description;
|
|
8440
6477
|
await mcpCall("chain.createCollection", {
|
|
8441
6478
|
slug: col.slug,
|
|
8442
6479
|
name: col.name,
|
|
8443
6480
|
description: col.description,
|
|
6481
|
+
purpose,
|
|
8444
6482
|
icon: col.icon,
|
|
8445
6483
|
idPrefix: col.idPrefix,
|
|
8446
6484
|
fields: col.fields,
|
|
@@ -10376,6 +8414,7 @@ function formatScanReport(result) {
|
|
|
10376
8414
|
|
|
10377
8415
|
// src/tools/orient.ts
|
|
10378
8416
|
import { z as z20 } from "zod";
|
|
8417
|
+
var PURPOSE_GAP_PREFIX = "purpose-gap-";
|
|
10379
8418
|
function extractSessionEntryIds(priorSessions) {
|
|
10380
8419
|
const allSeen = /* @__PURE__ */ new Set();
|
|
10381
8420
|
const all = [];
|
|
@@ -10531,6 +8570,10 @@ function registerOrientTool(server) {
|
|
|
10531
8570
|
const oneLiner = formatGapOneLiner(briefGaps[0]);
|
|
10532
8571
|
if (oneLiner) lines.push(oneLiner);
|
|
10533
8572
|
}
|
|
8573
|
+
const briefClassificationGaps = briefGaps.filter((g) => g.id?.startsWith?.(PURPOSE_GAP_PREFIX));
|
|
8574
|
+
if (briefClassificationGaps.length > 0) {
|
|
8575
|
+
lines.push(`${briefClassificationGaps.length} collection(s) with weak purpose \u2014 classification may be inaccurate.`);
|
|
8576
|
+
}
|
|
10534
8577
|
if (recoveryBlock) {
|
|
10535
8578
|
lines.push("");
|
|
10536
8579
|
lines.push(...formatRecoveryBlock(recoveryBlock));
|
|
@@ -10804,6 +8847,16 @@ function registerOrientTool(server) {
|
|
|
10804
8847
|
lines.push("");
|
|
10805
8848
|
}
|
|
10806
8849
|
}
|
|
8850
|
+
const classificationGaps = readiness?.gaps?.filter((g) => g.id?.startsWith?.(PURPOSE_GAP_PREFIX)) ?? [];
|
|
8851
|
+
if (classificationGaps.length > 0) {
|
|
8852
|
+
lines.push("## Classification gaps");
|
|
8853
|
+
lines.push("_Collections with weak purpose \u2014 entry classification may be inaccurate._");
|
|
8854
|
+
lines.push("");
|
|
8855
|
+
for (const g of classificationGaps) {
|
|
8856
|
+
lines.push(`- ${g.label ?? g.id?.replace?.("purpose-gap-", "") ?? "unknown"}`);
|
|
8857
|
+
}
|
|
8858
|
+
lines.push("");
|
|
8859
|
+
}
|
|
10807
8860
|
if (errors.length > 0) {
|
|
10808
8861
|
lines.push("## Errors");
|
|
10809
8862
|
for (const err of errors) lines.push(`- ${err}`);
|
|
@@ -11442,7 +9495,7 @@ Browse: \`entries action=list collection=tracking-events\`.`
|
|
|
11442
9495
|
"## Knowledge Graph\nEntries are connected via typed relations (`entryRelations` table). Relations are bidirectional and collection-agnostic \u2014 any entry can link to any other entry.\n\n**Recommended relation types** (extensible \u2014 any string accepted):\n- `governs` \u2014 a rule constrains behavior of a feature\n- `defines_term_for` \u2014 a glossary term is canonical vocabulary for a feature/area\n- `belongs_to` \u2014 a feature belongs to a product area or parent concept\n- `informs` \u2014 a decision or insight informs a feature\n- `fills_slot` \u2014 an ingredient entry fills a slot in a map\n- `surfaces_tension_in` \u2014 a tension exists within a feature area\n- `related_to`, `depends_on`, `replaces`, `conflicts_with`, `references`, `confused_with`\n\nEach relation type is defined as a glossary entry (prefix `GT-REL-*`) to prevent terminology drift.\n\n**Tools:**\n- `context action=gather` \u2014 get the full context around any entry (multi-hop graph traversal)\n- `graph action=suggest` \u2014 discover potential connections for an entry\n- `relations action=create` \u2014 create a typed link between two entries\n- `graph action=find` \u2014 list direct relations for an entry\n\n**Convention:** When creating or updating entries in governed collections, always use `graph action=suggest` to discover and create relevant relations."
|
|
11443
9496
|
);
|
|
11444
9497
|
sections.push(
|
|
11445
|
-
"## Creating Knowledge\n**Entries:** Use `capture` as the primary tool for creating new entries. It handles the full workflow in one call:\n1. Creates the entry with collection-aware defaults (auto-fills dates, infers domains, sets priority)\n2. Auto-links related entries from across the chain (up to 5 confident matches)\n3. Returns a quality scorecard (X/10) with actionable improvement suggestions\n\n**Smart profiles** exist for: `tensions`, `business-rules`, `glossary`, `decisions`, `features`, `audiences`, `strategy`, `standards`, `maps`, `chains`, `tracking-events`.\nAll other collections use the `ENT-{random}` fallback profile.\n\n**Processes:** Use `chain action=create` with a template (e.g., `strategy-coherence`, `idm-proposal`). Fill links with `chain action=edit`.\n\n**Maps:** Use `map action=create` with a template (e.g., `lean-canvas`). Fill slots with `map-slot action=add`. Commit with `map-version action=commit`.\nNote: committing a map version creates a snapshot but does NOT change the entry status. To activate: `update-entry entryId=MAP-xxx status=active autoPublish=true`.\nUse `map-suggest` to discover ingredients for empty slots.\n\n**Prompts** (invoke via MCP prompt protocol):\n- `name-check` \u2014 verify a name against glossary conventions\n- `draft-decision-record` \u2014 scaffold a decision entry\n- `review-against-rules` \u2014 check work against business rules for a domain\n- `draft-rule-from-context` \u2014 create a new business rule from context\n
|
|
9498
|
+
"## Creating Knowledge\n**Entries:** Use `capture` as the primary tool for creating new entries. It handles the full workflow in one call:\n1. Creates the entry with collection-aware defaults (auto-fills dates, infers domains, sets priority)\n2. Auto-links related entries from across the chain (up to 5 confident matches)\n3. Returns a quality scorecard (X/10) with actionable improvement suggestions\n\n**Smart profiles** exist for: `tensions`, `business-rules`, `glossary`, `decisions`, `features`, `audiences`, `strategy`, `standards`, `maps`, `chains`, `tracking-events`.\nAll other collections use the `ENT-{random}` fallback profile.\n\n**Processes:** Use `chain action=create` with a template (e.g., `strategy-coherence`, `idm-proposal`). Fill links with `chain action=edit`.\n\n**Maps:** Use `map action=create` with a template (e.g., `lean-canvas`). Fill slots with `map-slot action=add`. Commit with `map-version action=commit`.\nNote: committing a map version creates a snapshot but does NOT change the entry status. To activate: `update-entry entryId=MAP-xxx status=active autoPublish=true`.\nUse `map-suggest` to discover ingredients for empty slots.\n\n**Prompts** (invoke via MCP prompt protocol):\n- `name-check` \u2014 verify a name against glossary conventions\n- `draft-decision-record` \u2014 scaffold a decision entry\n- `review-against-rules` \u2014 check work against business rules for a domain\n- `draft-rule-from-context` \u2014 create a new business rule from context\n\nUse `quality action=check` to score existing entries retroactively.\nUse `update-entry` for post-creation adjustments (status changes, field updates, deprecation)."
|
|
11446
9499
|
);
|
|
11447
9500
|
sections.push(
|
|
11448
9501
|
"## Where to Go Next\n- **Create entry** \u2192 `capture` tool (auto-links + quality score in one call)\n- **Full context** \u2192 `context action=gather` (by entry ID or task description)\n- **Discover links** \u2192 `graph action=suggest`\n- **Quality audit** \u2192 `quality action=check`\n- **Terminology** \u2192 `name-check` prompt or `productbrain://terminology` resource\n- **Schema details** \u2192 `productbrain://collections` resource or `collections action=list`\n- **Labels** \u2192 `productbrain://labels` resource or `labels` tool\n- **Any collection** \u2192 `productbrain://{slug}/entries` resource\n- **Log a decision** \u2192 `draft-decision-record` prompt\n- **Build a map** \u2192 `map action=create` + `map-slot action=add` + `map-version action=commit`\n- **Architecture map** \u2192 `architecture action=show` tool (layered system visualization)\n- **Explore a layer** \u2192 `architecture action=explore` tool (drill into Auth, Core, Features, etc.)\n- **Growth funnel** \u2192 `productbrain://growth-funnel` resource or `entries action=list collection=strategy status=draft`\n- **Audiences** \u2192 `productbrain://audiences/entries` resource or `entries action=list collection=audiences`\n- **Session identity** \u2192 `health action=whoami`\n- **Health check** \u2192 `health action=check`\n- **Debug MCP calls** \u2192 `health action=audit`"
|
|
@@ -11467,7 +9520,7 @@ var AGENT_CHEATSHEET = `# Product Brain \u2014 Agent Cheatsheet
|
|
|
11467
9520
|
| \`quality\` | Score an entry | \`entryId\` |
|
|
11468
9521
|
| \`session\` | Start / close agent session | \`action\` |
|
|
11469
9522
|
| \`health\` | Check / audit / whoami | \`action\` |
|
|
11470
|
-
| \`facilitate\` |
|
|
9523
|
+
| \`facilitate\` | Session resume & constellation commit | \`action\`: resume, commit-constellation |
|
|
11471
9524
|
|
|
11472
9525
|
## Collection Prefixes
|
|
11473
9526
|
GLO (glossary), BR (business-rules), PRI (principles), STD (standards),
|
|
@@ -12191,11 +10244,8 @@ Use one of these IDs to run a workflow.`
|
|
|
12191
10244
|
} catch {
|
|
12192
10245
|
kbContext = "\n_Could not load chain context \u2014 proceed without it._";
|
|
12193
10246
|
}
|
|
12194
|
-
const
|
|
12195
|
-
|
|
12196
|
-
const isLastRound = index === wf.rounds.length - 1;
|
|
12197
|
-
const checkpointLine = isFacilitated ? isLastRound ? `**Checkpoint**: After completing this round, persist with \`workflows action="checkpoint" workflowId="${wf.id}" roundId="${r.id}" summaryEntryId=<betEntryId>\` to finalize the durable run and link the bet.` : `**Checkpoint**: After completing this round via \`facilitate\`, also persist with \`workflows action="checkpoint" workflowId="${wf.id}" roundId="${r.id}"\`` : `**Checkpoint**: Immediately persist this round with \`workflows action="checkpoint" workflowId="${wf.id}" roundId="${r.id}"\``;
|
|
12198
|
-
return `### Round ${r.num}: ${r.label}
|
|
10247
|
+
const roundsPlan = wf.rounds.map(
|
|
10248
|
+
(r) => `### Round ${r.num}: ${r.label}
|
|
12199
10249
|
**Type**: ${r.type} | **Duration**: ~${r.maxDurationHint ?? "5 min"}
|
|
12200
10250
|
**Instruction**: ${r.instruction}
|
|
12201
10251
|
**Facilitator guidance**: ${r.facilitatorGuidance}
|
|
@@ -12204,13 +10254,13 @@ Use one of these IDs to run a workflow.`
|
|
|
12204
10254
|
` + (q.options ? q.options.map((o) => ` - ${o.id}: ${o.label}`).join("\n") : " _(open response)_")
|
|
12205
10255
|
).join("\n") : "") + `
|
|
12206
10256
|
**Output**: Capture to \`${r.outputSchema.field}\` (${r.outputSchema.format})
|
|
12207
|
-
|
|
12208
|
-
|
|
10257
|
+
**Checkpoint**: \`workflows action="checkpoint" workflowId="${wf.id}" roundId="${r.id}"\``
|
|
10258
|
+
).join("\n\n---\n\n");
|
|
12209
10259
|
const contextLine = context ? `
|
|
12210
10260
|
The participant provided this context: "${context}"
|
|
12211
10261
|
Use it \u2014 don't make them repeat themselves.
|
|
12212
10262
|
` : "";
|
|
12213
|
-
const outputDescription = descriptor ? describeWorkflowPrimaryOutput(descriptor) : wf.kbOutputCollection ? `Primary record routes to \`${wf.kbOutputCollection}\`.` : "Primary output routing
|
|
10263
|
+
const outputDescription = descriptor ? describeWorkflowPrimaryOutput(descriptor) : wf.kbOutputCollection ? `Primary record routes to \`${wf.kbOutputCollection}\`.` : "Primary output routing resolved at runtime.";
|
|
12214
10264
|
const summaryCapture = descriptor ? getWorkflowSummaryCaptureDefinition(descriptor) : wf.kbOutputCollection && wf.kbOutputTemplate ? {
|
|
12215
10265
|
routing: {
|
|
12216
10266
|
mode: "fixed",
|
|
@@ -12223,25 +10273,7 @@ Use it \u2014 don't make them repeat themselves.
|
|
|
12223
10273
|
const chainOutputBlock = summaryCapture ? `Primary record routes to \`${summaryCapture.routing.mode === "fixed" ? summaryCapture.routing.collection : "classifier"}\`.
|
|
12224
10274
|
Summary capture template: ${summaryCapture.nameTemplate}
|
|
12225
10275
|
Description field: ${summaryCapture.descriptionField ?? "collection default"}
|
|
12226
|
-
` :
|
|
12227
|
-
Use the workflow guidance to capture the primary record directly rather than relying on final summary auto-capture.
|
|
12228
|
-
`;
|
|
12229
|
-
const checkpointingBlock = isFacilitated ? `## Checkpointing & Resume (Facilitated Workflow)
|
|
12230
|
-
|
|
12231
|
-
This workflow uses \`${descriptor?.runtime.primaryTool ?? "facilitate"}\` as its primary tool, with durable workflow runs for tracking and observability.
|
|
12232
|
-
1. Before starting, check for an existing run with \`workflows action="get-run" workflowId="${wf.id}"\`.
|
|
12233
|
-
2. The \`${descriptor?.runtime.primaryTool ?? "facilitate"}\` tool drives the session. Use it for scoring, coaching, capture, and constellation management.
|
|
12234
|
-
3. After completing EACH round via \`${descriptor?.runtime.primaryTool ?? "facilitate"}\`, also call \`workflows action="checkpoint" workflowId="${wf.id}" roundId="<round-id>" output=<round-output>\` to mirror progress to the durable run.
|
|
12235
|
-
4. On the final round, pass \`summaryEntryId=<betEntryId>\` to link the bet as the run's summary. Do NOT use \`isFinal: true\` \u2014 the run auto-completes when all rounds are checkpointed.
|
|
12236
|
-
5. If a checkpoint fails, continue the session \u2014 \`${descriptor?.runtime.primaryTool ?? "facilitate"}\` state is preserved independently.
|
|
12237
|
-
` : `## Checkpointing & Resume
|
|
12238
|
-
|
|
12239
|
-
This workflow now supports durable workflow runs.
|
|
12240
|
-
1. Before starting, check for an existing run in the current session with \`workflows action="get-run" workflowId="${wf.id}"\`.
|
|
12241
|
-
2. If an active run exists, resume from its \`currentRoundId\` instead of restarting the workflow.
|
|
12242
|
-
3. After completing EACH round, immediately call \`workflows action="checkpoint" workflowId="${wf.id}" roundId="<round-id>" output=<round-output>\`.
|
|
12243
|
-
4. Only call \`isFinal: true\` from the terminal round when the workflow supports summary capture.
|
|
12244
|
-
5. If a checkpoint fails, say so explicitly and continue the conversation as backup.
|
|
10276
|
+
` : `${outputDescription}
|
|
12245
10277
|
`;
|
|
12246
10278
|
return {
|
|
12247
10279
|
messages: [
|
|
@@ -12261,7 +10293,15 @@ ${wf.facilitatorPreamble}
|
|
|
12261
10293
|
|
|
12262
10294
|
---
|
|
12263
10295
|
|
|
12264
|
-
|
|
10296
|
+
## Checkpointing & Resume
|
|
10297
|
+
|
|
10298
|
+
1. Before starting, check for an existing run: \`workflows action="get-run" workflowId="${wf.id}"\`.
|
|
10299
|
+
2. If an active run exists, resume from its \`currentRoundId\`.
|
|
10300
|
+
3. After EACH round: \`workflows action="checkpoint" workflowId="${wf.id}" roundId="<round-id>" output=<round-output>\`.
|
|
10301
|
+
` + (wf.facilitatorPreamble.includes("commit-constellation") ? `4. Finalize via \`facilitate action=commit-constellation\` \u2014 do NOT use \`isFinal: true\`.
|
|
10302
|
+
` : `4. Only call \`isFinal: true\` from the terminal round (or pass \`summaryEntryId=<primary-entry-id>\` to finalize with a linked entry).
|
|
10303
|
+
`) + `5. If a checkpoint fails, continue \u2014 the conversation is the backup.
|
|
10304
|
+
|
|
12265
10305
|
---
|
|
12266
10306
|
|
|
12267
10307
|
## Rounds
|
|
@@ -12278,7 +10318,7 @@ ${wf.errorRecovery}
|
|
|
12278
10318
|
|
|
12279
10319
|
## Chain Output
|
|
12280
10320
|
|
|
12281
|
-
` +
|
|
10321
|
+
${chainOutputBlock}` + kbContext + `
|
|
12282
10322
|
|
|
12283
10323
|
---
|
|
12284
10324
|
|
|
@@ -12289,167 +10329,6 @@ ${wf.errorRecovery}
|
|
|
12289
10329
|
};
|
|
12290
10330
|
}
|
|
12291
10331
|
);
|
|
12292
|
-
server.prompt(
|
|
12293
|
-
"shape-a-bet",
|
|
12294
|
-
"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.",
|
|
12295
|
-
{
|
|
12296
|
-
idea: z23.string().describe("Brief description of the idea or feature to shape (e.g. 'Improve the glossary editing flow')")
|
|
12297
|
-
},
|
|
12298
|
-
async ({ idea }) => {
|
|
12299
|
-
let strategicContext = "";
|
|
12300
|
-
let governanceContext = "";
|
|
12301
|
-
let activeBetsContext = "";
|
|
12302
|
-
let tensionsContext = "";
|
|
12303
|
-
try {
|
|
12304
|
-
const orient = await mcpQuery("chain.getOrientEntries", {});
|
|
12305
|
-
const sc = orient?.strategicContext;
|
|
12306
|
-
if (sc?.vision) strategicContext += `**Vision:** ${sc.vision}
|
|
12307
|
-
`;
|
|
12308
|
-
if (sc?.purpose) strategicContext += `**Purpose:** ${sc.purpose}
|
|
12309
|
-
`;
|
|
12310
|
-
if (sc?.currentBet) strategicContext += `**Current bet:** ${sc.currentBet}
|
|
12311
|
-
`;
|
|
12312
|
-
strategicContext += `${sc?.activeBetCount ?? 0} active bet(s), ${sc?.activeTensionCount ?? 0} open tension(s).
|
|
12313
|
-
`;
|
|
12314
|
-
if (orient?.activeBets?.length) {
|
|
12315
|
-
activeBetsContext = orient.activeBets.map((b) => `- \`${b.entryId ?? "?"}\` ${b.name}`).join("\n");
|
|
12316
|
-
}
|
|
12317
|
-
const govParts = [];
|
|
12318
|
-
if (orient?.principles?.length) {
|
|
12319
|
-
govParts.push("**Principles:**\n" + orient.principles.map((p) => `- \`${p.entryId}\` ${p.name}`).join("\n"));
|
|
12320
|
-
}
|
|
12321
|
-
if (orient?.standards?.length) {
|
|
12322
|
-
govParts.push("**Standards:**\n" + orient.standards.map((s) => `- \`${s.entryId}\` ${s.name}`).join("\n"));
|
|
12323
|
-
}
|
|
12324
|
-
if (orient?.businessRules?.length) {
|
|
12325
|
-
govParts.push("**Business Rules:**\n" + orient.businessRules.map((r) => `- \`${r.entryId}\` ${r.name}`).join("\n"));
|
|
12326
|
-
}
|
|
12327
|
-
governanceContext = govParts.join("\n\n");
|
|
12328
|
-
} catch {
|
|
12329
|
-
strategicContext = "_Could not load workspace context \u2014 proceed without it._\n";
|
|
12330
|
-
}
|
|
12331
|
-
try {
|
|
12332
|
-
const tensions = await mcpQuery(
|
|
12333
|
-
"chain.listEntries",
|
|
12334
|
-
{ collectionSlug: "tensions" }
|
|
12335
|
-
);
|
|
12336
|
-
const open = (tensions ?? []).filter((t) => t.workflowStatus === "open");
|
|
12337
|
-
if (open.length > 0) {
|
|
12338
|
-
tensionsContext = open.slice(0, 10).map((t) => `- \`${t.entryId ?? "?"}\` ${t.name}`).join("\n");
|
|
12339
|
-
}
|
|
12340
|
-
} catch {
|
|
12341
|
-
}
|
|
12342
|
-
return {
|
|
12343
|
-
messages: [{
|
|
12344
|
-
role: "user",
|
|
12345
|
-
content: {
|
|
12346
|
-
type: "text",
|
|
12347
|
-
text: `# Shape a Bet: ${idea}
|
|
12348
|
-
|
|
12349
|
-
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.
|
|
12350
|
-
|
|
12351
|
-
## Your Role (DEC-56 Boundary)
|
|
12352
|
-
|
|
12353
|
-
The **server judges** \u2014 it scores input against 7 rubric dimensions, detects overlap with existing Chain entries, and checks alignment with governance. The server returns structured coaching responses.
|
|
12354
|
-
**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.
|
|
12355
|
-
The \`coaching.suggestedQuestion\` is a suggestion, not a script \u2014 rephrase or skip based on conversation flow.
|
|
12356
|
-
|
|
12357
|
-
## Rubric Dimensions (7 scoring criteria)
|
|
12358
|
-
|
|
12359
|
-
Each scored 0-10 by the server:
|
|
12360
|
-
1. **Problem Clarity** \u2014 workaround described, who affected, frequency/severity, differentiated from existing tensions
|
|
12361
|
-
2. **Appetite Definition** \u2014 time constraint explicit, scope bounded, trade-offs acknowledged
|
|
12362
|
-
3. **Element Decomposition** \u2014 breadboard-level pieces identified, independently describable, no implementation leaks
|
|
12363
|
-
4. **Architecture Fit** \u2014 layer boundaries and dependency implications are explicit, aligned with existing system constraints
|
|
12364
|
-
5. **Risk Coverage** \u2014 rabbit holes named, mitigations or acceptances, codebase-level risks
|
|
12365
|
-
6. **Boundary Specification** \u2014 explicit no-gos, each prevents scope creep in a specific direction
|
|
12366
|
-
7. **Done-When Quality** \u2014 testable completion outcomes, measurable verification criteria, and clear acceptance conditions
|
|
12367
|
-
|
|
12368
|
-
## How to Use the Facilitate Tool
|
|
12369
|
-
|
|
12370
|
-
### Prerequisites
|
|
12371
|
-
1. \`session action=start\` (if not already active)
|
|
12372
|
-
2. \`orient\` (loads context and unlocks writes)
|
|
12373
|
-
|
|
12374
|
-
### Session Flow
|
|
12375
|
-
1. **Start:** \`facilitate action=start betName="${idea}"\`
|
|
12376
|
-
Starts shaping context only (no entry is created yet).
|
|
12377
|
-
|
|
12378
|
-
2. **First respond:** \`facilitate action=respond betName="${idea}" userInput="<user's input>"\`
|
|
12379
|
-
This creates the draft bet and captures the initial problem context.
|
|
12380
|
-
|
|
12381
|
-
Save \`captured.betEntryId\` from the response and reuse it for every subsequent call.
|
|
12382
|
-
|
|
12383
|
-
3. **Respond (repeat):** \`facilitate action=respond betEntryId="..." userInput="<user's input>"\`
|
|
12384
|
-
Optionally add \`dimension="problem_clarity"\` to target a specific dimension.
|
|
12385
|
-
|
|
12386
|
-
**CRITICAL \u2014 Capture is REQUIRED for structured items:**
|
|
12387
|
-
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.
|
|
12388
|
-
- Elements: \`capture={type:"element", name:"...", description:"..."}\`
|
|
12389
|
-
- Risks: \`capture={type:"risk", name:"...", description:"...", theme:"implementation"}\`
|
|
12390
|
-
- No-gos: \`capture={type:"noGo", name:"...", description:"..."}\`
|
|
12391
|
-
Each element, risk, and no-go needs its own \`respond\` call with \`capture\`.
|
|
12392
|
-
|
|
12393
|
-
4. **Score (anytime):** \`facilitate action=score betEntryId="..."\`
|
|
12394
|
-
Check the scorecard without advancing.
|
|
12395
|
-
|
|
12396
|
-
5. **Resume:** \`facilitate action=resume betEntryId="..."\`
|
|
12397
|
-
Reconstruct session from Chain state if returning to an existing bet.
|
|
12398
|
-
|
|
12399
|
-
### Reading the Response
|
|
12400
|
-
The \`respond\` action returns JSON with:
|
|
12401
|
-
- \`scorecard\` \u2014 current scores per dimension (0-10)
|
|
12402
|
-
- \`overlap\` \u2014 existing Chain entries that may duplicate this work
|
|
12403
|
-
- \`chainSurfaced\` \u2014 related entries the user might not know about (weave into coaching)
|
|
12404
|
-
- \`coaching.observation\` \u2014 what the server observed
|
|
12405
|
-
- \`coaching.missing\` \u2014 what's still needed
|
|
12406
|
-
- \`coaching.pushback\` \u2014 challenge to present to the user
|
|
12407
|
-
- \`coaching.suggestedQuestion\` \u2014 next question to ask
|
|
12408
|
-
- \`coaching.captureReady\` \u2014 true when 3+ dimensions are complete
|
|
12409
|
-
- \`navigation.activeDimension\` \u2014 the dimension being coached
|
|
12410
|
-
- \`captured\` \u2014 entries created this turn, Studio URL
|
|
12411
|
-
- \`captureErrors\` \u2014 partial failures (non-blocking)
|
|
12412
|
-
|
|
12413
|
-
### Coaching Style
|
|
12414
|
-
- One dimension at a time. Don't dump all dimensions at once.
|
|
12415
|
-
- Push back on vague problems. "Who's affected?" is always a valid question.
|
|
12416
|
-
- When \`chainSurfaced\` returns related entries, weave them in: "There's a decision on the Chain about this \u2014 DEC-56 says..."
|
|
12417
|
-
- When \`overlap\` finds matches, challenge: "This tension already exists. How is your bet different?"
|
|
12418
|
-
- When \`captureReady\` is true, offer to finalize. Don't force completion.
|
|
12419
|
-
- Keep chat messages short (5 lines or fewer). The tool does the heavy lifting.
|
|
12420
|
-
|
|
12421
|
-
## Workspace Context
|
|
12422
|
-
|
|
12423
|
-
` + (strategicContext ? `### Strategic Context
|
|
12424
|
-
${strategicContext}
|
|
12425
|
-
` : "") + (activeBetsContext ? `### Active Bets
|
|
12426
|
-
${activeBetsContext}
|
|
12427
|
-
|
|
12428
|
-
` : "") + (tensionsContext ? `### Open Tensions
|
|
12429
|
-
${tensionsContext}
|
|
12430
|
-
|
|
12431
|
-
` : "") + (governanceContext ? `### Governance Constraints
|
|
12432
|
-
_The shaping must honor these:_
|
|
12433
|
-
|
|
12434
|
-
${governanceContext}
|
|
12435
|
-
|
|
12436
|
-
` : "") + `## Constellation Capture (STA-1)
|
|
12437
|
-
The facilitate tool captures in real-time:
|
|
12438
|
-
- Elements \u2192 \`features\` collection, \`part_of\` \u2192 bet
|
|
12439
|
-
- Risks \u2192 \`tensions\` collection, \`constrains\` \u2192 bet
|
|
12440
|
-
- Decisions \u2192 \`decisions\` collection, \`informs\` \u2192 bet
|
|
12441
|
-
- No-gos \u2192 updated on the bet entry directly
|
|
12442
|
-
|
|
12443
|
-
Walk away mid-session and everything captured so far exists as drafts. \`facilitate action=resume\` picks it up.
|
|
12444
|
-
|
|
12445
|
-
---
|
|
12446
|
-
|
|
12447
|
-
**Begin now.** Call \`facilitate action=start betName="${idea}"\` and then ask the user to describe the problem.`
|
|
12448
|
-
}
|
|
12449
|
-
}]
|
|
12450
|
-
};
|
|
12451
|
-
}
|
|
12452
|
-
);
|
|
12453
10332
|
server.prompt(
|
|
12454
10333
|
"capture-and-connect",
|
|
12455
10334
|
"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.",
|
|
@@ -12998,4 +10877,4 @@ export {
|
|
|
12998
10877
|
SERVER_VERSION,
|
|
12999
10878
|
createProductBrainServer
|
|
13000
10879
|
};
|
|
13001
|
-
//# sourceMappingURL=chunk-
|
|
10880
|
+
//# sourceMappingURL=chunk-2TAMQOP7.js.map
|