dual-brain 0.2.30 → 0.3.0
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/.dual-brain/docs/claude-code-extension-points.md +32 -0
- package/.dual-brain/docs/data-tools-capabilities.md +181 -0
- package/.dual-brain/docs/ecosystem-tools.md +91 -0
- package/.dual-brain/docs/panel-handoff.md +124 -0
- package/.dual-brain/docs/ruflo-analysis.md +48 -0
- package/bin/dual-brain.mjs +56 -56
- package/dist/mcp-server/index.d.ts +27 -0
- package/dist/mcp-server/index.js +359 -0
- package/dist/mcp-server/index.js.map +1 -0
- package/dist/src/agent-protocol.d.ts +163 -0
- package/dist/src/agent-protocol.js +368 -0
- package/dist/src/agent-protocol.js.map +1 -0
- package/dist/src/agents/registry.d.ts +52 -0
- package/dist/src/agents/registry.js +393 -0
- package/dist/src/agents/registry.js.map +1 -0
- package/dist/src/awareness.d.ts +93 -0
- package/dist/src/awareness.js +406 -0
- package/dist/src/awareness.js.map +1 -0
- package/dist/src/brief.d.ts +48 -0
- package/dist/src/brief.js +179 -0
- package/dist/src/brief.js.map +1 -0
- package/dist/src/calibration.d.ts +32 -0
- package/dist/src/calibration.js +133 -0
- package/dist/src/calibration.js.map +1 -0
- package/dist/src/checkpoint.d.ts +33 -0
- package/dist/src/checkpoint.js +99 -0
- package/dist/src/checkpoint.js.map +1 -0
- package/dist/src/ci-triage.d.ts +33 -0
- package/dist/src/ci-triage.js +193 -0
- package/dist/src/ci-triage.js.map +1 -0
- package/dist/src/cognitive-loop.d.ts +56 -0
- package/dist/src/cognitive-loop.js +495 -0
- package/dist/src/cognitive-loop.js.map +1 -0
- package/dist/src/collaboration.d.ts +147 -0
- package/dist/src/collaboration.js +438 -0
- package/dist/src/collaboration.js.map +1 -0
- package/dist/src/context-intel.d.ts +47 -0
- package/dist/src/context-intel.js +156 -0
- package/dist/src/context-intel.js.map +1 -0
- package/dist/src/context.d.ts +53 -0
- package/dist/src/context.js +332 -0
- package/dist/src/context.js.map +1 -0
- package/dist/src/continuity.d.ts +89 -0
- package/dist/src/continuity.js +230 -0
- package/dist/src/continuity.js.map +1 -0
- package/dist/src/cost-tracker.d.ts +47 -0
- package/dist/src/cost-tracker.js +170 -0
- package/dist/src/cost-tracker.js.map +1 -0
- package/dist/src/debrief.d.ts +53 -0
- package/dist/src/debrief.js +222 -0
- package/dist/src/debrief.js.map +1 -0
- package/dist/src/decide.d.ts +96 -0
- package/dist/src/decide.js +744 -0
- package/dist/src/decide.js.map +1 -0
- package/dist/src/decompose.d.ts +39 -0
- package/dist/src/decompose.js +218 -0
- package/dist/src/decompose.js.map +1 -0
- package/dist/src/detect.d.ts +91 -0
- package/dist/src/detect.js +544 -0
- package/dist/src/detect.js.map +1 -0
- package/dist/src/dispatch.d.ts +154 -0
- package/dist/src/dispatch.js +1306 -0
- package/dist/src/dispatch.js.map +1 -0
- package/dist/src/doctor.d.ts +421 -0
- package/dist/src/doctor.js +1689 -0
- package/dist/src/doctor.js.map +1 -0
- package/dist/src/engine.d.ts +70 -0
- package/dist/src/engine.js +155 -0
- package/dist/src/engine.js.map +1 -0
- package/dist/src/envelope.d.ts +36 -0
- package/dist/src/envelope.js +80 -0
- package/dist/src/envelope.js.map +1 -0
- package/dist/src/failure-memory.d.ts +55 -0
- package/dist/src/failure-memory.js +175 -0
- package/dist/src/failure-memory.js.map +1 -0
- package/dist/src/fx.d.ts +87 -0
- package/dist/src/fx.js +272 -0
- package/dist/src/fx.js.map +1 -0
- package/dist/src/governance.d.ts +93 -0
- package/dist/src/governance.js +261 -0
- package/dist/src/governance.js.map +1 -0
- package/dist/src/handoff.d.ts +11 -0
- package/dist/src/handoff.js +90 -0
- package/dist/src/handoff.js.map +1 -0
- package/dist/src/head-protocol.d.ts +76 -0
- package/dist/src/head-protocol.js +109 -0
- package/dist/src/head-protocol.js.map +1 -0
- package/dist/src/head.d.ts +222 -0
- package/dist/src/head.js +765 -0
- package/dist/src/head.js.map +1 -0
- package/dist/src/health.d.ts +132 -0
- package/dist/src/health.js +435 -0
- package/dist/src/health.js.map +1 -0
- package/dist/src/inbox.d.ts +70 -0
- package/dist/src/inbox.js +218 -0
- package/dist/src/inbox.js.map +1 -0
- package/dist/src/index.d.ts +33 -0
- package/dist/src/index.js +38 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/install-hooks.d.ts +13 -0
- package/dist/src/install-hooks.js +88 -0
- package/dist/src/install-hooks.js.map +1 -0
- package/dist/src/integrity.d.ts +59 -0
- package/dist/src/integrity.js +206 -0
- package/dist/src/integrity.js.map +1 -0
- package/dist/src/intelligence.d.ts +104 -0
- package/dist/src/intelligence.js +391 -0
- package/dist/src/intelligence.js.map +1 -0
- package/dist/src/ledger.d.ts +54 -0
- package/dist/src/ledger.js +179 -0
- package/dist/src/ledger.js.map +1 -0
- package/dist/src/living-docs.d.ts +14 -0
- package/dist/src/living-docs.js +197 -0
- package/dist/src/living-docs.js.map +1 -0
- package/dist/src/memory-tiers.d.ts +37 -0
- package/dist/src/memory-tiers.js +160 -0
- package/dist/src/memory-tiers.js.map +1 -0
- package/dist/src/model-profiles.d.ts +65 -0
- package/dist/src/model-profiles.js +568 -0
- package/dist/src/model-profiles.js.map +1 -0
- package/dist/src/models.d.ts +58 -0
- package/dist/src/models.js +327 -0
- package/dist/src/models.js.map +1 -0
- package/dist/src/narrative.d.ts +54 -0
- package/dist/src/narrative.js +163 -0
- package/dist/src/narrative.js.map +1 -0
- package/dist/src/nextstep.d.ts +16 -0
- package/dist/src/nextstep.js +103 -0
- package/dist/src/nextstep.js.map +1 -0
- package/dist/src/observer.d.ts +18 -0
- package/dist/src/observer.js +251 -0
- package/dist/src/observer.js.map +1 -0
- package/dist/src/outcome.d.ts +110 -0
- package/dist/src/outcome.js +377 -0
- package/dist/src/outcome.js.map +1 -0
- package/dist/src/pipeline.d.ts +167 -0
- package/dist/src/pipeline.js +1503 -0
- package/dist/src/pipeline.js.map +1 -0
- package/dist/src/playbook.d.ts +59 -0
- package/dist/src/playbook.js +238 -0
- package/dist/src/playbook.js.map +1 -0
- package/dist/src/pr-agent.d.ts +97 -0
- package/dist/src/pr-agent.js +195 -0
- package/dist/src/pr-agent.js.map +1 -0
- package/dist/src/predictive.d.ts +57 -0
- package/dist/src/predictive.js +230 -0
- package/dist/src/predictive.js.map +1 -0
- package/dist/src/profile.d.ts +294 -0
- package/dist/src/profile.js +1347 -0
- package/dist/src/profile.js.map +1 -0
- package/dist/src/prompt-audit.d.ts +22 -0
- package/dist/src/prompt-audit.js +194 -0
- package/dist/src/prompt-audit.js.map +1 -0
- package/dist/src/prompt-intel.d.ts +12 -0
- package/dist/src/prompt-intel.js +321 -0
- package/dist/src/prompt-intel.js.map +1 -0
- package/dist/src/provider-context.d.ts +121 -0
- package/dist/src/provider-context.js +222 -0
- package/dist/src/provider-context.js.map +1 -0
- package/dist/src/provider-manager.d.ts +92 -0
- package/dist/src/provider-manager.js +428 -0
- package/dist/src/provider-manager.js.map +1 -0
- package/dist/src/receipt.d.ts +87 -0
- package/dist/src/receipt.js +326 -0
- package/dist/src/receipt.js.map +1 -0
- package/dist/src/recommendations.d.ts +13 -0
- package/dist/src/recommendations.js +291 -0
- package/dist/src/recommendations.js.map +1 -0
- package/dist/src/redact.d.ts +15 -0
- package/dist/src/redact.js +129 -0
- package/dist/src/redact.js.map +1 -0
- package/dist/src/replit.d.ts +397 -0
- package/dist/src/replit.js +1160 -0
- package/dist/src/replit.js.map +1 -0
- package/dist/src/repo.d.ts +149 -0
- package/dist/src/repo.js +416 -0
- package/dist/src/repo.js.map +1 -0
- package/dist/src/revert.d.ts +30 -0
- package/dist/src/revert.js +166 -0
- package/dist/src/revert.js.map +1 -0
- package/dist/src/room.d.ts +102 -0
- package/dist/src/room.js +212 -0
- package/dist/src/room.js.map +1 -0
- package/dist/src/routing-advisor.d.ts +57 -0
- package/dist/src/routing-advisor.js +221 -0
- package/dist/src/routing-advisor.js.map +1 -0
- package/dist/src/self-correct.d.ts +40 -0
- package/dist/src/self-correct.js +137 -0
- package/dist/src/self-correct.js.map +1 -0
- package/dist/src/session-lock.d.ts +35 -0
- package/dist/src/session-lock.js +134 -0
- package/dist/src/session-lock.js.map +1 -0
- package/dist/src/session.d.ts +267 -0
- package/dist/src/session.js +1660 -0
- package/dist/src/session.js.map +1 -0
- package/dist/src/settings-tui.d.ts +5 -0
- package/dist/src/settings-tui.js +422 -0
- package/dist/src/settings-tui.js.map +1 -0
- package/dist/src/setup-flow.d.ts +63 -0
- package/dist/src/setup-flow.js +233 -0
- package/dist/src/setup-flow.js.map +1 -0
- package/dist/src/signal.d.ts +19 -0
- package/dist/src/signal.js +122 -0
- package/dist/src/signal.js.map +1 -0
- package/dist/src/simmer.d.ts +85 -0
- package/dist/src/simmer.js +224 -0
- package/dist/src/simmer.js.map +1 -0
- package/dist/src/state-export.d.ts +129 -0
- package/dist/src/state-export.js +233 -0
- package/dist/src/state-export.js.map +1 -0
- package/dist/src/strategy.d.ts +54 -0
- package/dist/src/strategy.js +95 -0
- package/dist/src/strategy.js.map +1 -0
- package/dist/src/subscription.d.ts +40 -0
- package/dist/src/subscription.js +189 -0
- package/dist/src/subscription.js.map +1 -0
- package/dist/src/templates.d.ts +208 -0
- package/dist/src/templates.js +238 -0
- package/dist/src/templates.js.map +1 -0
- package/dist/src/test.d.ts +9 -0
- package/dist/src/test.js +1173 -0
- package/dist/src/test.js.map +1 -0
- package/dist/src/think-engine.d.ts +67 -0
- package/dist/src/think-engine.js +412 -0
- package/dist/src/think-engine.js.map +1 -0
- package/dist/src/tui.d.ts +71 -0
- package/dist/src/tui.js +242 -0
- package/dist/src/tui.js.map +1 -0
- package/dist/src/types.d.ts +177 -0
- package/dist/src/types.js +6 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/update-check.d.ts +7 -0
- package/dist/src/update-check.js +36 -0
- package/dist/src/update-check.js.map +1 -0
- package/dist/src/wave-planner.d.ts +30 -0
- package/dist/src/wave-planner.js +281 -0
- package/dist/src/wave-planner.js.map +1 -0
- package/hooks/head-guard.sh +41 -0
- package/hooks/task-classifier.mjs +328 -0
- package/hooks/vibe-router.mjs +387 -0
- package/package.json +29 -153
- package/src/agents/registry.mjs +0 -405
- package/src/awareness.mjs +0 -425
- package/src/brief.mjs +0 -266
- package/src/calibration.mjs +0 -148
- package/src/checkpoint.mjs +0 -109
- package/src/ci-triage.mjs +0 -191
- package/src/cognitive-loop.mjs +0 -562
- package/src/collaboration.mjs +0 -545
- package/src/context-intel.mjs +0 -158
- package/src/context.mjs +0 -389
- package/src/continuity.mjs +0 -298
- package/src/cost-tracker.mjs +0 -184
- package/src/debrief.mjs +0 -228
- package/src/decide.mjs +0 -1099
- package/src/decompose.mjs +0 -331
- package/src/detect.mjs +0 -702
- package/src/dispatch.mjs +0 -1447
- package/src/doctor.mjs +0 -1607
- package/src/envelope.mjs +0 -139
- package/src/failure-memory.mjs +0 -178
- package/src/fx.mjs +0 -276
- package/src/governance.mjs +0 -279
- package/src/handoff.mjs +0 -87
- package/src/head-protocol.mjs +0 -128
- package/src/head.mjs +0 -952
- package/src/health.mjs +0 -528
- package/src/inbox.mjs +0 -195
- package/src/index.mjs +0 -44
- package/src/install-hooks.mjs +0 -100
- package/src/integrity.mjs +0 -245
- package/src/intelligence.mjs +0 -447
- package/src/ledger.mjs +0 -196
- package/src/living-docs.mjs +0 -210
- package/src/memory-tiers.mjs +0 -193
- package/src/models.mjs +0 -363
- package/src/narrative.mjs +0 -169
- package/src/nextstep.mjs +0 -100
- package/src/observer.mjs +0 -241
- package/src/outcome.mjs +0 -400
- package/src/pipeline.mjs +0 -1711
- package/src/playbook.mjs +0 -257
- package/src/pr-agent.mjs +0 -214
- package/src/predictive.mjs +0 -250
- package/src/profile.mjs +0 -1411
- package/src/prompt-audit.mjs +0 -231
- package/src/prompt-intel.mjs +0 -325
- package/src/provider-context.mjs +0 -257
- package/src/receipt.mjs +0 -344
- package/src/recommendations.mjs +0 -296
- package/src/redact.mjs +0 -192
- package/src/replit.mjs +0 -1210
- package/src/repo.mjs +0 -445
- package/src/revert.mjs +0 -149
- package/src/routing-advisor.mjs +0 -204
- package/src/self-correct.mjs +0 -147
- package/src/session-lock.mjs +0 -160
- package/src/session.mjs +0 -1655
- package/src/settings-tui.mjs +0 -373
- package/src/setup-flow.mjs +0 -223
- package/src/signal.mjs +0 -115
- package/src/simmer.mjs +0 -241
- package/src/strategy.mjs +0 -235
- package/src/subscription.mjs +0 -212
- package/src/templates.mjs +0 -260
- package/src/think-engine.mjs +0 -428
- package/src/tui.mjs +0 -276
- package/src/update-check.mjs +0 -35
- package/src/wave-planner.mjs +0 -294
package/src/collaboration.mjs
DELETED
|
@@ -1,545 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
// ── Blackboard: shared state across collaborating agents ────────────────────
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Create a fresh collaboration session.
|
|
8
|
-
* All agents in a multi-agent task share this blackboard.
|
|
9
|
-
*/
|
|
10
|
-
export function createSession(taskId, objective, opts = {}) {
|
|
11
|
-
return {
|
|
12
|
-
id: taskId || Date.now().toString(36) + Math.random().toString(36).slice(2, 5),
|
|
13
|
-
objective,
|
|
14
|
-
created: Date.now(),
|
|
15
|
-
status: 'active',
|
|
16
|
-
|
|
17
|
-
// Shared knowledge — agents write findings here, others read them
|
|
18
|
-
blackboard: {
|
|
19
|
-
findings: [], // { agentId, type, content, confidence, timestamp }
|
|
20
|
-
files: new Set(), // files discovered or changed (serialized as array)
|
|
21
|
-
decisions: [], // { agentId, decision, rationale, timestamp }
|
|
22
|
-
warnings: [], // { agentId, severity, message, timestamp }
|
|
23
|
-
context: {}, // arbitrary key-value context any agent can set
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
// Agent tracking
|
|
27
|
-
agents: [], // { id, role, provider, model, status, startedAt, completedAt, result }
|
|
28
|
-
|
|
29
|
-
// Event log — HEAD reads this to know what happened
|
|
30
|
-
events: [], // { type, agentId, data, timestamp }
|
|
31
|
-
|
|
32
|
-
// Chain configuration
|
|
33
|
-
chain: opts.chain || null, // ordered list of stages if chained execution
|
|
34
|
-
currentStage: 0,
|
|
35
|
-
|
|
36
|
-
// Cross-review config
|
|
37
|
-
crossReview: opts.crossReview ?? false,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// ── Blackboard operations ───────────────────────────────────────────────────
|
|
42
|
-
|
|
43
|
-
export function addFinding(session, agentId, type, content, confidence = 0.8) {
|
|
44
|
-
session.blackboard.findings.push({
|
|
45
|
-
agentId, type, content, confidence, timestamp: Date.now(),
|
|
46
|
-
});
|
|
47
|
-
_emitEvent(session, 'finding', agentId, { type, content, confidence });
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function addDecision(session, agentId, decision, rationale) {
|
|
51
|
-
session.blackboard.decisions.push({
|
|
52
|
-
agentId, decision, rationale, timestamp: Date.now(),
|
|
53
|
-
});
|
|
54
|
-
_emitEvent(session, 'decision', agentId, { decision, rationale });
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export function addWarning(session, agentId, severity, message) {
|
|
58
|
-
session.blackboard.warnings.push({
|
|
59
|
-
agentId, severity, message, timestamp: Date.now(),
|
|
60
|
-
});
|
|
61
|
-
_emitEvent(session, 'warning', agentId, { severity, message });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function setContext(session, key, value, agentId = 'head') {
|
|
65
|
-
session.blackboard.context[key] = value;
|
|
66
|
-
_emitEvent(session, 'context-set', agentId, { key });
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function trackFile(session, filePath, agentId) {
|
|
70
|
-
if (typeof session.blackboard.files === 'object' && session.blackboard.files instanceof Set) {
|
|
71
|
-
session.blackboard.files.add(filePath);
|
|
72
|
-
} else {
|
|
73
|
-
if (!Array.isArray(session.blackboard.files)) session.blackboard.files = [];
|
|
74
|
-
if (!session.blackboard.files.includes(filePath)) session.blackboard.files.push(filePath);
|
|
75
|
-
}
|
|
76
|
-
_emitEvent(session, 'file-tracked', agentId, { filePath });
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ── Agent lifecycle ─────────────────────────────────────────────────────────
|
|
80
|
-
|
|
81
|
-
export function registerAgent(session, agentId, role, provider, model) {
|
|
82
|
-
const agent = {
|
|
83
|
-
id: agentId,
|
|
84
|
-
role,
|
|
85
|
-
provider,
|
|
86
|
-
model,
|
|
87
|
-
status: 'registered',
|
|
88
|
-
startedAt: null,
|
|
89
|
-
completedAt: null,
|
|
90
|
-
result: null,
|
|
91
|
-
summary: null,
|
|
92
|
-
};
|
|
93
|
-
session.agents.push(agent);
|
|
94
|
-
_emitEvent(session, 'agent-registered', agentId, { role, provider, model });
|
|
95
|
-
return agent;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function startAgent(session, agentId) {
|
|
99
|
-
const agent = session.agents.find(a => a.id === agentId);
|
|
100
|
-
if (agent) {
|
|
101
|
-
agent.status = 'running';
|
|
102
|
-
agent.startedAt = Date.now();
|
|
103
|
-
_emitEvent(session, 'agent-started', agentId, {});
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function completeAgent(session, agentId, result, summary) {
|
|
108
|
-
const agent = session.agents.find(a => a.id === agentId);
|
|
109
|
-
if (agent) {
|
|
110
|
-
agent.status = result?.error ? 'failed' : 'completed';
|
|
111
|
-
agent.completedAt = Date.now();
|
|
112
|
-
agent.result = result;
|
|
113
|
-
agent.summary = summary || _extractSummary(result);
|
|
114
|
-
_emitEvent(session, 'agent-completed', agentId, {
|
|
115
|
-
status: agent.status,
|
|
116
|
-
durationMs: agent.completedAt - agent.startedAt,
|
|
117
|
-
summary: agent.summary,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ── Context builder: what an agent sees from prior agents ───────────────────
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Build a context injection string for the next agent in the collaboration.
|
|
126
|
-
* Contains: blackboard findings, decisions, warnings, and prior agent summaries.
|
|
127
|
-
* Token-budgeted to stay compact.
|
|
128
|
-
*/
|
|
129
|
-
export function buildAgentContext(session, forAgentId, maxTokens = 2000) {
|
|
130
|
-
const lines = [];
|
|
131
|
-
const charBudget = maxTokens * 4;
|
|
132
|
-
|
|
133
|
-
lines.push('[COLLABORATION CONTEXT]');
|
|
134
|
-
|
|
135
|
-
// Prior agent summaries (most valuable — what others already did)
|
|
136
|
-
const completedAgents = session.agents.filter(a => a.status === 'completed' && a.id !== forAgentId);
|
|
137
|
-
if (completedAgents.length > 0) {
|
|
138
|
-
lines.push('');
|
|
139
|
-
lines.push('Prior work:');
|
|
140
|
-
for (const a of completedAgents) {
|
|
141
|
-
const duration = a.completedAt - a.startedAt;
|
|
142
|
-
const durationLabel = duration > 60000 ? `${Math.round(duration / 60000)}m` : `${Math.round(duration / 1000)}s`;
|
|
143
|
-
lines.push(`- ${a.role} (${a.provider}/${a.model}, ${durationLabel}): ${(a.summary || 'completed').slice(0, 200)}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Key findings (high confidence first)
|
|
148
|
-
const findings = [...session.blackboard.findings]
|
|
149
|
-
.sort((a, b) => b.confidence - a.confidence)
|
|
150
|
-
.slice(0, 10);
|
|
151
|
-
if (findings.length > 0) {
|
|
152
|
-
lines.push('');
|
|
153
|
-
lines.push('Findings:');
|
|
154
|
-
for (const f of findings) {
|
|
155
|
-
lines.push(`- [${f.type}] ${f.content.slice(0, 150)}`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Decisions made
|
|
160
|
-
if (session.blackboard.decisions.length > 0) {
|
|
161
|
-
lines.push('');
|
|
162
|
-
lines.push('Decisions:');
|
|
163
|
-
for (const d of session.blackboard.decisions.slice(-5)) {
|
|
164
|
-
lines.push(`- ${d.decision}: ${d.rationale.slice(0, 100)}`);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Active warnings
|
|
169
|
-
const activeWarnings = session.blackboard.warnings.filter(w => w.severity === 'high' || w.severity === 'critical');
|
|
170
|
-
if (activeWarnings.length > 0) {
|
|
171
|
-
lines.push('');
|
|
172
|
-
lines.push('Warnings:');
|
|
173
|
-
for (const w of activeWarnings) {
|
|
174
|
-
lines.push(`- [${w.severity}] ${w.message.slice(0, 120)}`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Files touched
|
|
179
|
-
const files = session.blackboard.files instanceof Set
|
|
180
|
-
? [...session.blackboard.files]
|
|
181
|
-
: (Array.isArray(session.blackboard.files) ? session.blackboard.files : []);
|
|
182
|
-
if (files.length > 0) {
|
|
183
|
-
lines.push('');
|
|
184
|
-
lines.push(`Files in play: ${files.slice(0, 15).join(', ')}${files.length > 15 ? ` (+${files.length - 15} more)` : ''}`);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
lines.push('[/COLLABORATION CONTEXT]');
|
|
188
|
-
|
|
189
|
-
let result = lines.join('\n');
|
|
190
|
-
if (result.length > charBudget) {
|
|
191
|
-
result = result.slice(0, charBudget - 20) + '\n[...truncated]';
|
|
192
|
-
}
|
|
193
|
-
return result;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// ── Chain execution: ordered multi-stage pipelines ──────────────────────────
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Define a chain of agent stages.
|
|
200
|
-
* Each stage runs after the previous completes, with full blackboard access.
|
|
201
|
-
*
|
|
202
|
-
* @param {Array<{ role: string, tier: string, promptTemplate: Function, provider?: string, model?: string }>} stages
|
|
203
|
-
*/
|
|
204
|
-
export function defineChain(stages) {
|
|
205
|
-
return stages.map((s, i) => ({
|
|
206
|
-
index: i,
|
|
207
|
-
role: s.role,
|
|
208
|
-
tier: s.tier || 'execute',
|
|
209
|
-
promptTemplate: s.promptTemplate,
|
|
210
|
-
provider: s.provider || 'claude',
|
|
211
|
-
model: s.model || null,
|
|
212
|
-
dependsOn: i > 0 ? [i - 1] : [],
|
|
213
|
-
}));
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Get the next stage to execute in a chain.
|
|
218
|
-
* Returns null when all stages are complete or if dependencies aren't met.
|
|
219
|
-
*/
|
|
220
|
-
export function getNextStage(session) {
|
|
221
|
-
if (!session.chain) return null;
|
|
222
|
-
|
|
223
|
-
const stage = session.chain[session.currentStage];
|
|
224
|
-
if (!stage) return null;
|
|
225
|
-
|
|
226
|
-
// Check dependencies
|
|
227
|
-
for (const depIdx of stage.dependsOn || []) {
|
|
228
|
-
const depAgent = session.agents.find(a => a.role === session.chain[depIdx]?.role);
|
|
229
|
-
if (!depAgent || depAgent.status !== 'completed') return null;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return stage;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Advance the chain to the next stage.
|
|
237
|
-
*/
|
|
238
|
-
export function advanceChain(session) {
|
|
239
|
-
session.currentStage++;
|
|
240
|
-
return session.currentStage < (session.chain?.length || 0);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* Build the prompt for a chain stage, injecting collaboration context.
|
|
245
|
-
*/
|
|
246
|
-
export function buildChainPrompt(session, stage) {
|
|
247
|
-
const context = buildAgentContext(session, `chain-${stage.index}`);
|
|
248
|
-
const basePrompt = stage.promptTemplate(session);
|
|
249
|
-
return `${context}\n\n${basePrompt}`;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// ── Cross-review: opposite provider reviews the work ────────────────────────
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Build a cross-review prompt for an agent's output.
|
|
256
|
-
* Symmetric: works in both directions (Claude→OpenAI and OpenAI→Claude).
|
|
257
|
-
* Falls back to same-provider review with a different model if the opposite
|
|
258
|
-
* provider isn't available.
|
|
259
|
-
*
|
|
260
|
-
* @param {object} session
|
|
261
|
-
* @param {string} agentId
|
|
262
|
-
* @param {string[]} [availableProviders] Which providers are online
|
|
263
|
-
*/
|
|
264
|
-
export function buildCrossReviewPrompt(session, agentId, availableProviders) {
|
|
265
|
-
const agent = session.agents.find(a => a.id === agentId);
|
|
266
|
-
if (!agent || !agent.result) return null;
|
|
267
|
-
|
|
268
|
-
// Symmetric provider swap — respects availability
|
|
269
|
-
const opposite = agent.provider === 'claude' ? 'openai' : 'claude';
|
|
270
|
-
const reviewProvider = (!availableProviders || availableProviders.includes(opposite))
|
|
271
|
-
? opposite
|
|
272
|
-
: agent.provider;
|
|
273
|
-
|
|
274
|
-
// When same-provider review, use a different model tier
|
|
275
|
-
const sameProvider = reviewProvider === agent.provider;
|
|
276
|
-
const reviewModel = sameProvider
|
|
277
|
-
? (agent.model === 'opus' ? 'sonnet' : 'opus')
|
|
278
|
-
: null;
|
|
279
|
-
|
|
280
|
-
const prompt = [
|
|
281
|
-
`Review the following work by ${agent.provider}/${agent.model} (${agent.role}):`,
|
|
282
|
-
'',
|
|
283
|
-
`Objective: ${session.objective}`,
|
|
284
|
-
'',
|
|
285
|
-
`Result summary: ${(agent.summary || '').slice(0, 500)}`,
|
|
286
|
-
'',
|
|
287
|
-
'Check for:',
|
|
288
|
-
'- Correctness: does the output match the objective?',
|
|
289
|
-
'- Missed edge cases or risks',
|
|
290
|
-
'- Anything the next agent should know',
|
|
291
|
-
'',
|
|
292
|
-
'Be concise. Return: assessment (pass/flag/fail), key concerns, and suggestions.',
|
|
293
|
-
sameProvider ? '\nNote: You are reviewing work done by the same provider but a different model. Be especially critical.' : '',
|
|
294
|
-
].join('\n');
|
|
295
|
-
|
|
296
|
-
return { prompt, provider: reviewProvider, model: reviewModel, tier: 'search' };
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// ── HEAD observation: synthesize what happened ──────────────────────────────
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* Generate a compact summary of the collaboration session for HEAD.
|
|
303
|
-
* HEAD uses this to understand what happened without reading raw outputs.
|
|
304
|
-
*/
|
|
305
|
-
export function synthesize(session) {
|
|
306
|
-
const completed = session.agents.filter(a => a.status === 'completed');
|
|
307
|
-
const failed = session.agents.filter(a => a.status === 'failed');
|
|
308
|
-
const running = session.agents.filter(a => a.status === 'running');
|
|
309
|
-
|
|
310
|
-
const totalDuration = completed.reduce((sum, a) => sum + (a.completedAt - a.startedAt), 0);
|
|
311
|
-
|
|
312
|
-
const files = session.blackboard.files instanceof Set
|
|
313
|
-
? [...session.blackboard.files]
|
|
314
|
-
: (Array.isArray(session.blackboard.files) ? session.blackboard.files : []);
|
|
315
|
-
|
|
316
|
-
return {
|
|
317
|
-
sessionId: session.id,
|
|
318
|
-
objective: session.objective,
|
|
319
|
-
status: failed.length > 0 ? 'partial' : running.length > 0 ? 'in-progress' : 'complete',
|
|
320
|
-
agents: {
|
|
321
|
-
total: session.agents.length,
|
|
322
|
-
completed: completed.length,
|
|
323
|
-
failed: failed.length,
|
|
324
|
-
running: running.length,
|
|
325
|
-
},
|
|
326
|
-
summaries: completed.map(a => ({
|
|
327
|
-
role: a.role,
|
|
328
|
-
provider: a.provider,
|
|
329
|
-
model: a.model,
|
|
330
|
-
summary: a.summary,
|
|
331
|
-
durationMs: a.completedAt - a.startedAt,
|
|
332
|
-
})),
|
|
333
|
-
findings: session.blackboard.findings.length,
|
|
334
|
-
decisions: session.blackboard.decisions,
|
|
335
|
-
warnings: session.blackboard.warnings.filter(w => w.severity !== 'low'),
|
|
336
|
-
filesAffected: files,
|
|
337
|
-
totalDurationMs: totalDuration,
|
|
338
|
-
eventCount: session.events.length,
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// ── Preset collaboration patterns ───────────────────────────────────────────
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Plan-Code-Review: the Devin-style self-review loop.
|
|
346
|
-
* 1. Plan agent outlines the approach
|
|
347
|
-
* 2. Code agent implements
|
|
348
|
-
* 3. Review agent checks the work
|
|
349
|
-
* 4. If review fails, code agent gets another pass with review feedback
|
|
350
|
-
*/
|
|
351
|
-
export function planCodeReviewChain(objective, scope, opts = {}) {
|
|
352
|
-
return defineChain([
|
|
353
|
-
{
|
|
354
|
-
role: 'planner',
|
|
355
|
-
tier: 'think',
|
|
356
|
-
provider: opts.planProvider || 'claude',
|
|
357
|
-
model: opts.planModel || 'opus',
|
|
358
|
-
promptTemplate: (session) => {
|
|
359
|
-
return [
|
|
360
|
-
`Plan the implementation for: ${objective}`,
|
|
361
|
-
'',
|
|
362
|
-
`Scope: ${scope.join(', ')}`,
|
|
363
|
-
'',
|
|
364
|
-
'Return: step-by-step plan, files to modify, risks, and acceptance criteria.',
|
|
365
|
-
'Do NOT implement — only plan.',
|
|
366
|
-
].join('\n');
|
|
367
|
-
},
|
|
368
|
-
},
|
|
369
|
-
{
|
|
370
|
-
role: 'implementer',
|
|
371
|
-
tier: 'execute',
|
|
372
|
-
provider: opts.codeProvider || 'claude',
|
|
373
|
-
model: opts.codeModel || 'sonnet',
|
|
374
|
-
promptTemplate: (session) => {
|
|
375
|
-
const planAgent = session.agents.find(a => a.role === 'planner');
|
|
376
|
-
const plan = planAgent?.summary || 'No plan available — use best judgment.';
|
|
377
|
-
return [
|
|
378
|
-
`Implement: ${objective}`,
|
|
379
|
-
'',
|
|
380
|
-
`Plan: ${plan}`,
|
|
381
|
-
'',
|
|
382
|
-
`Scope: ${scope.join(', ')}`,
|
|
383
|
-
'',
|
|
384
|
-
'Follow the plan exactly. Report files changed and tests run.',
|
|
385
|
-
].join('\n');
|
|
386
|
-
},
|
|
387
|
-
},
|
|
388
|
-
{
|
|
389
|
-
role: 'reviewer',
|
|
390
|
-
tier: 'review',
|
|
391
|
-
provider: opts.reviewProvider || (opts.codeProvider === 'claude' ? 'openai' : 'claude'),
|
|
392
|
-
model: opts.reviewModel || null,
|
|
393
|
-
promptTemplate: (session) => {
|
|
394
|
-
const implAgent = session.agents.find(a => a.role === 'implementer');
|
|
395
|
-
return [
|
|
396
|
-
`Review the implementation of: ${objective}`,
|
|
397
|
-
'',
|
|
398
|
-
`What was done: ${implAgent?.summary || 'unknown'}`,
|
|
399
|
-
'',
|
|
400
|
-
`Scope: ${scope.join(', ')}`,
|
|
401
|
-
'',
|
|
402
|
-
'Check: correctness, edge cases, security, test coverage, architectural drift.',
|
|
403
|
-
'Return: pass/fail, findings with severity, and fixes needed.',
|
|
404
|
-
].join('\n');
|
|
405
|
-
},
|
|
406
|
-
},
|
|
407
|
-
]);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Research-Synthesize: multiple agents research in parallel, one synthesizes.
|
|
412
|
-
*/
|
|
413
|
-
export function researchSynthesizePattern(question, sources, opts = {}) {
|
|
414
|
-
const researchStages = sources.map((source, i) => ({
|
|
415
|
-
role: `researcher-${i}`,
|
|
416
|
-
tier: 'search',
|
|
417
|
-
provider: i % 2 === 0 ? 'claude' : (opts.altProvider || 'claude'),
|
|
418
|
-
model: opts.researchModel || 'haiku',
|
|
419
|
-
promptTemplate: () => `Research: ${question}\nFocus on: ${source}\nReturn: key findings, file references, confidence level.`,
|
|
420
|
-
}));
|
|
421
|
-
|
|
422
|
-
return defineChain([
|
|
423
|
-
...researchStages,
|
|
424
|
-
{
|
|
425
|
-
role: 'synthesizer',
|
|
426
|
-
tier: 'think',
|
|
427
|
-
provider: opts.synthProvider || 'claude',
|
|
428
|
-
model: opts.synthModel || 'sonnet',
|
|
429
|
-
promptTemplate: (session) => {
|
|
430
|
-
const researchFindings = session.agents
|
|
431
|
-
.filter(a => a.role.startsWith('researcher-') && a.status === 'completed')
|
|
432
|
-
.map(a => `[${a.role}]: ${a.summary || 'no findings'}`)
|
|
433
|
-
.join('\n');
|
|
434
|
-
return [
|
|
435
|
-
`Synthesize research on: ${question}`,
|
|
436
|
-
'',
|
|
437
|
-
'Research findings:',
|
|
438
|
-
researchFindings,
|
|
439
|
-
'',
|
|
440
|
-
'Combine findings into a coherent answer. Note disagreements between sources.',
|
|
441
|
-
'Return: synthesis, confidence level, remaining unknowns.',
|
|
442
|
-
].join('\n');
|
|
443
|
-
},
|
|
444
|
-
},
|
|
445
|
-
]);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* Dual-Review: two providers independently review, then a third reconciles.
|
|
450
|
-
*/
|
|
451
|
-
export function dualReviewPattern(files, context, opts = {}) {
|
|
452
|
-
return defineChain([
|
|
453
|
-
{
|
|
454
|
-
role: 'reviewer-claude',
|
|
455
|
-
tier: 'review',
|
|
456
|
-
provider: 'claude',
|
|
457
|
-
model: opts.claudeModel || 'sonnet',
|
|
458
|
-
promptTemplate: () => `Review these files: ${files.join(', ')}\nContext: ${context}\nReturn: findings with severity and line references.`,
|
|
459
|
-
},
|
|
460
|
-
{
|
|
461
|
-
role: 'reviewer-openai',
|
|
462
|
-
tier: 'review',
|
|
463
|
-
provider: 'openai',
|
|
464
|
-
model: opts.openaiModel || 'gpt-4o',
|
|
465
|
-
promptTemplate: () => `Review these files: ${files.join(', ')}\nContext: ${context}\nReturn: findings with severity and line references.`,
|
|
466
|
-
},
|
|
467
|
-
{
|
|
468
|
-
role: 'reconciler',
|
|
469
|
-
tier: 'think',
|
|
470
|
-
provider: 'claude',
|
|
471
|
-
model: opts.reconcileModel || 'opus',
|
|
472
|
-
promptTemplate: (session) => {
|
|
473
|
-
const reviews = session.agents
|
|
474
|
-
.filter(a => a.role.startsWith('reviewer-') && a.status === 'completed')
|
|
475
|
-
.map(a => `[${a.provider}]: ${a.summary || 'no findings'}`)
|
|
476
|
-
.join('\n\n');
|
|
477
|
-
return [
|
|
478
|
-
'Reconcile two independent code reviews:',
|
|
479
|
-
'',
|
|
480
|
-
reviews,
|
|
481
|
-
'',
|
|
482
|
-
'Identify: agreements (high confidence), disagreements (need resolution), and missed items.',
|
|
483
|
-
'Return: final consolidated review with severity ratings.',
|
|
484
|
-
].join('\n');
|
|
485
|
-
},
|
|
486
|
-
},
|
|
487
|
-
]);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// ── Persistence ─────────────────────────────────────────────────────────────
|
|
491
|
-
|
|
492
|
-
export function saveSession(session, cwd) {
|
|
493
|
-
const dir = join(cwd || process.cwd(), '.dual-brain', 'collaborations');
|
|
494
|
-
mkdirSync(dir, { recursive: true });
|
|
495
|
-
|
|
496
|
-
// Convert Set to Array for JSON serialization
|
|
497
|
-
const serializable = {
|
|
498
|
-
...session,
|
|
499
|
-
blackboard: {
|
|
500
|
-
...session.blackboard,
|
|
501
|
-
files: session.blackboard.files instanceof Set
|
|
502
|
-
? [...session.blackboard.files]
|
|
503
|
-
: session.blackboard.files,
|
|
504
|
-
},
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
writeFileSync(join(dir, `${session.id}.json`), JSON.stringify(serializable, null, 2));
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
export function loadSession(sessionId, cwd) {
|
|
511
|
-
const path = join(cwd || process.cwd(), '.dual-brain', 'collaborations', `${sessionId}.json`);
|
|
512
|
-
if (!existsSync(path)) return null;
|
|
513
|
-
try {
|
|
514
|
-
const data = JSON.parse(readFileSync(path, 'utf8'));
|
|
515
|
-
data.blackboard.files = new Set(data.blackboard.files || []);
|
|
516
|
-
return data;
|
|
517
|
-
} catch {
|
|
518
|
-
return null;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// ── Event bus (internal) ────────────────────────────────────────────────────
|
|
523
|
-
|
|
524
|
-
function _emitEvent(session, type, agentId, data) {
|
|
525
|
-
session.events.push({ type, agentId, data, timestamp: Date.now() });
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
function _extractSummary(result) {
|
|
529
|
-
if (!result) return null;
|
|
530
|
-
if (typeof result === 'string') return result.slice(0, 300);
|
|
531
|
-
if (result.summary) return String(result.summary).slice(0, 300);
|
|
532
|
-
if (result.rawOutput) return String(result.rawOutput).slice(0, 300);
|
|
533
|
-
return null;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// ── Event log persistence (append-only JSONL) ───────────────────────────────
|
|
537
|
-
|
|
538
|
-
export function persistEvents(session, cwd) {
|
|
539
|
-
const dir = join(cwd || process.cwd(), '.dual-brain', 'collaborations');
|
|
540
|
-
mkdirSync(dir, { recursive: true });
|
|
541
|
-
const logPath = join(dir, `${session.id}.events.jsonl`);
|
|
542
|
-
for (const event of session.events) {
|
|
543
|
-
appendFileSync(logPath, JSON.stringify(event) + '\n');
|
|
544
|
-
}
|
|
545
|
-
}
|
package/src/context-intel.mjs
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
-
import { join, resolve } from 'node:path';
|
|
3
|
-
|
|
4
|
-
export const MODEL_FORMAT = {
|
|
5
|
-
claude: 'xml', sonnet: 'xml', haiku: 'xml', opus: 'xml',
|
|
6
|
-
gpt: 'markdown', 'o4-mini': 'markdown',
|
|
7
|
-
o3: 'prose',
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
function detectFormat(targetModel, role) {
|
|
11
|
-
const m = targetModel.toLowerCase();
|
|
12
|
-
if (m.includes('o3') || (m.includes('opus') && role === 'thinker')) return 'prose';
|
|
13
|
-
if (m.includes('gpt') || m.includes('o3') || m.includes('o4')) return 'markdown';
|
|
14
|
-
return 'xml';
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function selectRelevant(pack, role) {
|
|
18
|
-
if (!pack) return { intent: '', constraints: [], acceptanceCriteria: [] };
|
|
19
|
-
const { intent, constraints, priorAttempts, repoState, fileSummaries,
|
|
20
|
-
acceptanceCriteria, files } = pack;
|
|
21
|
-
if (role === 'thinker') {
|
|
22
|
-
return { intent, constraints, priorAttempts, repoState,
|
|
23
|
-
fileSummaries, acceptanceCriteria };
|
|
24
|
-
}
|
|
25
|
-
if (role === 'worker') {
|
|
26
|
-
const inScope = [...(files?.explicit || []), ...(files?.gitChanged || [])];
|
|
27
|
-
return { intent, acceptanceCriteria, constraints, inScope };
|
|
28
|
-
}
|
|
29
|
-
// reviewer
|
|
30
|
-
return { intent, acceptanceCriteria, constraints, fileSummaries, repoState };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function readFiles(paths, cwd) {
|
|
34
|
-
const base = cwd || process.cwd();
|
|
35
|
-
const out = {};
|
|
36
|
-
for (const p of paths) {
|
|
37
|
-
const abs = resolve(base, p);
|
|
38
|
-
if (existsSync(abs)) {
|
|
39
|
-
try { out[p] = readFileSync(abs, 'utf8'); } catch { out[p] = '(unreadable)'; }
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return out;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function renderForModel(sections, targetModel, role) {
|
|
46
|
-
const fmt = detectFormat(targetModel, role);
|
|
47
|
-
|
|
48
|
-
if (fmt === 'prose') {
|
|
49
|
-
const parts = [];
|
|
50
|
-
if (sections.intent) parts.push(`Task: ${sections.intent}`);
|
|
51
|
-
if (sections.acceptanceCriteria?.length)
|
|
52
|
-
parts.push(`Success looks like: ${sections.acceptanceCriteria.join('; ')}`);
|
|
53
|
-
if (sections.constraints?.length)
|
|
54
|
-
parts.push(`Constraints: ${sections.constraints.join('; ')}`);
|
|
55
|
-
if (sections.repoState) parts.push(`Repo: ${JSON.stringify(sections.repoState)}`);
|
|
56
|
-
if (sections.fileSummaries) parts.push(`Files: ${JSON.stringify(sections.fileSummaries)}`);
|
|
57
|
-
if (sections.priorAttempts?.length)
|
|
58
|
-
parts.push(`Prior attempts: ${JSON.stringify(sections.priorAttempts)}`);
|
|
59
|
-
return parts.join('\n\n');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (fmt === 'markdown') {
|
|
63
|
-
const lines = [];
|
|
64
|
-
if (sections.intent) lines.push(`## Objective\n${sections.intent}`);
|
|
65
|
-
if (sections.constraints?.length)
|
|
66
|
-
lines.push(`## Constraints\n${sections.constraints.map(c => `- ${c}`).join('\n')}`);
|
|
67
|
-
if (sections.acceptanceCriteria?.length)
|
|
68
|
-
lines.push(`## Acceptance Criteria\n${sections.acceptanceCriteria.map(c => `- ${c}`).join('\n')}`);
|
|
69
|
-
if (sections.repoState) lines.push(`## Repo State\n\`\`\`json\n${JSON.stringify(sections.repoState, null, 2)}\n\`\`\``);
|
|
70
|
-
if (sections.fileSummaries) lines.push(`## Files\n\`\`\`json\n${JSON.stringify(sections.fileSummaries, null, 2)}\n\`\`\``);
|
|
71
|
-
if (sections.fileContents) {
|
|
72
|
-
lines.push('## File Contents');
|
|
73
|
-
for (const [p, content] of Object.entries(sections.fileContents))
|
|
74
|
-
lines.push(`### ${p}\n\`\`\`\n${content}\n\`\`\``);
|
|
75
|
-
}
|
|
76
|
-
if (sections.inScope?.length)
|
|
77
|
-
lines.push(`## In-Scope Files\n${sections.inScope.map(f => `- ${f}`).join('\n')}`);
|
|
78
|
-
if (sections.priorAttempts?.length)
|
|
79
|
-
lines.push(`## Prior Attempts\n${JSON.stringify(sections.priorAttempts, null, 2)}`);
|
|
80
|
-
return lines.join('\n\n');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// xml (Claude models)
|
|
84
|
-
const tags = [];
|
|
85
|
-
if (sections.intent) tags.push(`<objective>${sections.intent}</objective>`);
|
|
86
|
-
if (sections.constraints?.length)
|
|
87
|
-
tags.push(`<constraints>\n${sections.constraints.map(c => ` <constraint>${c}</constraint>`).join('\n')}\n</constraints>`);
|
|
88
|
-
if (sections.acceptanceCriteria?.length)
|
|
89
|
-
tags.push(`<criteria>\n${sections.acceptanceCriteria.map(c => ` <criterion>${c}</criterion>`).join('\n')}\n</criteria>`);
|
|
90
|
-
if (sections.repoState)
|
|
91
|
-
tags.push(`<repo_state>${JSON.stringify(sections.repoState)}</repo_state>`);
|
|
92
|
-
if (sections.fileSummaries)
|
|
93
|
-
tags.push(`<files>${JSON.stringify(sections.fileSummaries)}</files>`);
|
|
94
|
-
if (sections.fileContents) {
|
|
95
|
-
const fc = Object.entries(sections.fileContents)
|
|
96
|
-
.map(([p, c]) => ` <file path="${p}">\n${c}\n </file>`).join('\n');
|
|
97
|
-
tags.push(`<file_contents>\n${fc}\n</file_contents>`);
|
|
98
|
-
}
|
|
99
|
-
if (sections.inScope?.length)
|
|
100
|
-
tags.push(`<in_scope_files>\n${sections.inScope.map(f => ` <file>${f}</file>`).join('\n')}\n</in_scope_files>`);
|
|
101
|
-
if (sections.priorAttempts?.length)
|
|
102
|
-
tags.push(`<prior_attempts>${JSON.stringify(sections.priorAttempts)}</prior_attempts>`);
|
|
103
|
-
return `<context>\n${tags.join('\n')}\n</context>`;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function enforceTokenBudget(rendered, budget) {
|
|
107
|
-
const chars = budget * 4;
|
|
108
|
-
const originalTokens = Math.ceil(rendered.length / 4);
|
|
109
|
-
if (rendered.length <= chars) return { text: rendered, truncated: false, originalTokens, finalTokens: originalTokens };
|
|
110
|
-
|
|
111
|
-
// Try dropping prior_attempts / prior attempts block
|
|
112
|
-
let text = rendered
|
|
113
|
-
.replace(/<prior_attempts>[\s\S]*?<\/prior_attempts>/g, '')
|
|
114
|
-
.replace(/## Prior Attempts[\s\S]*?(?=\n## |$)/, '')
|
|
115
|
-
.replace(/Prior attempts:.*?(?=\n\n|$)/s, '');
|
|
116
|
-
|
|
117
|
-
if (text.length <= chars) return { text: text.trim(), truncated: true, originalTokens, finalTokens: Math.ceil(text.length / 4) };
|
|
118
|
-
|
|
119
|
-
// Summarize git/repo state
|
|
120
|
-
text = text
|
|
121
|
-
.replace(/<repo_state>[\s\S]*?<\/repo_state>/g, '<repo_state>(truncated)</repo_state>')
|
|
122
|
-
.replace(/## Repo State[\s\S]*?(?=\n## |$)/, '## Repo State\n(truncated)');
|
|
123
|
-
|
|
124
|
-
if (text.length <= chars) return { text: text.trim(), truncated: true, originalTokens, finalTokens: Math.ceil(text.length / 4) };
|
|
125
|
-
|
|
126
|
-
// Hard truncate
|
|
127
|
-
text = text.slice(0, chars) + '\n...(truncated)';
|
|
128
|
-
return { text, truncated: true, originalTokens, finalTokens: Math.ceil(text.length / 4) };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function attachOutputSchema(role) {
|
|
132
|
-
if (role === 'thinker')
|
|
133
|
-
return 'Return JSON: { decision: string, confidence: 0-1, reasoning: string, workSpec: { objective, files, criteria } }';
|
|
134
|
-
if (role === 'worker')
|
|
135
|
-
return 'Return JSON: { filesChanged: string[], testsRun: boolean, issues: string[] }';
|
|
136
|
-
return 'Return JSON: { pass: boolean, findings: [{ severity, file, line, issue, fix }] }';
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export function shapeForRole(pack, role, targetModel = 'sonnet', tokenBudget = 8000) {
|
|
140
|
-
const sections = selectRelevant(pack, role);
|
|
141
|
-
|
|
142
|
-
if (role === 'worker' && sections.inScope?.length) {
|
|
143
|
-
const cwd = pack.repoState?.cwd;
|
|
144
|
-
sections.fileContents = readFiles(sections.inScope, cwd);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const rendered = renderForModel(sections, targetModel, role);
|
|
148
|
-
const { text, truncated, originalTokens, finalTokens } = enforceTokenBudget(rendered, tokenBudget);
|
|
149
|
-
const sectionKeys = Object.keys(sections);
|
|
150
|
-
|
|
151
|
-
return { shaped: text, role, model: targetModel, tokenEstimate: finalTokens, sections: sectionKeys };
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function compilePacket(pack, role, targetModel, tokenBudget) {
|
|
155
|
-
const { shaped } = shapeForRole(pack, role, targetModel, tokenBudget);
|
|
156
|
-
const schema = attachOutputSchema(role);
|
|
157
|
-
return `${shaped}\n\n${schema}`;
|
|
158
|
-
}
|