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.
Files changed (68) hide show
  1. package/AGENTS.md +97 -0
  2. package/CLAUDE.md +147 -0
  3. package/LICENSE +21 -0
  4. package/README.md +197 -0
  5. package/agents/implementer.md +22 -0
  6. package/agents/researcher.md +25 -0
  7. package/agents/verifier.md +30 -0
  8. package/bin/dual-brain.mjs +2868 -0
  9. package/hooks/auto-update-wrapper.mjs +102 -0
  10. package/hooks/auto-update.sh +67 -0
  11. package/hooks/budget-balancer.mjs +679 -0
  12. package/hooks/control-panel.mjs +1195 -0
  13. package/hooks/cost-logger.mjs +286 -0
  14. package/hooks/cost-report.mjs +351 -0
  15. package/hooks/decision-ledger.mjs +299 -0
  16. package/hooks/dual-brain-review.mjs +404 -0
  17. package/hooks/dual-brain-think.mjs +393 -0
  18. package/hooks/enforce-tier.mjs +469 -0
  19. package/hooks/failure-detector.mjs +138 -0
  20. package/hooks/gpt-work-dispatcher.mjs +512 -0
  21. package/hooks/head-guard.mjs +105 -0
  22. package/hooks/health-check.mjs +444 -0
  23. package/hooks/install-git-hooks.mjs +106 -0
  24. package/hooks/model-registry.mjs +859 -0
  25. package/hooks/plan-generator.mjs +544 -0
  26. package/hooks/profiles.mjs +254 -0
  27. package/hooks/quality-gate.mjs +355 -0
  28. package/hooks/risk-classifier.mjs +41 -0
  29. package/hooks/session-report.mjs +514 -0
  30. package/hooks/setup-wizard.mjs +130 -0
  31. package/hooks/summary-checkpoint.mjs +432 -0
  32. package/hooks/task-classifier.mjs +328 -0
  33. package/hooks/test-orchestrator.mjs +1077 -0
  34. package/hooks/vibe-memory.mjs +463 -0
  35. package/hooks/vibe-router.mjs +387 -0
  36. package/hooks/wave-orchestrator.mjs +1397 -0
  37. package/install.mjs +1541 -0
  38. package/mcp-server/README.md +81 -0
  39. package/mcp-server/index.mjs +388 -0
  40. package/orchestrator.json +215 -0
  41. package/package.json +108 -0
  42. package/playbooks/debug.json +49 -0
  43. package/playbooks/refactor.json +57 -0
  44. package/playbooks/security-audit.json +57 -0
  45. package/playbooks/security.json +38 -0
  46. package/playbooks/test-gen.json +48 -0
  47. package/plugin.json +22 -0
  48. package/review-rules.md +17 -0
  49. package/shell-hook.sh +26 -0
  50. package/skills/go.md +22 -0
  51. package/skills/review.md +19 -0
  52. package/skills/status.md +13 -0
  53. package/skills/think.md +22 -0
  54. package/src/brief.mjs +266 -0
  55. package/src/decide.mjs +635 -0
  56. package/src/decompose.mjs +331 -0
  57. package/src/detect.mjs +345 -0
  58. package/src/dispatch.mjs +942 -0
  59. package/src/health.mjs +253 -0
  60. package/src/index.mjs +44 -0
  61. package/src/install-hooks.mjs +100 -0
  62. package/src/playbook.mjs +257 -0
  63. package/src/profile.mjs +990 -0
  64. package/src/redact.mjs +192 -0
  65. package/src/repo.mjs +292 -0
  66. package/src/session.mjs +1036 -0
  67. package/src/tui.mjs +197 -0
  68. 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
+ }