dual-brain 0.1.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/AGENTS.md +97 -0
- package/CLAUDE.md +147 -0
- package/LICENSE +21 -0
- package/README.md +197 -0
- package/agents/implementer.md +22 -0
- package/agents/researcher.md +25 -0
- package/agents/verifier.md +30 -0
- package/bin/dual-brain.mjs +2868 -0
- package/hooks/auto-update-wrapper.mjs +102 -0
- package/hooks/auto-update.sh +67 -0
- package/hooks/budget-balancer.mjs +679 -0
- package/hooks/control-panel.mjs +1195 -0
- package/hooks/cost-logger.mjs +286 -0
- package/hooks/cost-report.mjs +351 -0
- package/hooks/decision-ledger.mjs +299 -0
- package/hooks/dual-brain-review.mjs +404 -0
- package/hooks/dual-brain-think.mjs +393 -0
- package/hooks/enforce-tier.mjs +469 -0
- package/hooks/failure-detector.mjs +138 -0
- package/hooks/gpt-work-dispatcher.mjs +512 -0
- package/hooks/head-guard.mjs +105 -0
- package/hooks/health-check.mjs +444 -0
- package/hooks/install-git-hooks.mjs +106 -0
- package/hooks/model-registry.mjs +859 -0
- package/hooks/plan-generator.mjs +544 -0
- package/hooks/profiles.mjs +254 -0
- package/hooks/quality-gate.mjs +355 -0
- package/hooks/risk-classifier.mjs +41 -0
- package/hooks/session-report.mjs +514 -0
- package/hooks/setup-wizard.mjs +130 -0
- package/hooks/summary-checkpoint.mjs +432 -0
- package/hooks/task-classifier.mjs +328 -0
- package/hooks/test-orchestrator.mjs +1077 -0
- package/hooks/vibe-memory.mjs +463 -0
- package/hooks/vibe-router.mjs +387 -0
- package/hooks/wave-orchestrator.mjs +1397 -0
- package/install.mjs +1541 -0
- package/mcp-server/README.md +81 -0
- package/mcp-server/index.mjs +388 -0
- package/orchestrator.json +215 -0
- package/package.json +108 -0
- package/playbooks/debug.json +49 -0
- package/playbooks/refactor.json +57 -0
- package/playbooks/security-audit.json +57 -0
- package/playbooks/security.json +38 -0
- package/playbooks/test-gen.json +48 -0
- package/plugin.json +22 -0
- package/review-rules.md +17 -0
- package/shell-hook.sh +26 -0
- package/skills/go.md +22 -0
- package/skills/review.md +19 -0
- package/skills/status.md +13 -0
- package/skills/think.md +22 -0
- package/src/brief.mjs +266 -0
- package/src/decide.mjs +635 -0
- package/src/decompose.mjs +331 -0
- package/src/detect.mjs +345 -0
- package/src/dispatch.mjs +942 -0
- package/src/health.mjs +253 -0
- package/src/index.mjs +44 -0
- package/src/install-hooks.mjs +100 -0
- package/src/playbook.mjs +257 -0
- package/src/profile.mjs +990 -0
- package/src/redact.mjs +192 -0
- package/src/repo.mjs +292 -0
- package/src/session.mjs +1036 -0
- package/src/tui.mjs +197 -0
- package/src/update-check.mjs +35 -0
package/src/brief.mjs
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* brief.mjs — Delegation brief generator for the Dual-Brain Orchestrator HEAD.
|
|
4
|
+
*
|
|
5
|
+
* Generates typed delegation prompts from role-based templates. The HEAD's
|
|
6
|
+
* primary skill is writing great agent briefs. Pure string construction —
|
|
7
|
+
* no imports from sibling modules.
|
|
8
|
+
*
|
|
9
|
+
* Exports: generateBrief, compressPriorResults, listRoles
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ─── Brief templates (role-based) ────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
const TEMPLATES = {
|
|
15
|
+
researcher: {
|
|
16
|
+
prefix: 'You are a code researcher. READ ONLY — do NOT edit any files.',
|
|
17
|
+
outputFormat: 'Return: { findings: string, files: string[], lineRefs: string[], confidence: "high"|"medium"|"low" }',
|
|
18
|
+
constraints: 'Max 50 lines of quoted code. Focus on structure and relationships, not implementation details.',
|
|
19
|
+
},
|
|
20
|
+
implementer: {
|
|
21
|
+
prefix: 'You are a code implementer. Edit ONLY files in your ownership list.',
|
|
22
|
+
outputFormat: 'Return: { filesChanged: string[], testsRun: string[], decisions: string[], risks: string[] }',
|
|
23
|
+
constraints: 'Run tests after changes. Make minimal edits. Do not refactor beyond scope.',
|
|
24
|
+
},
|
|
25
|
+
reviewer: {
|
|
26
|
+
prefix: 'You are a code reviewer. READ ONLY — do NOT edit any files.',
|
|
27
|
+
outputFormat: 'Return: { issues: { severity: string, file: string, line: number, description: string }[], approved: boolean, risks: string[] }',
|
|
28
|
+
constraints: 'Focus on correctness, security, and behavior preservation. Ignore style.',
|
|
29
|
+
},
|
|
30
|
+
verifier: {
|
|
31
|
+
prefix: 'You are a test verifier. Run the test suite and report results.',
|
|
32
|
+
outputFormat: 'Return: { passed: boolean, failCount: number, failures: string[], rootCause: string | null }',
|
|
33
|
+
constraints: 'If tests fail, identify root cause but DO NOT fix. Report only.',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ─── Exported: listRoles ──────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Return available role names and their short descriptions.
|
|
41
|
+
* @returns {{ role: string, description: string }[]}
|
|
42
|
+
*/
|
|
43
|
+
export function listRoles() {
|
|
44
|
+
return [
|
|
45
|
+
{ role: 'researcher', description: 'Read-only exploration and mapping. Returns findings, file refs, and confidence.' },
|
|
46
|
+
{ role: 'implementer', description: 'Makes edits within ownership scope. Returns changed files, test results, and decisions.' },
|
|
47
|
+
{ role: 'reviewer', description: 'Read-only code review. Returns issues by severity and an approval verdict.' },
|
|
48
|
+
{ role: 'verifier', description: 'Runs the test suite and identifies failures. Does not fix anything.' },
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── Exported: compressPriorResults ──────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Take an array of prior wave results and return a compact string suitable for
|
|
56
|
+
* injection into agent briefs. Strips code blocks, keeps decisions and file paths.
|
|
57
|
+
*
|
|
58
|
+
* @param {Array<{ stepId?: string, taskId?: string, summary?: string, output?: string, filesChanged?: string[], decisions?: string[], findings?: string }>} results
|
|
59
|
+
* @returns {string}
|
|
60
|
+
*/
|
|
61
|
+
export function compressPriorResults(results) {
|
|
62
|
+
if (!results || results.length === 0) return '';
|
|
63
|
+
|
|
64
|
+
const lines = [];
|
|
65
|
+
for (const r of results) {
|
|
66
|
+
const id = r.stepId ?? r.taskId ?? '?';
|
|
67
|
+
const parts = [];
|
|
68
|
+
|
|
69
|
+
// Extract summary or findings
|
|
70
|
+
const rawSummary = r.summary ?? r.findings ?? r.output ?? '';
|
|
71
|
+
if (rawSummary) {
|
|
72
|
+
// Strip code blocks
|
|
73
|
+
const cleaned = String(rawSummary)
|
|
74
|
+
.replace(/```[\s\S]*?```/g, '[code block]')
|
|
75
|
+
.replace(/`[^`]+`/g, (m) => m.replace(/\n/g, ' '))
|
|
76
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
77
|
+
.trim();
|
|
78
|
+
// Keep only first 200 chars of text
|
|
79
|
+
const snippet = cleaned.length > 200 ? cleaned.slice(0, 197) + '...' : cleaned;
|
|
80
|
+
if (snippet) parts.push(snippet);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Append file lists compactly
|
|
84
|
+
if (Array.isArray(r.filesChanged) && r.filesChanged.length > 0) {
|
|
85
|
+
parts.push(`files: ${r.filesChanged.join(', ')}`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Append key decisions
|
|
89
|
+
if (Array.isArray(r.decisions) && r.decisions.length > 0) {
|
|
90
|
+
parts.push(`decisions: ${r.decisions.slice(0, 3).join('; ')}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (parts.length > 0) {
|
|
94
|
+
lines.push(`[${id}] ${parts.join(' | ')}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return lines.join('\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── Section builders ─────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
function buildOwnershipSection(owns) {
|
|
104
|
+
if (!owns || owns.length === 0) return null;
|
|
105
|
+
return `File ownership (ONLY edit these):\n ${owns.join('\n ')}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildPriorResultsSection(priorResults) {
|
|
109
|
+
const compressed = compressPriorResults(priorResults);
|
|
110
|
+
if (!compressed) return null;
|
|
111
|
+
return `Prior wave results (context only — do not repeat this work):\n${compressed}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function buildAcceptanceCriteria(task) {
|
|
115
|
+
const lines = [];
|
|
116
|
+
|
|
117
|
+
if (task.role === 'researcher') {
|
|
118
|
+
lines.push('- Return a structured findings object with file paths and line references');
|
|
119
|
+
lines.push('- Report confidence level based on coverage of the codebase you explored');
|
|
120
|
+
} else if (task.role === 'implementer') {
|
|
121
|
+
lines.push('- All edits must stay within the ownership list above');
|
|
122
|
+
lines.push('- Run existing tests; report any failures');
|
|
123
|
+
lines.push('- Document decisions made during implementation');
|
|
124
|
+
} else if (task.role === 'reviewer') {
|
|
125
|
+
lines.push('- Report every issue with severity (critical / high / medium / low)');
|
|
126
|
+
lines.push('- Provide a clear approved: true/false verdict');
|
|
127
|
+
lines.push('- Do not edit any files');
|
|
128
|
+
} else if (task.role === 'verifier') {
|
|
129
|
+
lines.push('- Run the full test suite');
|
|
130
|
+
lines.push('- If tests fail, identify root cause but do NOT fix');
|
|
131
|
+
lines.push('- Return pass/fail with failure details');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (task.consensus) {
|
|
135
|
+
lines.push('- This task requires dual-brain consensus — be thorough and explicit in your reasoning');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return lines.length > 0 ? `Acceptance criteria:\n${lines.join('\n')}` : null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ─── Exported: generateBrief ──────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Generate a full delegation prompt for a single agent task.
|
|
145
|
+
*
|
|
146
|
+
* @param {{
|
|
147
|
+
* id: string,
|
|
148
|
+
* title: string,
|
|
149
|
+
* goal: string,
|
|
150
|
+
* tier: string,
|
|
151
|
+
* role: 'researcher'|'implementer'|'reviewer'|'verifier',
|
|
152
|
+
* owns: string[],
|
|
153
|
+
* consensus: boolean,
|
|
154
|
+
* risk: string,
|
|
155
|
+
* }} task — from decompose output
|
|
156
|
+
* @param {{
|
|
157
|
+
* prompt?: string,
|
|
158
|
+
* priorResults?: object[],
|
|
159
|
+
* repo?: { projectType?: string, branch?: string, testCmd?: string, lintCmd?: string },
|
|
160
|
+
* cwd?: string,
|
|
161
|
+
* }} [context]
|
|
162
|
+
* @returns {string} Full delegation prompt string
|
|
163
|
+
*/
|
|
164
|
+
export function generateBrief(task, context = {}) {
|
|
165
|
+
const { prompt = '', priorResults = [], repo = {} } = context;
|
|
166
|
+
const role = task.role ?? 'implementer';
|
|
167
|
+
const template = TEMPLATES[role] ?? TEMPLATES.implementer;
|
|
168
|
+
|
|
169
|
+
const sections = [];
|
|
170
|
+
|
|
171
|
+
// 1. Role header
|
|
172
|
+
sections.push(template.prefix);
|
|
173
|
+
sections.push('');
|
|
174
|
+
|
|
175
|
+
// 2. Task identity
|
|
176
|
+
sections.push(`Task: ${task.title}`);
|
|
177
|
+
if (task.id) sections.push(`Task ID: ${task.id}`);
|
|
178
|
+
if (task.tier) sections.push(`Tier: ${task.tier}`);
|
|
179
|
+
sections.push('');
|
|
180
|
+
|
|
181
|
+
// 3. Goal
|
|
182
|
+
sections.push(`Goal:\n${task.goal}`);
|
|
183
|
+
sections.push('');
|
|
184
|
+
|
|
185
|
+
// 4. Original user request (for context)
|
|
186
|
+
if (prompt && prompt !== task.goal) {
|
|
187
|
+
const snippet = prompt.length > 300 ? prompt.slice(0, 297) + '...' : prompt;
|
|
188
|
+
sections.push(`Original request (for context):\n${snippet}`);
|
|
189
|
+
sections.push('');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 5. File ownership
|
|
193
|
+
const ownershipSection = buildOwnershipSection(task.owns);
|
|
194
|
+
if (ownershipSection) {
|
|
195
|
+
sections.push(ownershipSection);
|
|
196
|
+
sections.push('');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 6. Repository context (if available)
|
|
200
|
+
const repoLines = [];
|
|
201
|
+
if (repo.projectType) repoLines.push(`Project type: ${repo.projectType}`);
|
|
202
|
+
if (repo.branch) repoLines.push(`Branch: ${repo.branch}`);
|
|
203
|
+
if (repo.testCmd) repoLines.push(`Test command: ${repo.testCmd}`);
|
|
204
|
+
if (repo.lintCmd) repoLines.push(`Lint command: ${repo.lintCmd}`);
|
|
205
|
+
if (repoLines.length > 0) {
|
|
206
|
+
sections.push(`Repository context:\n ${repoLines.join('\n ')}`);
|
|
207
|
+
sections.push('');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// 7. Prior results
|
|
211
|
+
const priorSection = buildPriorResultsSection(priorResults);
|
|
212
|
+
if (priorSection) {
|
|
213
|
+
sections.push(priorSection);
|
|
214
|
+
sections.push('');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 8. Acceptance criteria
|
|
218
|
+
const criteriaSection = buildAcceptanceCriteria(task);
|
|
219
|
+
if (criteriaSection) {
|
|
220
|
+
sections.push(criteriaSection);
|
|
221
|
+
sections.push('');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 9. Constraints
|
|
225
|
+
sections.push(`Constraints:\n${template.constraints}`);
|
|
226
|
+
sections.push('');
|
|
227
|
+
|
|
228
|
+
// 10. Output format requirement
|
|
229
|
+
sections.push(`Required output format:\n${template.outputFormat}`);
|
|
230
|
+
|
|
231
|
+
return sections.join('\n').trimEnd();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ─── CLI ──────────────────────────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
if (process.argv[1] && new URL(import.meta.url).pathname === process.argv[1]) {
|
|
237
|
+
const args = process.argv.slice(2);
|
|
238
|
+
const cmd = args[0];
|
|
239
|
+
|
|
240
|
+
if (cmd === 'roles') {
|
|
241
|
+
console.log(JSON.stringify(listRoles(), null, 2));
|
|
242
|
+
process.exit(0);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (cmd === 'generate') {
|
|
246
|
+
const role = args[1] ?? 'implementer';
|
|
247
|
+
const goal = args[2] ?? 'No goal provided.';
|
|
248
|
+
const task = {
|
|
249
|
+
id: 'task-1',
|
|
250
|
+
title: goal.slice(0, 60),
|
|
251
|
+
goal,
|
|
252
|
+
tier: 'execute',
|
|
253
|
+
role,
|
|
254
|
+
owns: [],
|
|
255
|
+
consensus: false,
|
|
256
|
+
risk: 'medium',
|
|
257
|
+
};
|
|
258
|
+
console.log(generateBrief(task, { prompt: goal }));
|
|
259
|
+
process.exit(0);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
console.error('Usage:');
|
|
263
|
+
console.error(' node src/brief.mjs roles');
|
|
264
|
+
console.error(' node src/brief.mjs generate <role> "<goal>"');
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|