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,35 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { listIdeationRounds } from './ideation.js';
|
|
4
|
+
export function generatePlansFromConvergence(threadSlug, cwd) {
|
|
5
|
+
const root = cwd ?? process.cwd();
|
|
6
|
+
const rounds = listIdeationRounds(threadSlug, root);
|
|
7
|
+
const lastRound = rounds[rounds.length - 1];
|
|
8
|
+
if (!lastRound?.convergences.length) {
|
|
9
|
+
return { plans: [], skipped: [] };
|
|
10
|
+
}
|
|
11
|
+
const plans = [];
|
|
12
|
+
const skipped = [];
|
|
13
|
+
for (const text of lastRound.convergences) {
|
|
14
|
+
try {
|
|
15
|
+
const out = execFileSync('node', [path.join(root, 'dist/cli.js'), 'plan', 'create', text, '--tag', 'codev', 'auto-generated', '--type', 'feat'], { cwd: root, encoding: 'utf-8' });
|
|
16
|
+
const id = /\[(pln_[^\]]+)\]/.exec(out)?.[1];
|
|
17
|
+
if (!id) {
|
|
18
|
+
skipped.push(text);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
plans.push({ id, text });
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
skipped.push(text);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { plans, skipped };
|
|
28
|
+
}
|
|
29
|
+
export function generateSummaryNote(threadSlug, result, cwd) {
|
|
30
|
+
const root = cwd ?? process.cwd();
|
|
31
|
+
const ids = result.plans.map((p) => p.id).join(', ') || 'none';
|
|
32
|
+
const note = `CoDev session ${threadSlug} generated ${result.plans.length} plan(s): ${ids}. Skipped: ${result.skipped.length}.`;
|
|
33
|
+
execFileSync('node', [path.join(root, 'dist/cli.js'), 'note', note], { cwd: root, encoding: 'utf-8' });
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=codev-plan-gen.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CoDev v3 prompt builder functions for iterative group discussion model.
|
|
3
|
+
* Prompts are intentionally short — agents should spend time thinking, not reading.
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
export function truncate(text, maxLength) {
|
|
7
|
+
if (text.length <= maxLength)
|
|
8
|
+
return text;
|
|
9
|
+
return text.slice(0, maxLength - 3) + '...';
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Round 0 — Each agent states their initial position on the topic.
|
|
13
|
+
*/
|
|
14
|
+
export function buildPositionPrompt(persona, exposition, targetDurationSeconds, responseFilePath) {
|
|
15
|
+
return `[CoDev Round 0 — Position initiale]
|
|
16
|
+
Tu es ${persona.name}: ${persona.focus}
|
|
17
|
+
|
|
18
|
+
## Sujet
|
|
19
|
+
${truncate(exposition, 800)}
|
|
20
|
+
|
|
21
|
+
## Consigne
|
|
22
|
+
Donne ta position en 3-5 phrases maximum. Sois direct et concret.
|
|
23
|
+
Durée cible: ${targetDurationSeconds}s.
|
|
24
|
+
|
|
25
|
+
## Format de réponse
|
|
26
|
+
Écris ta réponse dans le fichier: ${responseFilePath}
|
|
27
|
+
Format: JSON avec { "persona": "${persona.name}", "text": "<ta réponse>" }`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Rounds 1-N — Each agent reacts to the previous round's positions.
|
|
31
|
+
*/
|
|
32
|
+
export function buildReactionPrompt(persona, previousPositions, roundNumber, targetDurationSeconds, responseFilePath) {
|
|
33
|
+
const positionLines = previousPositions
|
|
34
|
+
.filter((p) => p.persona !== persona.name)
|
|
35
|
+
.map((p) => `**${p.persona}**: ${truncate(p.text, 200)}`)
|
|
36
|
+
.join('\n');
|
|
37
|
+
return `[CoDev Round ${roundNumber} — Réaction]
|
|
38
|
+
Tu es ${persona.name}: ${persona.focus}
|
|
39
|
+
|
|
40
|
+
## Positions précédentes
|
|
41
|
+
${positionLines}
|
|
42
|
+
|
|
43
|
+
## Consigne
|
|
44
|
+
Réagis aux positions ci-dessus depuis ton point de vue de ${persona.name}.
|
|
45
|
+
Challenge, renforce ou propose une alternative. 3-5 phrases.
|
|
46
|
+
Durée cible: ${targetDurationSeconds}s.
|
|
47
|
+
|
|
48
|
+
## Format de réponse
|
|
49
|
+
Écris ta réponse dans le fichier: ${responseFilePath}
|
|
50
|
+
Format: JSON avec { "persona": "${persona.name}", "text": "<ta réponse>" }`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Final round — Identify convergence and remaining disagreements.
|
|
54
|
+
*/
|
|
55
|
+
export function buildConvergencePrompt(persona, allPositions, targetDurationSeconds, responseFilePath) {
|
|
56
|
+
const positionLines = allPositions
|
|
57
|
+
.map((p) => `[R${p.round}] **${p.persona}**: ${truncate(p.text, 200)}`)
|
|
58
|
+
.join('\n');
|
|
59
|
+
return `[CoDev — Convergence]
|
|
60
|
+
Tu es ${persona.name}: ${persona.focus}
|
|
61
|
+
|
|
62
|
+
## Toutes les positions
|
|
63
|
+
${positionLines}
|
|
64
|
+
|
|
65
|
+
## Consigne
|
|
66
|
+
Identifie 3 points de convergence et 2 désaccords restants.
|
|
67
|
+
Propose des actions concrètes pour avancer. 3-5 phrases.
|
|
68
|
+
Durée cible: ${targetDurationSeconds}s.
|
|
69
|
+
|
|
70
|
+
## Format de réponse
|
|
71
|
+
Écris ta réponse dans le fichier: ${responseFilePath}
|
|
72
|
+
Format: JSON avec { "persona": "${persona.name}", "text": "<ta réponse>" }`;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=codev-prompts.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { memoryDir } from './io.js';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
function syncSleep(ms) {
|
|
5
|
+
if (ms <= 0)
|
|
6
|
+
return;
|
|
7
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
8
|
+
}
|
|
9
|
+
function sanitizeForPath(slug) {
|
|
10
|
+
return slug.replace(/[<>:"/\\|?*]/g, '_');
|
|
11
|
+
}
|
|
12
|
+
export function responseDir(threadSlug, cwd) {
|
|
13
|
+
return path.join(memoryDir(cwd), 'coordination', 'ideation', sanitizeForPath(threadSlug), 'responses');
|
|
14
|
+
}
|
|
15
|
+
export function responseFilePath(threadSlug, roundNumber, persona, cwd) {
|
|
16
|
+
return path.join(responseDir(threadSlug, cwd), `round_${roundNumber}_${persona}.json`);
|
|
17
|
+
}
|
|
18
|
+
export function writeResponse(threadSlug, roundNumber, persona, agent, text, cwd) {
|
|
19
|
+
const filePath = responseFilePath(threadSlug, roundNumber, persona, cwd);
|
|
20
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
21
|
+
const payload = { persona, agent, text, created_at: new Date().toISOString() };
|
|
22
|
+
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), 'utf8');
|
|
23
|
+
}
|
|
24
|
+
export function readResponse(threadSlug, roundNumber, persona, cwd) {
|
|
25
|
+
const filePath = responseFilePath(threadSlug, roundNumber, persona, cwd);
|
|
26
|
+
if (!fs.existsSync(filePath))
|
|
27
|
+
return undefined;
|
|
28
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
29
|
+
}
|
|
30
|
+
export function collectResponses(threadSlug, roundNumber, personas, cwd) {
|
|
31
|
+
const responses = [];
|
|
32
|
+
for (const persona of personas) {
|
|
33
|
+
const response = readResponse(threadSlug, roundNumber, persona, cwd);
|
|
34
|
+
if (response)
|
|
35
|
+
responses.push(response);
|
|
36
|
+
}
|
|
37
|
+
return responses;
|
|
38
|
+
}
|
|
39
|
+
export function awaitAllResponses(threadSlug, roundNumber, personas, cwd, pollIntervalMs = 10000) {
|
|
40
|
+
const startedAt = Date.now();
|
|
41
|
+
const pending = new Set(personas);
|
|
42
|
+
const received = new Map();
|
|
43
|
+
while (pending.size > 0) {
|
|
44
|
+
for (const persona of pending) {
|
|
45
|
+
const response = readResponse(threadSlug, roundNumber, persona, cwd);
|
|
46
|
+
if (response) {
|
|
47
|
+
received.set(persona, { ...response, duration_ms: Date.now() - startedAt });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const persona of received.keys()) {
|
|
51
|
+
pending.delete(persona);
|
|
52
|
+
}
|
|
53
|
+
if (pending.size === 0)
|
|
54
|
+
break;
|
|
55
|
+
console.log(`Waiting for responses: ${received.size}/${personas.length} received...`);
|
|
56
|
+
syncSleep(pollIntervalMs);
|
|
57
|
+
}
|
|
58
|
+
return personas
|
|
59
|
+
.map((persona) => received.get(persona))
|
|
60
|
+
.filter((response) => Boolean(response));
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=codev-responses.js.map
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CoDev v3 — Round protocol engine.
|
|
3
|
+
* Orchestrates prompt generation, agent spawning, response collection,
|
|
4
|
+
* metrics recording, and ideation round persistence.
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
import { saveIdeationRound, loadIdeationRound, listIdeationRounds } from './ideation.js';
|
|
8
|
+
import { recordResponse } from './codev-metrics.js';
|
|
9
|
+
import { buildPositionPrompt, buildReactionPrompt, buildConvergencePrompt } from './codev-prompts.js';
|
|
10
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import os from 'node:os';
|
|
14
|
+
// ── Helpers ───────────────────────────────────────────────────
|
|
15
|
+
function sanitizeForPath(slug) {
|
|
16
|
+
return slug.replace(/[<>:"/\\|?*]/g, '_');
|
|
17
|
+
}
|
|
18
|
+
function responseFilePath(threadSlug, roundNumber, personaName) {
|
|
19
|
+
return path.join(os.tmpdir(), 'brainclaw-codev', sanitizeForPath(threadSlug), `round_${roundNumber}`, `${personaName}.json`);
|
|
20
|
+
}
|
|
21
|
+
function sleepSync(ms) {
|
|
22
|
+
if (process.platform === 'win32') {
|
|
23
|
+
spawnSync('powershell', ['-NonInteractive', '-Command', `Start-Sleep -Milliseconds ${ms}`], { stdio: 'ignore' });
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
spawnSync('sleep', [`${ms / 1000}`], { stdio: 'ignore' });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function spawnAgent(binaryPath, agentName, prompt, personaName, cwd, outputFile, modelOverride) {
|
|
30
|
+
const tmpDir = path.join(os.tmpdir(), 'brainclaw-codev');
|
|
31
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
32
|
+
const promptFile = path.join(tmpDir, `${personaName}-${Date.now()}.md`);
|
|
33
|
+
fs.writeFileSync(promptFile, prompt, 'utf-8');
|
|
34
|
+
// Capture stdout → response file. The agent just responds normally;
|
|
35
|
+
// the facilitator writes its output to the expected response path.
|
|
36
|
+
fs.mkdirSync(path.dirname(outputFile), { recursive: true });
|
|
37
|
+
const outFd = fs.openSync(outputFile + '.partial', 'w');
|
|
38
|
+
// Build agent-specific command
|
|
39
|
+
let shellCmd;
|
|
40
|
+
if (agentName === 'codex') {
|
|
41
|
+
// Codex: reads from stdin via '-', uses exec --full-auto
|
|
42
|
+
shellCmd = `cat "${promptFile}" | "${binaryPath}" exec --full-auto - ; rm -f "${promptFile}"`;
|
|
43
|
+
}
|
|
44
|
+
else if (agentName === 'antigravity') {
|
|
45
|
+
// Gemini CLI: -p flag, optional -m for model
|
|
46
|
+
const modelFlag = modelOverride ? ` -m "${modelOverride}"` : '';
|
|
47
|
+
shellCmd = `"${binaryPath}"${modelFlag} -p "$(cat "${promptFile}")" ; rm -f "${promptFile}"`;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Claude Code and others: -p flag with --allowedTools, optional --model
|
|
51
|
+
const modelFlag = modelOverride ? ` --model "${modelOverride}"` : '';
|
|
52
|
+
shellCmd = `"${binaryPath}" -p "$(cat "${promptFile}")"${modelFlag} --allowedTools "Read,Glob,Grep,Bash" ; rm -f "${promptFile}"`;
|
|
53
|
+
}
|
|
54
|
+
const child = spawn('sh', ['-c', shellCmd], {
|
|
55
|
+
detached: true,
|
|
56
|
+
stdio: ['ignore', outFd, 'ignore'],
|
|
57
|
+
cwd,
|
|
58
|
+
});
|
|
59
|
+
child.on('error', (err) => {
|
|
60
|
+
console.warn(` ⚠ Spawn error for ${agentName}/${personaName}: ${err.message}`);
|
|
61
|
+
});
|
|
62
|
+
child.on('exit', () => {
|
|
63
|
+
try {
|
|
64
|
+
fs.closeSync(outFd);
|
|
65
|
+
}
|
|
66
|
+
catch { /* already closed */ }
|
|
67
|
+
// Rename .partial → .json to signal completion atomically
|
|
68
|
+
try {
|
|
69
|
+
const raw = fs.readFileSync(outputFile + '.partial', 'utf-8').trim();
|
|
70
|
+
const response = { persona: personaName, agent: agentName, text: raw, created_at: new Date().toISOString() };
|
|
71
|
+
fs.writeFileSync(outputFile, JSON.stringify(response, null, 2), 'utf-8');
|
|
72
|
+
}
|
|
73
|
+
catch { /* best effort */ }
|
|
74
|
+
});
|
|
75
|
+
child.unref();
|
|
76
|
+
}
|
|
77
|
+
// ── Main export ───────────────────────────────────────────────
|
|
78
|
+
export function executeRound(config) {
|
|
79
|
+
const { threadSlug, roundNumber, roundType, personas, agents, exposition, targetDurationSeconds, cwd, quorum, modelOverrides } = config;
|
|
80
|
+
const quorumTarget = quorum != null && quorum > 0 ? Math.min(quorum, personas.length) : personas.length;
|
|
81
|
+
const dispatchedAt = new Date();
|
|
82
|
+
const collected = [];
|
|
83
|
+
try {
|
|
84
|
+
// a. Generate prompts and spawn agents (round-robin)
|
|
85
|
+
for (let i = 0; i < personas.length; i++) {
|
|
86
|
+
const persona = personas[i];
|
|
87
|
+
const agent = agents[i % agents.length];
|
|
88
|
+
const respFile = responseFilePath(threadSlug, roundNumber, persona.name);
|
|
89
|
+
fs.mkdirSync(path.dirname(respFile), { recursive: true });
|
|
90
|
+
let prompt;
|
|
91
|
+
if (roundType === 'position') {
|
|
92
|
+
prompt = buildPositionPrompt(persona, exposition, targetDurationSeconds, respFile);
|
|
93
|
+
}
|
|
94
|
+
else if (roundType === 'reaction') {
|
|
95
|
+
const prev = loadIdeationRound(threadSlug, roundNumber - 1, cwd);
|
|
96
|
+
const previousPositions = (prev?.positions ?? []).map(p => ({ persona: p.persona, text: p.text }));
|
|
97
|
+
prompt = buildReactionPrompt(persona, previousPositions, roundNumber, targetDurationSeconds, respFile);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const allRounds = listIdeationRounds(threadSlug, cwd);
|
|
101
|
+
const allPositions = allRounds.flatMap(r => r.positions.map(p => ({ persona: p.persona, text: p.text, round: r.round_number })));
|
|
102
|
+
prompt = buildConvergencePrompt(persona, allPositions, targetDurationSeconds, respFile);
|
|
103
|
+
}
|
|
104
|
+
spawnAgent(agent.binaryPath, agent.name, prompt, persona.name, cwd, respFile, modelOverrides?.[persona.name]);
|
|
105
|
+
console.log(` → [R${roundNumber}] ${persona.name} → ${agent.name}${modelOverrides?.[persona.name] ? ` (model: ${modelOverrides[persona.name]})` : ''}`);
|
|
106
|
+
}
|
|
107
|
+
// b. Poll for responses — no hard timeout
|
|
108
|
+
// Check both .json (final) and .partial (stdout capture still in progress).
|
|
109
|
+
// A .partial is considered done when its size is >0 and stable across 2 polls.
|
|
110
|
+
const pending = new Set(personas.map(p => p.name));
|
|
111
|
+
const partialSizes = new Map();
|
|
112
|
+
if (quorumTarget < personas.length) {
|
|
113
|
+
console.log(`Polling for ${pending.size} response(s) (round ${roundNumber}, quorum=${quorumTarget})...`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.log(`Polling for ${pending.size} response(s) (round ${roundNumber})...`);
|
|
117
|
+
}
|
|
118
|
+
while (pending.size > 0) {
|
|
119
|
+
sleepSync(10_000);
|
|
120
|
+
for (const personaName of [...pending]) {
|
|
121
|
+
const respFile = responseFilePath(threadSlug, roundNumber, personaName);
|
|
122
|
+
// Check final .json first (written by exit handler)
|
|
123
|
+
let raw;
|
|
124
|
+
if (fs.existsSync(respFile)) {
|
|
125
|
+
try {
|
|
126
|
+
raw = JSON.parse(fs.readFileSync(respFile, 'utf-8'));
|
|
127
|
+
}
|
|
128
|
+
catch { /* not ready */ }
|
|
129
|
+
}
|
|
130
|
+
// Fallback: check .partial stdout capture — stable size means agent finished
|
|
131
|
+
if (!raw) {
|
|
132
|
+
const partialFile = respFile + '.partial';
|
|
133
|
+
if (!fs.existsSync(partialFile))
|
|
134
|
+
continue;
|
|
135
|
+
try {
|
|
136
|
+
const stat = fs.statSync(partialFile);
|
|
137
|
+
if (stat.size === 0)
|
|
138
|
+
continue;
|
|
139
|
+
const prevSize = partialSizes.get(personaName) ?? 0;
|
|
140
|
+
if (stat.size === prevSize && prevSize > 0) {
|
|
141
|
+
// Stable — agent is done writing, read it
|
|
142
|
+
const text = fs.readFileSync(partialFile, 'utf-8').trim();
|
|
143
|
+
if (text.length > 0) {
|
|
144
|
+
raw = { persona: personaName, text };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
partialSizes.set(personaName, stat.size);
|
|
148
|
+
if (!raw)
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const respondedAt = new Date();
|
|
157
|
+
const agentIdx = personas.findIndex(p => p.name === personaName);
|
|
158
|
+
const agentName = agents[agentIdx % agents.length].name;
|
|
159
|
+
const duration_ms = respondedAt.getTime() - dispatchedAt.getTime();
|
|
160
|
+
recordResponse(threadSlug, {
|
|
161
|
+
thread_id: threadSlug,
|
|
162
|
+
round: roundNumber,
|
|
163
|
+
persona: personaName,
|
|
164
|
+
agent_name: agentName,
|
|
165
|
+
dispatched_at: dispatchedAt.toISOString(),
|
|
166
|
+
responded_at: respondedAt.toISOString(),
|
|
167
|
+
duration_ms,
|
|
168
|
+
}, cwd);
|
|
169
|
+
collected.push({ persona: personaName, agent: agentName, text: raw.text, duration_ms });
|
|
170
|
+
pending.delete(personaName);
|
|
171
|
+
console.log(` ✓ ${personaName} responded (${Math.round(duration_ms / 1000)}s)`);
|
|
172
|
+
// Quorum: advance once enough responses have been collected
|
|
173
|
+
if (collected.length >= quorumTarget) {
|
|
174
|
+
if (pending.size > 0) {
|
|
175
|
+
console.log(` Quorum reached (${collected.length}/${personas.length}) — advancing round.`);
|
|
176
|
+
}
|
|
177
|
+
pending.clear();
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// File may be partially written — retry on next poll
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (pending.size > 0)
|
|
185
|
+
console.log(` Waiting for: ${[...pending].join(', ')}`);
|
|
186
|
+
}
|
|
187
|
+
// c. Build and persist the IdeationRound artifact
|
|
188
|
+
const round = {
|
|
189
|
+
schema_version: 1,
|
|
190
|
+
thread_id: threadSlug,
|
|
191
|
+
round_number: roundNumber,
|
|
192
|
+
round_type: roundType,
|
|
193
|
+
positions: collected,
|
|
194
|
+
tensions: [],
|
|
195
|
+
convergences: [],
|
|
196
|
+
created_at: new Date().toISOString(),
|
|
197
|
+
};
|
|
198
|
+
saveIdeationRound(threadSlug, round, cwd);
|
|
199
|
+
return round;
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
203
|
+
console.error(`Round ${roundNumber} failed: ${message}`);
|
|
204
|
+
const failedRound = {
|
|
205
|
+
schema_version: 1,
|
|
206
|
+
thread_id: threadSlug,
|
|
207
|
+
round_number: roundNumber,
|
|
208
|
+
round_type: roundType,
|
|
209
|
+
positions: collected,
|
|
210
|
+
tensions: [`Round ${roundNumber} failed: ${message}`],
|
|
211
|
+
convergences: [],
|
|
212
|
+
created_at: new Date().toISOString(),
|
|
213
|
+
};
|
|
214
|
+
saveIdeationRound(threadSlug, failedRound, cwd);
|
|
215
|
+
return failedRound;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=codev-rounds.js.map
|
package/dist/core/config.js
CHANGED
|
@@ -73,6 +73,10 @@ export function defaultConfig(projectName, options = {}) {
|
|
|
73
73
|
cross_project_links: [],
|
|
74
74
|
implicit_session_ttl: '4h',
|
|
75
75
|
auto_reflect_notes: false,
|
|
76
|
+
auto_refresh_live: true,
|
|
77
|
+
claims: {
|
|
78
|
+
auto_release_after_hours: 24,
|
|
79
|
+
},
|
|
76
80
|
};
|
|
77
81
|
}
|
|
78
82
|
export function loadConfig(cwd, preferredDirName) {
|