brainclaw 0.29.2 → 1.5.3
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/README.md +193 -170
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +673 -24
- package/dist/commands/accept.js +3 -0
- package/dist/commands/add-step.js +11 -26
- package/dist/commands/agent-board.js +70 -3
- package/dist/commands/audit.js +19 -0
- package/dist/commands/check-policy.js +54 -0
- package/dist/commands/check-security-mcp.js +145 -0
- package/dist/commands/check-security.js +106 -0
- package/dist/commands/claim-resource.js +1 -0
- package/dist/commands/codev.js +672 -0
- package/dist/commands/compact.js +74 -0
- package/dist/commands/complete-step.js +16 -26
- package/dist/commands/constraint.js +8 -20
- package/dist/commands/decision.js +9 -20
- package/dist/commands/delete-plan.js +10 -12
- package/dist/commands/delete-step.js +16 -0
- package/dist/commands/dispatch.js +163 -0
- package/dist/commands/doctor.js +1122 -49
- package/dist/commands/enable-agent.js +1 -0
- package/dist/commands/export.js +280 -22
- package/dist/commands/handoff.js +33 -0
- package/dist/commands/harvest.js +189 -0
- package/dist/commands/hooks.js +82 -25
- package/dist/commands/inbox.js +169 -0
- package/dist/commands/init.js +38 -31
- package/dist/commands/install-hooks.js +71 -44
- package/dist/commands/link.js +89 -0
- package/dist/commands/list-claims.js +48 -3
- package/dist/commands/list-plans.js +129 -25
- package/dist/commands/loops-handlers.js +409 -0
- package/dist/commands/mcp-read-handlers.js +1628 -0
- package/dist/commands/mcp-schemas.generated.js +74 -0
- package/dist/commands/mcp.js +4221 -1501
- package/dist/commands/plan-resource.js +64 -0
- package/dist/commands/plan.js +12 -26
- package/dist/commands/prune.js +37 -2
- package/dist/commands/reflect.js +20 -7
- package/dist/commands/release-claim.js +11 -6
- package/dist/commands/release-notes.js +170 -0
- package/dist/commands/repair.js +210 -0
- package/dist/commands/run-profile.js +57 -0
- package/dist/commands/sequence.js +113 -0
- package/dist/commands/session-end.js +423 -14
- package/dist/commands/session-start.js +214 -41
- package/dist/commands/setup-security.js +103 -0
- package/dist/commands/setup.js +42 -4
- package/dist/commands/stale.js +109 -0
- package/dist/commands/switch.js +100 -2
- package/dist/commands/trap.js +14 -31
- package/dist/commands/update-handoff.js +63 -4
- package/dist/commands/update-plan.js +21 -28
- package/dist/commands/update-step.js +37 -0
- package/dist/commands/upgrade.js +313 -6
- package/dist/commands/usage.js +102 -0
- package/dist/commands/version.js +20 -0
- package/dist/commands/who.js +33 -5
- package/dist/commands/worktree.js +105 -0
- package/dist/core/actions.js +315 -0
- package/dist/core/agent-capability.js +610 -17
- package/dist/core/agent-context.js +7 -1
- package/dist/core/agent-files.js +1169 -85
- package/dist/core/agent-integrations.js +160 -5
- package/dist/core/agent-inventory.js +2 -0
- package/dist/core/agent-profiles.js +93 -0
- package/dist/core/agent-registry.js +162 -30
- package/dist/core/agentrun-reconciler.js +345 -0
- package/dist/core/agentruns.js +424 -0
- package/dist/core/ai-agent-detection.js +31 -10
- package/dist/core/archival.js +77 -0
- package/dist/core/assignment-sweeper.js +82 -0
- package/dist/core/assignments.js +367 -0
- package/dist/core/audit.js +30 -0
- package/dist/core/brainclaw-version.js +94 -2
- package/dist/core/candidates.js +93 -2
- package/dist/core/claims.js +419 -0
- package/dist/core/codev-metrics.js +77 -0
- package/dist/core/codev-personas.js +31 -0
- package/dist/core/codev-plan-gen.js +35 -0
- package/dist/core/codev-prompts.js +74 -0
- package/dist/core/codev-responses.js +62 -0
- package/dist/core/codev-rounds.js +218 -0
- package/dist/core/config.js +4 -0
- package/dist/core/context.js +381 -34
- package/dist/core/coordination.js +201 -6
- package/dist/core/cross-project.js +230 -16
- package/dist/core/default-profiles/doctor.yaml +11 -0
- package/dist/core/default-profiles/janitor.yaml +11 -0
- package/dist/core/default-profiles/onboarder.yaml +11 -0
- package/dist/core/default-profiles/reviewer.yaml +13 -0
- package/dist/core/dispatcher.js +1189 -0
- package/dist/core/duplicates.js +2 -2
- package/dist/core/entity-operations.js +450 -0
- package/dist/core/entity-registry.js +344 -0
- package/dist/core/events.js +106 -2
- package/dist/core/execution-adapters.js +154 -0
- package/dist/core/execution-context.js +63 -0
- package/dist/core/execution-profile.js +270 -0
- package/dist/core/execution.js +255 -0
- package/dist/core/facade-schema.js +81 -0
- package/dist/core/federation-cloud.js +99 -0
- package/dist/core/federation-message.js +52 -0
- package/dist/core/federation-transport.js +65 -0
- package/dist/core/gc-semantic.js +482 -0
- package/dist/core/governance.js +247 -0
- package/dist/core/guards.js +19 -0
- package/dist/core/ideation.js +72 -0
- package/dist/core/identity.js +110 -25
- package/dist/core/ids.js +6 -0
- package/dist/core/input-validation.js +2 -2
- package/dist/core/instruction-templates.js +344 -136
- package/dist/core/io.js +90 -11
- package/dist/core/lock.js +6 -2
- package/dist/core/loops/brief-assembly.js +213 -0
- package/dist/core/loops/facade-schema.js +148 -0
- package/dist/core/loops/index.js +7 -0
- package/dist/core/loops/iteration-engine.js +139 -0
- package/dist/core/loops/lock.js +385 -0
- package/dist/core/loops/store.js +201 -0
- package/dist/core/loops/types.js +403 -0
- package/dist/core/loops/verbs.js +534 -0
- package/dist/core/markdown.js +15 -3
- package/dist/core/memory-compactor.js +432 -0
- package/dist/core/memory-git.js +152 -8
- package/dist/core/messaging.js +278 -0
- package/dist/core/migration.js +32 -1
- package/dist/core/mutation-pipeline.js +4 -2
- package/dist/core/operations/memory-mutation.js +129 -0
- package/dist/core/operations/memory-write.js +78 -0
- package/dist/core/operations/plan.js +190 -0
- package/dist/core/policy.js +169 -0
- package/dist/core/reputation.js +9 -3
- package/dist/core/schema.js +491 -6
- package/dist/core/search.js +21 -2
- package/dist/core/security-cache.js +71 -0
- package/dist/core/security-guard.js +152 -0
- package/dist/core/security-scoring.js +86 -0
- package/dist/core/sequence.js +130 -0
- package/dist/core/socket-client.js +113 -0
- package/dist/core/staleness.js +246 -0
- package/dist/core/state.js +98 -22
- package/dist/core/store-resolution.js +43 -11
- package/dist/core/toml-writer.js +76 -0
- package/dist/core/upgrades/backup.js +232 -0
- package/dist/core/upgrades/health-check.js +169 -0
- package/dist/core/upgrades/patches/candidate-archive.js +145 -0
- package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
- package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
- package/dist/core/upgrades/schema-version.js +97 -0
- package/dist/core/worktree.js +606 -0
- package/dist/facts.js +114 -0
- package/dist/facts.json +111 -0
- package/docs/architecture/project-refs.md +5 -1
- package/docs/cli.md +690 -43
- package/docs/concepts/ideation-loop.md +317 -0
- package/docs/concepts/loop-engine.md +456 -0
- package/docs/concepts/mcp-governance.md +268 -0
- package/docs/concepts/memory-staleness.md +122 -0
- package/docs/concepts/multi-agent-workflows.md +166 -0
- package/docs/concepts/plans-and-claims.md +31 -6
- package/docs/concepts/project-md-convention.md +35 -0
- package/docs/concepts/troubleshooting.md +220 -0
- package/docs/concepts/upgrade-cli.md +202 -0
- package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
- package/docs/context-format-changelog.md +2 -2
- package/docs/context-format.md +2 -2
- package/docs/index.md +68 -0
- package/docs/integrations/agents.md +15 -16
- package/docs/integrations/cline.md +88 -0
- package/docs/integrations/codex.md +75 -23
- package/docs/integrations/continue.md +60 -0
- package/docs/integrations/copilot.md +67 -9
- package/docs/integrations/kilocode.md +72 -0
- package/docs/integrations/mcp.md +304 -21
- package/docs/integrations/mistral-vibe.md +122 -0
- package/docs/integrations/opencode.md +84 -0
- package/docs/integrations/overview.md +23 -8
- package/docs/integrations/roo.md +74 -0
- package/docs/integrations/windsurf.md +83 -0
- package/docs/mcp-schema-changelog.md +191 -1
- package/docs/playbooks/integration/index.md +121 -0
- package/docs/playbooks/productivity/index.md +102 -0
- package/docs/playbooks/team/index.md +122 -0
- package/docs/product/agent-first-model.md +184 -0
- package/docs/product/entity-model-audit.md +462 -0
- package/docs/quickstart-existing-project.md +135 -0
- package/docs/quickstart.md +124 -37
- package/docs/release-maintenance.md +79 -0
- package/docs/review.md +2 -0
- package/docs/server-operations.md +118 -0
- package/package.json +20 -12
- package/dist/commands/claude-desktop-extension.js +0 -18
- package/dist/commands/diff.js +0 -99
- package/dist/core/claude-desktop-extension.js +0 -224
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: brainclaw codev <topic>
|
|
3
|
+
*
|
|
4
|
+
* v2: Autonomous facilitation with auto-generated briefs.
|
|
5
|
+
* Creates a thread, loads project context, generates rich exposition,
|
|
6
|
+
* persona briefs with phase instructions, facilitation contract, and synthesis.
|
|
7
|
+
* Optionally spawns agent CLI instances with --spawn.
|
|
8
|
+
*
|
|
9
|
+
* Supports multiple agent engines via --agents (e.g. claude-code,codex,antigravity).
|
|
10
|
+
* Personas are distributed round-robin across available agents.
|
|
11
|
+
*
|
|
12
|
+
* Without --spawn: enhanced v1 — creates thread + prints coordinator prompt.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
17
|
+
import { execFileSync } from 'node:child_process';
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import os from 'node:os';
|
|
21
|
+
import { memoryExists, readProjectVision } from '../core/io.js';
|
|
22
|
+
import { sendMessage, getThread, getThreadCount } from '../core/messaging.js';
|
|
23
|
+
import { resolveCurrentAgentName } from '../core/agent-registry.js';
|
|
24
|
+
import { CODEV_PERSONAS, listPersonas } from '../core/codev-personas.js';
|
|
25
|
+
import { buildContext } from '../core/context.js';
|
|
26
|
+
import { buildCoordinationSnapshot } from '../core/coordination.js';
|
|
27
|
+
import { getDefaultInvokeTemplate, getSpawnableAgents } from '../core/agent-capability.js';
|
|
28
|
+
import { executeRound } from '../core/codev-rounds.js';
|
|
29
|
+
import { loadIdeationRound } from '../core/ideation.js';
|
|
30
|
+
import { summarizeMetrics, summarizeMetricsByRound } from '../core/codev-metrics.js';
|
|
31
|
+
import { generatePlansFromConvergence, generateSummaryNote } from '../core/codev-plan-gen.js';
|
|
32
|
+
function toSlug(topic) {
|
|
33
|
+
return topic.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 40);
|
|
34
|
+
}
|
|
35
|
+
function sanitizeForPath(slug) {
|
|
36
|
+
return slug.replace(/[<>:"/\\|?*]/g, '_');
|
|
37
|
+
}
|
|
38
|
+
const STOP_WORDS = new Set([
|
|
39
|
+
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
|
40
|
+
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
|
|
41
|
+
'should', 'may', 'might', 'shall', 'can', 'to', 'of', 'in', 'for',
|
|
42
|
+
'on', 'with', 'at', 'by', 'from', 'as', 'into', 'through', 'during',
|
|
43
|
+
'before', 'after', 'above', 'below', 'between', 'out', 'off', 'over',
|
|
44
|
+
'under', 'again', 'further', 'then', 'once', 'and', 'but', 'or', 'nor',
|
|
45
|
+
'not', 'so', 'yet', 'both', 'each', 'few', 'more', 'most', 'other',
|
|
46
|
+
'some', 'such', 'no', 'only', 'own', 'same', 'than', 'too', 'very',
|
|
47
|
+
'just', 'about', 'how', 'what', 'which', 'who', 'when', 'where', 'why',
|
|
48
|
+
'all', 'any', 'every', 'this', 'that', 'these', 'those', 'it', 'its',
|
|
49
|
+
]);
|
|
50
|
+
function extractKeywords(topic) {
|
|
51
|
+
return topic.toLowerCase()
|
|
52
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
53
|
+
.split(/\s+/)
|
|
54
|
+
.filter(w => w.length > 2 && !STOP_WORDS.has(w));
|
|
55
|
+
}
|
|
56
|
+
export function runCodev(topic, options = {}) {
|
|
57
|
+
// Handle --personas list (no topic required)
|
|
58
|
+
if (options.personas === 'list') {
|
|
59
|
+
console.log('\nAvailable CoDev personas:\n');
|
|
60
|
+
console.log(listPersonas());
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!topic) {
|
|
64
|
+
console.error('Error: <topic> is required. Usage: brainclaw codev <topic>');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const cwd = options.cwd ?? process.cwd();
|
|
68
|
+
if (!memoryExists(cwd)) {
|
|
69
|
+
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
// Parse --model-map into a lookup: persona → model
|
|
73
|
+
const modelOverrides = {};
|
|
74
|
+
if (options.modelMap) {
|
|
75
|
+
for (const entry of options.modelMap.split(',')) {
|
|
76
|
+
const [persona, model] = entry.split(':').map(s => s.trim());
|
|
77
|
+
if (persona && model)
|
|
78
|
+
modelOverrides[persona] = model;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const agent = resolveCurrentAgentName(cwd) ?? 'coordinator';
|
|
82
|
+
const tierName = options.personas ?? 'tier1';
|
|
83
|
+
const personas = CODEV_PERSONAS[tierName];
|
|
84
|
+
if (!personas) {
|
|
85
|
+
console.error(`Error: unknown persona tier "${tierName}". Use tier1, tier2, or list.`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
89
|
+
const threadId = `codev:${date}:${toSlug(topic)}`;
|
|
90
|
+
// ── Load project context ──────────────────────────────────
|
|
91
|
+
const vision = readProjectVision(cwd);
|
|
92
|
+
const snapshot = buildCoordinationSnapshot({ cwd });
|
|
93
|
+
const contextResult = buildContext({ cwd });
|
|
94
|
+
// Find related plans via keyword matching
|
|
95
|
+
const keywords = extractKeywords(topic);
|
|
96
|
+
const relatedPlans = snapshot.active_plans.filter(p => keywords.some(k => p.text?.toLowerCase().includes(k)));
|
|
97
|
+
// Extract constraints, decisions, traps from context
|
|
98
|
+
const constraints = contextResult.selected
|
|
99
|
+
.filter(i => i.section === 'constraint')
|
|
100
|
+
.map(i => i.text);
|
|
101
|
+
const decisions = contextResult.selected
|
|
102
|
+
.filter(i => i.section === 'decision')
|
|
103
|
+
.map(i => i.text);
|
|
104
|
+
const traps = (snapshot.known_traps ?? [])
|
|
105
|
+
.map(t => `[${t.severity}] ${t.text}`);
|
|
106
|
+
// ── Phase 1: Exposition ───────────────────────────────────
|
|
107
|
+
const expositionText = buildExposition(topic, vision, relatedPlans, constraints, traps);
|
|
108
|
+
const opening = sendMessage({
|
|
109
|
+
from: agent,
|
|
110
|
+
to: agent,
|
|
111
|
+
type: 'rfc',
|
|
112
|
+
text: expositionText,
|
|
113
|
+
thread_id: threadId,
|
|
114
|
+
tags: ['codev', 'phase:exposition'],
|
|
115
|
+
}, cwd);
|
|
116
|
+
// ── Resolve spawn agents ──────────────────────────────────
|
|
117
|
+
let spawnAgents = [];
|
|
118
|
+
if (options.spawn) {
|
|
119
|
+
spawnAgents = resolveSpawnAgents(options.agents);
|
|
120
|
+
if (spawnAgents.length === 0) {
|
|
121
|
+
console.error('Error: --spawn requested but no spawnable agents found in PATH.');
|
|
122
|
+
console.error('Available agents: ' + getSpawnableAgents().map(a => a.name).join(', '));
|
|
123
|
+
console.error('Install one or specify with --agents <name1,name2,...>');
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
const agentNames = spawnAgents.map(a => a.name).join(', ');
|
|
127
|
+
console.log(`Resolved ${spawnAgents.length} spawn agent(s): ${agentNames}`);
|
|
128
|
+
if (options.fresh) {
|
|
129
|
+
const tmpDir = path.join(os.tmpdir(), 'brainclaw-codev', sanitizeForPath(threadId));
|
|
130
|
+
if (fs.existsSync(tmpDir))
|
|
131
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
132
|
+
// Also delete ideation artifacts for this thread
|
|
133
|
+
const ideDir = path.join(cwd, '.brainclaw', 'coordination', 'ideation', sanitizeForPath(threadId));
|
|
134
|
+
if (fs.existsSync(ideDir))
|
|
135
|
+
fs.rmSync(ideDir, { recursive: true, force: true });
|
|
136
|
+
console.log('Fresh run: cleared cached responses and artifacts.');
|
|
137
|
+
}
|
|
138
|
+
// ── v3: Round-based orchestration ────────────────────────
|
|
139
|
+
const totalRounds = Math.max(2, options.rounds ?? 3);
|
|
140
|
+
const targetDuration = options.targetDuration ?? 120;
|
|
141
|
+
const sessionStart = Date.now();
|
|
142
|
+
for (let r = 0; r < totalRounds; r++) {
|
|
143
|
+
let roundType;
|
|
144
|
+
if (r === 0)
|
|
145
|
+
roundType = 'position';
|
|
146
|
+
else if (r === totalRounds - 1)
|
|
147
|
+
roundType = 'convergence';
|
|
148
|
+
else
|
|
149
|
+
roundType = 'reaction';
|
|
150
|
+
console.log(`\nRound ${r}/${totalRounds} (${roundType})...`);
|
|
151
|
+
executeRound({
|
|
152
|
+
threadSlug: threadId,
|
|
153
|
+
roundNumber: r,
|
|
154
|
+
roundType,
|
|
155
|
+
personas,
|
|
156
|
+
agents: spawnAgents.map(a => ({ name: a.name, binaryPath: a.binaryPath })),
|
|
157
|
+
exposition: expositionText,
|
|
158
|
+
targetDurationSeconds: targetDuration,
|
|
159
|
+
cwd,
|
|
160
|
+
quorum: options.quorum,
|
|
161
|
+
modelOverrides: Object.keys(modelOverrides).length > 0 ? modelOverrides : undefined,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
// Cleanup temp response files
|
|
165
|
+
const tmpDir = path.join(os.tmpdir(), 'brainclaw-codev', sanitizeForPath(threadId));
|
|
166
|
+
try {
|
|
167
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
168
|
+
}
|
|
169
|
+
catch { /* best effort */ }
|
|
170
|
+
// ── Print final summary ──────────────────────────────────
|
|
171
|
+
const lastRound = loadIdeationRound(threadId, totalRounds - 1, cwd);
|
|
172
|
+
if (options.json) {
|
|
173
|
+
console.log(JSON.stringify({
|
|
174
|
+
threadId,
|
|
175
|
+
opening: opening.id,
|
|
176
|
+
personas: personas.map(p => p.name),
|
|
177
|
+
rounds: totalRounds,
|
|
178
|
+
convergences: lastRound?.convergences ?? [],
|
|
179
|
+
tensions: lastRound?.tensions ?? [],
|
|
180
|
+
spawned: true,
|
|
181
|
+
}, null, 2));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
console.log(`\n--- CoDev v3 session complete: ${threadId} ---\n`);
|
|
185
|
+
const elapsedSec = Math.round((Date.now() - sessionStart) / 1000);
|
|
186
|
+
const elapsedMin = Math.floor(elapsedSec / 60);
|
|
187
|
+
const elapsedDisplay = elapsedMin > 0 ? `${elapsedMin}m ${elapsedSec % 60}s` : `${elapsedSec}s`;
|
|
188
|
+
console.log(`${totalRounds} rounds completed across ${spawnAgents.length} agent(s) [${agentNames}] in ${elapsedDisplay}.`);
|
|
189
|
+
if (lastRound) {
|
|
190
|
+
if (lastRound.convergences.length > 0) {
|
|
191
|
+
console.log('\nConvergences:');
|
|
192
|
+
for (const c of lastRound.convergences)
|
|
193
|
+
console.log(` - ${c}`);
|
|
194
|
+
}
|
|
195
|
+
if (lastRound.tensions.length > 0) {
|
|
196
|
+
console.log('\nTensions:');
|
|
197
|
+
for (const t of lastRound.tensions)
|
|
198
|
+
console.log(` - ${t}`);
|
|
199
|
+
}
|
|
200
|
+
if (lastRound.positions.length > 0) {
|
|
201
|
+
console.log('\nFinal positions:');
|
|
202
|
+
for (const p of lastRound.positions) {
|
|
203
|
+
console.log(` [${p.persona}] ${p.text.slice(0, 200)}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
console.log(`\nMonitor thread: brainclaw thread ${threadId}`);
|
|
208
|
+
// ── Plan generation from convergences ───────────────────
|
|
209
|
+
if (lastRound?.convergences.length) {
|
|
210
|
+
console.log('\nGenerating plan items from convergences...');
|
|
211
|
+
try {
|
|
212
|
+
const planResult = generatePlansFromConvergence(threadId, cwd);
|
|
213
|
+
if (planResult.plans.length > 0) {
|
|
214
|
+
console.log(` ✓ Created ${planResult.plans.length} plan(s):`);
|
|
215
|
+
for (const p of planResult.plans)
|
|
216
|
+
console.log(` [${p.id}] ${p.text.slice(0, 120)}`);
|
|
217
|
+
generateSummaryNote(threadId, planResult, cwd);
|
|
218
|
+
}
|
|
219
|
+
if (planResult.skipped.length > 0) {
|
|
220
|
+
console.log(` ⚠ Skipped ${planResult.skipped.length} convergence(s) (plan creation failed).`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
console.warn(` ⚠ Plan generation failed: ${err.message}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// ── Timing metrics ───────────────────────────────────────
|
|
228
|
+
if (options.metrics) {
|
|
229
|
+
printMetricsSummary(threadId, cwd);
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
// ── Phase 2: Clarification briefs ─────────────────────────
|
|
234
|
+
for (let i = 0; i < personas.length; i++) {
|
|
235
|
+
const persona = personas[i];
|
|
236
|
+
// For spawned agents, skip thread history to avoid exponential message growth.
|
|
237
|
+
// Each spawned agent gets the exposition + their persona brief — that's enough context.
|
|
238
|
+
const threadHistory = options.spawn ? [] : getThread(threadId, cwd, { truncateText: 2000 });
|
|
239
|
+
const brief = buildConsultantBrief(persona, expositionText, 'clarification', threadHistory);
|
|
240
|
+
sendMessage({
|
|
241
|
+
from: agent,
|
|
242
|
+
to: agent,
|
|
243
|
+
type: 'rfc',
|
|
244
|
+
text: brief,
|
|
245
|
+
thread_id: threadId,
|
|
246
|
+
tags: ['codev', 'phase:clarification', `persona:${persona.name}`],
|
|
247
|
+
}, cwd);
|
|
248
|
+
if (options.spawn && spawnAgents.length > 0) {
|
|
249
|
+
const targetAgent = spawnAgents[i % spawnAgents.length];
|
|
250
|
+
console.log(` → ${persona.name} (clarification) → ${targetAgent.name}`);
|
|
251
|
+
spawnConsultant(brief, threadId, persona.name, cwd, targetAgent);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (options.spawn) {
|
|
255
|
+
const baseline = getThreadCount(threadId, cwd);
|
|
256
|
+
console.log(`Waiting for ${personas.length} clarification responses (timeout: 5 min)...`);
|
|
257
|
+
const poll = awaitThreadGrowth(threadId, baseline, personas.length, cwd);
|
|
258
|
+
if (poll.timedOut) {
|
|
259
|
+
console.log(`Warning: timed out — received ${poll.received}/${personas.length} clarification responses.`);
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
console.log(`All ${personas.length} clarification responses received.`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// ── Phase 3: Facilitation contract ────────────────────────
|
|
266
|
+
const clarificationMessages = getThread(threadId, cwd, { truncateText: 3000 })
|
|
267
|
+
.filter(m => m.tags?.includes('phase:clarification'));
|
|
268
|
+
const contractText = buildContract(topic, clarificationMessages, decisions, constraints, traps);
|
|
269
|
+
sendMessage({
|
|
270
|
+
from: agent,
|
|
271
|
+
to: agent,
|
|
272
|
+
type: 'rfc',
|
|
273
|
+
text: contractText,
|
|
274
|
+
thread_id: threadId,
|
|
275
|
+
tags: ['codev', 'phase:contract'],
|
|
276
|
+
}, cwd);
|
|
277
|
+
// ── Phase 4: Consultation briefs ──────────────────────────
|
|
278
|
+
for (let i = 0; i < personas.length; i++) {
|
|
279
|
+
const persona = personas[i];
|
|
280
|
+
// For spawned agents, include only the contract (not full history) to avoid message explosion
|
|
281
|
+
const threadHistory = options.spawn ? [] : getThread(threadId, cwd, { truncateText: 2000 });
|
|
282
|
+
const brief = buildConsultantBrief(persona, expositionText, 'consultation', threadHistory);
|
|
283
|
+
sendMessage({
|
|
284
|
+
from: agent,
|
|
285
|
+
to: agent,
|
|
286
|
+
type: 'rfc',
|
|
287
|
+
text: brief,
|
|
288
|
+
thread_id: threadId,
|
|
289
|
+
tags: ['codev', 'phase:consultation', `persona:${persona.name}`],
|
|
290
|
+
}, cwd);
|
|
291
|
+
if (options.spawn && spawnAgents.length > 0) {
|
|
292
|
+
const targetAgent = spawnAgents[i % spawnAgents.length];
|
|
293
|
+
console.log(` → ${persona.name} (consultation) → ${targetAgent.name}`);
|
|
294
|
+
spawnConsultant(brief, threadId, persona.name, cwd, targetAgent);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (options.spawn) {
|
|
298
|
+
const baseline = getThreadCount(threadId, cwd);
|
|
299
|
+
console.log(`Waiting for ${personas.length} consultation responses (timeout: 5 min)...`);
|
|
300
|
+
const poll = awaitThreadGrowth(threadId, baseline, personas.length, cwd);
|
|
301
|
+
if (poll.timedOut) {
|
|
302
|
+
console.log(`Warning: timed out — received ${poll.received}/${personas.length} consultation responses.`);
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
console.log(`All ${personas.length} consultation responses received.`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// ── Phase 5: Synthesis ────────────────────────────────────
|
|
309
|
+
const consultationMessages = getThread(threadId, cwd, { truncateText: 3000 })
|
|
310
|
+
.filter(m => m.tags?.includes('phase:consultation'));
|
|
311
|
+
const synthesisText = buildSynthesis(topic, consultationMessages);
|
|
312
|
+
sendMessage({
|
|
313
|
+
from: agent,
|
|
314
|
+
to: agent,
|
|
315
|
+
type: 'rfc',
|
|
316
|
+
text: synthesisText,
|
|
317
|
+
thread_id: threadId,
|
|
318
|
+
tags: ['codev', 'phase:synthesis'],
|
|
319
|
+
}, cwd);
|
|
320
|
+
// ── Output ────────────────────────────────────────────────
|
|
321
|
+
if (options.json) {
|
|
322
|
+
console.log(JSON.stringify({
|
|
323
|
+
threadId,
|
|
324
|
+
opening: opening.id,
|
|
325
|
+
personas: personas.map(p => p.name),
|
|
326
|
+
phases: ['exposition', 'clarification', 'contract', 'consultation', 'synthesis'],
|
|
327
|
+
spawned: options.spawn ?? false,
|
|
328
|
+
}, null, 2));
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
console.log(`\n--- CoDev v2 session: ${threadId} ---\n`);
|
|
332
|
+
console.log(`Thread created with ${personas.length} persona briefs (${tierName}).`);
|
|
333
|
+
console.log(`Phases: exposition → clarification → contract → consultation → synthesis\n`);
|
|
334
|
+
if (options.spawn) {
|
|
335
|
+
const agentNames = spawnAgents.map(a => a.name).join(', ');
|
|
336
|
+
console.log(`Spawned ${personas.length * 2} agent instances across [${agentNames}] (clarification + consultation).`);
|
|
337
|
+
console.log('Agents will write responses to the thread. Monitor with:');
|
|
338
|
+
console.log(` brainclaw thread ${threadId}\n`);
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
if (options.checkpoint) {
|
|
342
|
+
console.log('CHECKPOINT: Review the topic and personas above.');
|
|
343
|
+
console.log('When ready, ask the LLM to proceed with the consultation.\n');
|
|
344
|
+
}
|
|
345
|
+
console.log(buildCoordinatorPrompt(threadId, personas, topic, options.checkpoint));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function resolveAgentBinaryPath(binary) {
|
|
349
|
+
try {
|
|
350
|
+
const isWindows = process.platform === 'win32';
|
|
351
|
+
const cmd = isWindows ? 'where' : 'which';
|
|
352
|
+
const result = execFileSync(cmd, [binary], { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
353
|
+
const firstLine = result.trim().split('\n')[0]?.trim();
|
|
354
|
+
return firstLine || undefined;
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function resolveSpawnAgents(agentsOption) {
|
|
361
|
+
if (agentsOption) {
|
|
362
|
+
const names = agentsOption.split(',').map(n => n.trim()).filter(Boolean);
|
|
363
|
+
const resolved = [];
|
|
364
|
+
for (const name of names) {
|
|
365
|
+
const template = getDefaultInvokeTemplate(name);
|
|
366
|
+
if (!template) {
|
|
367
|
+
console.warn(`⚠ Agent '${name}' has no invoke template — skipping.`);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const binaryPath = resolveAgentBinaryPath(template.binary);
|
|
371
|
+
if (!binaryPath) {
|
|
372
|
+
console.warn(`⚠ Agent '${name}' binary '${template.binary}' not found in PATH — skipping.`);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
resolved.push({ name, binary: template.binary, binaryPath, template });
|
|
376
|
+
}
|
|
377
|
+
return resolved;
|
|
378
|
+
}
|
|
379
|
+
// Default: auto-detect available spawnable agents
|
|
380
|
+
const all = getSpawnableAgents();
|
|
381
|
+
const resolved = [];
|
|
382
|
+
for (const { name, template } of all) {
|
|
383
|
+
const binaryPath = resolveAgentBinaryPath(template.binary);
|
|
384
|
+
if (binaryPath) {
|
|
385
|
+
resolved.push({ name, binary: template.binary, binaryPath, template });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return resolved;
|
|
389
|
+
}
|
|
390
|
+
// ── Spawn helper ──────────────────────────────────────────────
|
|
391
|
+
function buildResponseInstruction(threadId, personaName) {
|
|
392
|
+
return [
|
|
393
|
+
'',
|
|
394
|
+
'## Response Protocol',
|
|
395
|
+
'After formulating your response, you MUST post it back to the brainclaw thread.',
|
|
396
|
+
'Run this command in Bash (adapt quoting as needed for your response content):',
|
|
397
|
+
'',
|
|
398
|
+
`brainclaw inbox send coordinator "$(cat <<'CODEV_RESPONSE'`,
|
|
399
|
+
'<YOUR RESPONSE HERE>',
|
|
400
|
+
`CODEV_RESPONSE`,
|
|
401
|
+
`)" --type rfc --thread ${threadId} --agent ${personaName}`,
|
|
402
|
+
'',
|
|
403
|
+
'Replace <YOUR RESPONSE HERE> with your actual response text.',
|
|
404
|
+
'This is REQUIRED — your work is lost if you do not post it.',
|
|
405
|
+
].join('\n');
|
|
406
|
+
}
|
|
407
|
+
function writeBriefToTempFile(brief, personaName) {
|
|
408
|
+
const tmpDir = path.join(os.tmpdir(), 'brainclaw-codev');
|
|
409
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
410
|
+
const filePath = path.join(tmpDir, `${personaName}-${Date.now()}.md`);
|
|
411
|
+
fs.writeFileSync(filePath, brief, 'utf-8');
|
|
412
|
+
return filePath;
|
|
413
|
+
}
|
|
414
|
+
function spawnConsultant(brief, threadId, personaName, cwd, agent) {
|
|
415
|
+
const fullBrief = brief + buildResponseInstruction(threadId, personaName);
|
|
416
|
+
const briefFile = writeBriefToTempFile(fullBrief, personaName);
|
|
417
|
+
const { binaryPath, name: agentName } = agent;
|
|
418
|
+
// Attach error handler to all spawned children to prevent unhandled 'error' events from crashing the parent
|
|
419
|
+
const attachErrorHandler = (child) => {
|
|
420
|
+
child.on('error', (err) => {
|
|
421
|
+
// Log but don't crash — the agent simply failed to start
|
|
422
|
+
console.warn(` ⚠ Spawn error for ${agentName}/${personaName}: ${err.message}`);
|
|
423
|
+
});
|
|
424
|
+
};
|
|
425
|
+
if (agentName === 'codex') {
|
|
426
|
+
// Codex: use temp file via shell to avoid Windows .cmd ENOENT issues
|
|
427
|
+
const child = spawn('sh', ['-c', `cat "${briefFile}" | "${binaryPath}" exec --full-auto - ; rm -f "${briefFile}"`], {
|
|
428
|
+
detached: true,
|
|
429
|
+
stdio: 'ignore',
|
|
430
|
+
cwd,
|
|
431
|
+
});
|
|
432
|
+
attachErrorHandler(child);
|
|
433
|
+
child.unref();
|
|
434
|
+
}
|
|
435
|
+
else if (agentName === 'antigravity') {
|
|
436
|
+
// Gemini CLI: use temp file via shell redirection
|
|
437
|
+
const child = spawn('sh', ['-c', `"${binaryPath}" -p "$(cat "${briefFile}")" ; rm -f "${briefFile}"`], {
|
|
438
|
+
detached: true,
|
|
439
|
+
stdio: 'ignore',
|
|
440
|
+
cwd,
|
|
441
|
+
});
|
|
442
|
+
attachErrorHandler(child);
|
|
443
|
+
child.unref();
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
// Claude Code: use temp file via shell cat substitution
|
|
447
|
+
const child = spawn('sh', ['-c', `"${binaryPath}" -p "$(cat "${briefFile}")" --allowedTools "Read,Glob,Grep,Bash" ; rm -f "${briefFile}"`], {
|
|
448
|
+
detached: true,
|
|
449
|
+
stdio: 'ignore',
|
|
450
|
+
cwd,
|
|
451
|
+
});
|
|
452
|
+
attachErrorHandler(child);
|
|
453
|
+
child.unref();
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
// ── Polling helpers ──────────────────────────────────────────
|
|
457
|
+
function sleepSync(ms) {
|
|
458
|
+
if (process.platform === 'win32') {
|
|
459
|
+
spawnSync('powershell', ['-NonInteractive', '-Command', `Start-Sleep -Milliseconds ${ms}`], { stdio: 'ignore' });
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
spawnSync('sleep', [`${ms / 1000}`], { stdio: 'ignore' });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
function awaitThreadGrowth(threadId, baselineCount, expectedNew, cwd, timeoutMs = 300_000, intervalMs = 30_000) {
|
|
466
|
+
const target = baselineCount + expectedNew;
|
|
467
|
+
const start = Date.now();
|
|
468
|
+
while (Date.now() - start < timeoutMs) {
|
|
469
|
+
// Use lightweight count to avoid OOM from loading full messages each poll
|
|
470
|
+
const count = getThreadCount(threadId, cwd);
|
|
471
|
+
if (count >= target) {
|
|
472
|
+
return { received: count - baselineCount, timedOut: false };
|
|
473
|
+
}
|
|
474
|
+
sleepSync(intervalMs);
|
|
475
|
+
}
|
|
476
|
+
const count = getThreadCount(threadId, cwd);
|
|
477
|
+
return { received: count - baselineCount, timedOut: true };
|
|
478
|
+
}
|
|
479
|
+
// ── Exposition builder ────────────────────────────────────────
|
|
480
|
+
function buildExposition(topic, vision, relatedPlans, constraints, traps) {
|
|
481
|
+
const sections = [
|
|
482
|
+
`[CoDev] Topic for multi-perspective consultation:\n\n${topic}`,
|
|
483
|
+
];
|
|
484
|
+
if (vision) {
|
|
485
|
+
sections.push(`\n## Project Vision\n${vision}`);
|
|
486
|
+
}
|
|
487
|
+
if (relatedPlans.length > 0) {
|
|
488
|
+
const planLines = relatedPlans.map(p => `- ${p.text ?? p.id}`).join('\n');
|
|
489
|
+
sections.push(`\n## Related Plans\n${planLines}`);
|
|
490
|
+
}
|
|
491
|
+
if (constraints.length > 0) {
|
|
492
|
+
sections.push(`\n## Active Constraints\n${constraints.map(c => `- ${c}`).join('\n')}`);
|
|
493
|
+
}
|
|
494
|
+
if (traps.length > 0) {
|
|
495
|
+
sections.push(`\n## Relevant Traps\n${traps.map(t => `- ${t}`).join('\n')}`);
|
|
496
|
+
}
|
|
497
|
+
return sections.join('\n');
|
|
498
|
+
}
|
|
499
|
+
// ── Consultant brief builder ──────────────────────────────────
|
|
500
|
+
function buildConsultantBrief(persona, exposition, phase, threadHistory) {
|
|
501
|
+
const phaseInstruction = phase === 'clarification'
|
|
502
|
+
? 'CLARIFICATION: Ask 3-5 questions ONLY, no suggestions'
|
|
503
|
+
: 'CONSULTATION: Give concrete proposals';
|
|
504
|
+
// Limit thread history to avoid string explosion — only include last 5 messages, truncated
|
|
505
|
+
const MAX_HISTORY_MESSAGES = 5;
|
|
506
|
+
const MAX_HISTORY_CHARS = 8000;
|
|
507
|
+
const recentHistory = threadHistory.slice(-MAX_HISTORY_MESSAGES);
|
|
508
|
+
let historyText = recentHistory.map(m => (m.text ?? '').slice(0, 1500)).join('\n---\n');
|
|
509
|
+
if (historyText.length > MAX_HISTORY_CHARS) {
|
|
510
|
+
historyText = historyText.slice(0, MAX_HISTORY_CHARS) + '\n[...truncated]';
|
|
511
|
+
}
|
|
512
|
+
const historyBlock = recentHistory.length > 0
|
|
513
|
+
? `\n## Thread History (last ${recentHistory.length})\n${historyText}`
|
|
514
|
+
: '';
|
|
515
|
+
return [
|
|
516
|
+
`[Persona: ${persona.name}]`,
|
|
517
|
+
`Focus: ${persona.focus}`,
|
|
518
|
+
'',
|
|
519
|
+
`Phase: ${phaseInstruction}`,
|
|
520
|
+
'',
|
|
521
|
+
'## Context',
|
|
522
|
+
exposition,
|
|
523
|
+
historyBlock,
|
|
524
|
+
'',
|
|
525
|
+
phase === 'clarification'
|
|
526
|
+
? 'Ask 3-5 clarifying questions from your perspective. Do NOT provide suggestions yet.'
|
|
527
|
+
: 'Provide concrete, actionable proposals from your perspective in 3-5 key points.',
|
|
528
|
+
].join('\n');
|
|
529
|
+
}
|
|
530
|
+
// ── Facilitation contract builder ─────────────────────────────
|
|
531
|
+
function buildContract(topic, clarificationMessages, decisions, constraints, traps) {
|
|
532
|
+
const sections = [
|
|
533
|
+
`[Facilitator Contract] Answering clarification questions for: ${topic}`,
|
|
534
|
+
'',
|
|
535
|
+
'## Clarification Questions Received',
|
|
536
|
+
];
|
|
537
|
+
if (clarificationMessages.length > 0) {
|
|
538
|
+
for (const msg of clarificationMessages) {
|
|
539
|
+
const personaTag = msg.tags?.find(t => t.startsWith('persona:'));
|
|
540
|
+
const personaName = personaTag?.replace('persona:', '') ?? 'unknown';
|
|
541
|
+
const truncatedText = (msg.text ?? '').slice(0, 3000);
|
|
542
|
+
sections.push(`\n### ${personaName}\n${truncatedText}`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
sections.push('\n(No clarification questions received yet — consultants will ask during clarification phase)');
|
|
547
|
+
}
|
|
548
|
+
sections.push('\n## Project Context for Answers');
|
|
549
|
+
if (decisions.length > 0) {
|
|
550
|
+
sections.push(`\n### Decisions\n${decisions.map(d => `- ${d}`).join('\n')}`);
|
|
551
|
+
}
|
|
552
|
+
if (constraints.length > 0) {
|
|
553
|
+
sections.push(`\n### Constraints\n${constraints.map(c => `- ${c}`).join('\n')}`);
|
|
554
|
+
}
|
|
555
|
+
if (traps.length > 0) {
|
|
556
|
+
sections.push(`\n### Known Traps\n${traps.map(t => `- ${t}`).join('\n')}`);
|
|
557
|
+
}
|
|
558
|
+
sections.push('\n## Contract');
|
|
559
|
+
sections.push('Use the project context above to answer clarification questions.');
|
|
560
|
+
sections.push('Consultants may now proceed to the consultation phase with this context.');
|
|
561
|
+
return sections.join('\n');
|
|
562
|
+
}
|
|
563
|
+
// ── Synthesis builder ─────────────────────────────────────────
|
|
564
|
+
function buildSynthesis(topic, consultationMessages) {
|
|
565
|
+
const sections = [
|
|
566
|
+
`[Synthesis] Multi-perspective analysis: ${topic}`,
|
|
567
|
+
'',
|
|
568
|
+
];
|
|
569
|
+
if (consultationMessages.length > 0) {
|
|
570
|
+
sections.push('## Consultation Responses');
|
|
571
|
+
for (const msg of consultationMessages) {
|
|
572
|
+
const personaTag = msg.tags?.find(t => t.startsWith('persona:'));
|
|
573
|
+
const personaName = personaTag?.replace('persona:', '') ?? 'unknown';
|
|
574
|
+
const truncatedText = (msg.text ?? '').slice(0, 3000);
|
|
575
|
+
sections.push(`\n### ${personaName}\n${truncatedText}`);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
sections.push('\n## Synthesis Template');
|
|
579
|
+
sections.push('');
|
|
580
|
+
sections.push('### Consensus Points');
|
|
581
|
+
sections.push('(Points where 3+ perspectives agree)');
|
|
582
|
+
sections.push('');
|
|
583
|
+
sections.push('### Key Tensions / Dissent');
|
|
584
|
+
sections.push('(Where perspectives conflict)');
|
|
585
|
+
sections.push('');
|
|
586
|
+
sections.push('### Risks Identified');
|
|
587
|
+
sections.push('(Top risks surfaced across perspectives)');
|
|
588
|
+
sections.push('');
|
|
589
|
+
sections.push('### Recommended Action');
|
|
590
|
+
sections.push('(Concrete next step based on the analysis)');
|
|
591
|
+
return sections.join('\n');
|
|
592
|
+
}
|
|
593
|
+
// ── Coordinator prompt (v1 fallback, enhanced) ────────────────
|
|
594
|
+
function buildCoordinatorPrompt(threadId, personas, topic, checkpoint) {
|
|
595
|
+
const personaList = personas.map(p => `- **${p.name}**: ${p.focus}`).join('\n');
|
|
596
|
+
const checkpointBlock = checkpoint
|
|
597
|
+
? '\n4. PAUSE and present clarifying questions to the human before proceeding to steps 5-6.\n'
|
|
598
|
+
: '';
|
|
599
|
+
return [
|
|
600
|
+
'--- COORDINATOR PROMPT (copy to your LLM) ---',
|
|
601
|
+
'',
|
|
602
|
+
`You are the coordinator of a CoDev ideation session on thread ${threadId}.`,
|
|
603
|
+
`Topic: ${topic}`,
|
|
604
|
+
'',
|
|
605
|
+
'Participants:',
|
|
606
|
+
personaList,
|
|
607
|
+
'',
|
|
608
|
+
'Instructions:',
|
|
609
|
+
'1. Read the thread messages to understand each persona brief and the project context.',
|
|
610
|
+
'2. Role-play each persona in order. For each, write a reply with 3-5 specific points from that perspective.',
|
|
611
|
+
'3. After all personas have spoken, identify areas of agreement and tension.',
|
|
612
|
+
checkpoint ? checkpointBlock : '',
|
|
613
|
+
`${checkpoint ? '5' : '4'}. Synthesize into a structured output:`,
|
|
614
|
+
' - **Consensus**: points where 3+ personas agree',
|
|
615
|
+
' - **Key tensions**: where perspectives conflict',
|
|
616
|
+
' - **Risks**: top 3 risks surfaced',
|
|
617
|
+
' - **Recommended action**: concrete next step',
|
|
618
|
+
' - **Dissenting view**: the strongest counter-argument to the recommendation',
|
|
619
|
+
'',
|
|
620
|
+
'--- END COORDINATOR PROMPT ---',
|
|
621
|
+
].join('\n');
|
|
622
|
+
}
|
|
623
|
+
// ── codev metrics ─────────────────────────────────────────────
|
|
624
|
+
function printMetricsSummary(threadSlug, cwd) {
|
|
625
|
+
const summary = summarizeMetrics(threadSlug, cwd);
|
|
626
|
+
if (!Object.keys(summary.by_agent).length)
|
|
627
|
+
return;
|
|
628
|
+
const byRound = summarizeMetricsByRound(threadSlug, cwd);
|
|
629
|
+
console.log('\n── Response Metrics ──────────────────────────────');
|
|
630
|
+
console.log(` Overall avg: ${Math.round(summary.avg_ms / 1000)}s p95: ${Math.round(summary.p95_ms / 1000)}s`);
|
|
631
|
+
if (byRound.length > 0) {
|
|
632
|
+
console.log(' Per round:');
|
|
633
|
+
for (const r of byRound) {
|
|
634
|
+
console.log(` Round ${r.round}: avg=${Math.round(r.avg_ms / 1000)}s p95=${Math.round(r.p95_ms / 1000)}s responses=${r.count}`);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
for (const [agent, stats] of Object.entries(summary.by_agent)) {
|
|
638
|
+
console.log(` ${agent}: avg=${Math.round(stats.avg_ms / 1000)}s count=${stats.count}`);
|
|
639
|
+
}
|
|
640
|
+
console.log('');
|
|
641
|
+
}
|
|
642
|
+
export function runCodevMetrics(threadSlug, options = {}) {
|
|
643
|
+
if (!threadSlug) {
|
|
644
|
+
console.error('Error: <thread> is required. Usage: brainclaw codev-metrics <thread>');
|
|
645
|
+
process.exit(1);
|
|
646
|
+
}
|
|
647
|
+
const cwd = options.cwd ?? process.cwd();
|
|
648
|
+
const summary = summarizeMetrics(threadSlug, cwd);
|
|
649
|
+
const byRound = summarizeMetricsByRound(threadSlug, cwd);
|
|
650
|
+
if (options.json) {
|
|
651
|
+
console.log(JSON.stringify({ summary, by_round: byRound }, null, 2));
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (!Object.keys(summary.by_agent).length) {
|
|
655
|
+
console.log(`No metrics found for thread: ${threadSlug}`);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
console.log(`\nCoDev metrics for thread: ${threadSlug}\n`);
|
|
659
|
+
console.log(` Overall avg: ${Math.round(summary.avg_ms / 1000)}s p95: ${Math.round(summary.p95_ms / 1000)}s`);
|
|
660
|
+
if (byRound.length > 0) {
|
|
661
|
+
console.log('\n Per round:');
|
|
662
|
+
for (const r of byRound) {
|
|
663
|
+
console.log(` Round ${r.round}: avg=${Math.round(r.avg_ms / 1000)}s p95=${Math.round(r.p95_ms / 1000)}s responses=${r.count}`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
console.log('\n Per agent:');
|
|
667
|
+
for (const [agent, stats] of Object.entries(summary.by_agent)) {
|
|
668
|
+
console.log(` ${agent}: avg=${Math.round(stats.avg_ms / 1000)}s count=${stats.count}`);
|
|
669
|
+
}
|
|
670
|
+
console.log('');
|
|
671
|
+
}
|
|
672
|
+
//# sourceMappingURL=codev.js.map
|