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/playbook.mjs
DELETED
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* playbook.mjs — Playbook loader and executor for the Dual-Brain Orchestrator.
|
|
4
|
-
*
|
|
5
|
-
* Exports:
|
|
6
|
-
* loadPlaybook(intent, cwd) → playbook object | null
|
|
7
|
-
* listPlaybooks(cwd) → [{ name, source, stepCount }]
|
|
8
|
-
* executePlaybook(playbook, context) → { steps, summary, runId }
|
|
9
|
-
* createRunArtifact(runId, results, cwd) → artifact path
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
13
|
-
import { homedir } from 'node:os';
|
|
14
|
-
import { join, dirname, basename } from 'node:path';
|
|
15
|
-
import { fileURLToPath } from 'node:url';
|
|
16
|
-
import { randomUUID } from 'node:crypto';
|
|
17
|
-
|
|
18
|
-
import { loadProfile } from './profile.mjs';
|
|
19
|
-
import { decideRoute, shouldDualBrain } from './decide.mjs';
|
|
20
|
-
import { dispatch, dispatchDualBrain } from './dispatch.mjs';
|
|
21
|
-
|
|
22
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
|
-
const BUILTIN_DIR = join(__dirname, '..', 'playbooks');
|
|
24
|
-
const GLOBAL_DIR = join(homedir(), '.config', 'dual-brain', 'playbooks');
|
|
25
|
-
|
|
26
|
-
// ─── Playbook resolution helpers ─────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
function projectDir(cwd) {
|
|
29
|
-
return join(cwd || process.cwd(), '.dualbrain', 'playbooks');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function readJson(path) {
|
|
33
|
-
try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return null; }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function playbookPath(dir, intent) {
|
|
37
|
-
return join(dir, `${intent}.json`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ─── Exported: loadPlaybook ───────────────────────────────────────────────────
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Find and return a playbook matching the given intent.
|
|
44
|
-
* Search order: project-local → global user → built-in.
|
|
45
|
-
* Returns null if no match found.
|
|
46
|
-
* @param {string} intent
|
|
47
|
-
* @param {string} [cwd]
|
|
48
|
-
* @returns {object|null}
|
|
49
|
-
*/
|
|
50
|
-
export function loadPlaybook(intent, cwd) {
|
|
51
|
-
if (!intent) return null;
|
|
52
|
-
|
|
53
|
-
const candidates = [
|
|
54
|
-
{ dir: projectDir(cwd), source: 'project' },
|
|
55
|
-
{ dir: GLOBAL_DIR, source: 'global' },
|
|
56
|
-
{ dir: BUILTIN_DIR, source: 'builtin' },
|
|
57
|
-
];
|
|
58
|
-
|
|
59
|
-
for (const { dir, source } of candidates) {
|
|
60
|
-
const path = playbookPath(dir, intent);
|
|
61
|
-
if (existsSync(path)) {
|
|
62
|
-
const pb = readJson(path);
|
|
63
|
-
if (pb) return { ...pb, _source: source, _path: path };
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ─── Exported: listPlaybooks ──────────────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Return all available playbooks across all sources, deduped (project wins).
|
|
74
|
-
* @param {string} [cwd]
|
|
75
|
-
* @returns {{ name: string, source: string, stepCount: number }[]}
|
|
76
|
-
*/
|
|
77
|
-
export function listPlaybooks(cwd) {
|
|
78
|
-
const seen = new Map(); // name → entry (first write wins: project > global > builtin)
|
|
79
|
-
|
|
80
|
-
const sources = [
|
|
81
|
-
{ dir: projectDir(cwd), source: 'project' },
|
|
82
|
-
{ dir: GLOBAL_DIR, source: 'global' },
|
|
83
|
-
{ dir: BUILTIN_DIR, source: 'builtin' },
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
for (const { dir, source } of sources) {
|
|
87
|
-
if (!existsSync(dir)) continue;
|
|
88
|
-
let files;
|
|
89
|
-
try { files = readdirSync(dir); } catch { continue; }
|
|
90
|
-
|
|
91
|
-
for (const file of files) {
|
|
92
|
-
if (!file.endsWith('.json')) continue;
|
|
93
|
-
const name = basename(file, '.json');
|
|
94
|
-
if (seen.has(name)) continue; // project-local already registered
|
|
95
|
-
const pb = readJson(join(dir, file));
|
|
96
|
-
if (!pb) continue;
|
|
97
|
-
seen.set(name, {
|
|
98
|
-
name: pb.name ?? name,
|
|
99
|
-
source,
|
|
100
|
-
stepCount: Array.isArray(pb.steps) ? pb.steps.length : 0,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return [...seen.values()];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ─── Exported: createRunArtifact ─────────────────────────────────────────────
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Persist a run manifest under .dualbrain/runs/<runId>/manifest.json.
|
|
112
|
-
* @param {string} runId
|
|
113
|
-
* @param {object[]} results — step result objects
|
|
114
|
-
* @param {string} [cwd]
|
|
115
|
-
* @returns {string} path to the manifest file
|
|
116
|
-
*/
|
|
117
|
-
export function createRunArtifact(runId, results, cwd) {
|
|
118
|
-
const dir = join(cwd || process.cwd(), '.dualbrain', 'runs', runId);
|
|
119
|
-
mkdirSync(dir, { recursive: true });
|
|
120
|
-
const path = join(dir, 'manifest.json');
|
|
121
|
-
const manifest = {
|
|
122
|
-
runId,
|
|
123
|
-
createdAt: new Date().toISOString(),
|
|
124
|
-
stepCount: results.length,
|
|
125
|
-
steps: results,
|
|
126
|
-
};
|
|
127
|
-
writeFileSync(path, JSON.stringify(manifest, null, 2));
|
|
128
|
-
return path;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ─── Step prompt builder ──────────────────────────────────────────────────────
|
|
132
|
-
|
|
133
|
-
function buildStepPrompt(step, priorOutputs, basePrompt) {
|
|
134
|
-
const parts = [];
|
|
135
|
-
|
|
136
|
-
if (basePrompt) parts.push(`Context: ${basePrompt}`);
|
|
137
|
-
|
|
138
|
-
if (priorOutputs.length > 0) {
|
|
139
|
-
parts.push('Prior step results:');
|
|
140
|
-
for (const prior of priorOutputs) {
|
|
141
|
-
parts.push(` [${prior.stepId}] ${prior.summary ?? '(no output)'}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
parts.push(`\nCurrent task — ${step.title}: ${step.goal}`);
|
|
146
|
-
|
|
147
|
-
if (step.output?.kind) {
|
|
148
|
-
parts.push(`Expected output format: ${step.output.kind}`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return parts.join('\n');
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ─── Exported: executePlaybook ────────────────────────────────────────────────
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Execute all steps in a playbook sequentially, feeding prior outputs forward.
|
|
158
|
-
* @param {object} playbook
|
|
159
|
-
* @param {{ profile?: object, prompt?: string, files?: string[], cwd?: string, dryRun?: boolean, verbose?: boolean }} context
|
|
160
|
-
* @returns {Promise<{ steps: object[], summary: string, runId: string }>}
|
|
161
|
-
*/
|
|
162
|
-
export async function executePlaybook(playbook, context = {}) {
|
|
163
|
-
const {
|
|
164
|
-
prompt = '',
|
|
165
|
-
files = [],
|
|
166
|
-
cwd = process.cwd(),
|
|
167
|
-
dryRun = false,
|
|
168
|
-
verbose = false,
|
|
169
|
-
} = context;
|
|
170
|
-
|
|
171
|
-
let { profile } = context;
|
|
172
|
-
if (!profile) {
|
|
173
|
-
try { profile = await loadProfile(cwd); } catch { profile = {}; }
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const runId = randomUUID();
|
|
177
|
-
const steps = playbook.steps ?? [];
|
|
178
|
-
const results = [];
|
|
179
|
-
const priorOuts = [];
|
|
180
|
-
|
|
181
|
-
if (verbose) {
|
|
182
|
-
console.log(`[playbook] Starting "${playbook.name}" — ${steps.length} steps (runId: ${runId})`);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
for (const step of steps) {
|
|
186
|
-
const stepPrompt = buildStepPrompt(step, priorOuts, prompt);
|
|
187
|
-
|
|
188
|
-
// Build synthetic detection that respects the step's declared tier
|
|
189
|
-
const detection = {
|
|
190
|
-
intent: step.tier === 'think' ? 'architecture'
|
|
191
|
-
: step.tier === 'search' ? 'search'
|
|
192
|
-
: 'edit',
|
|
193
|
-
tier: step.tier ?? 'execute',
|
|
194
|
-
risk: 'medium',
|
|
195
|
-
complexity: 'moderate',
|
|
196
|
-
effort: 'medium',
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
// Force dual-brain if step declares consensus:true OR risk warrants it
|
|
200
|
-
const forceDual = step.consensus === true || shouldDualBrain(detection, profile);
|
|
201
|
-
const decision = decideRoute({ profile, detection, cwd });
|
|
202
|
-
if (forceDual) decision.dualBrain = true;
|
|
203
|
-
|
|
204
|
-
if (verbose) {
|
|
205
|
-
const mode = forceDual ? 'dual-brain' : `${decision.provider}/${decision.model}`;
|
|
206
|
-
console.log(`[playbook] Step "${step.id}" → ${mode} (${decision.tier})`);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Gate: log and continue (blocking gates are a future concern)
|
|
210
|
-
if (step.gate) {
|
|
211
|
-
console.log(`[playbook] Gate "${step.gate}" — checking (non-blocking in this version)`);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
let result;
|
|
215
|
-
try {
|
|
216
|
-
if (forceDual) {
|
|
217
|
-
result = await dispatchDualBrain({ decision, prompt: stepPrompt, files, cwd, dryRun });
|
|
218
|
-
result = {
|
|
219
|
-
status: result.consensus === 'both-failed' ? 'failed' : 'completed',
|
|
220
|
-
summary: result.claude?.summary ?? result.openai?.summary ?? '(dual-brain)',
|
|
221
|
-
dualBrain: result,
|
|
222
|
-
};
|
|
223
|
-
} else {
|
|
224
|
-
result = await dispatch({ decision, prompt: stepPrompt, files, cwd, dryRun });
|
|
225
|
-
}
|
|
226
|
-
} catch (err) {
|
|
227
|
-
result = { status: 'error', summary: err.message, error: err.message };
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const stepResult = {
|
|
231
|
-
stepId: step.id,
|
|
232
|
-
title: step.title,
|
|
233
|
-
tier: step.tier ?? 'execute',
|
|
234
|
-
dualBrain: forceDual,
|
|
235
|
-
status: result.status,
|
|
236
|
-
summary: result.summary ?? null,
|
|
237
|
-
error: result.error ?? null,
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
results.push(stepResult);
|
|
241
|
-
priorOuts.push({ stepId: step.id, summary: result.summary });
|
|
242
|
-
|
|
243
|
-
if (verbose) {
|
|
244
|
-
console.log(`[playbook] → ${stepResult.status}: ${stepResult.summary ?? stepResult.error}`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const passed = results.filter(r => r.status === 'completed' || r.status === 'dry-run').length;
|
|
249
|
-
const failed = results.filter(r => r.status === 'failed' || r.status === 'error').length;
|
|
250
|
-
const summary = `Playbook "${playbook.name}" finished: ${passed}/${steps.length} steps passed${failed ? `, ${failed} failed` : ''}.`;
|
|
251
|
-
|
|
252
|
-
if (!dryRun) {
|
|
253
|
-
try { createRunArtifact(runId, results, cwd); } catch {}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return { steps: results, summary, runId };
|
|
257
|
-
}
|
package/src/pr-agent.mjs
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
// pr-agent.mjs — PR workflow module for dual-brain.
|
|
2
|
-
// Provides issue/task → branch → implement → PR automation using the gh CLI.
|
|
3
|
-
// Exports: hasGitHub, getBranchInfo, createBranch, getDiffSummary, createPR,
|
|
4
|
-
// listPRs, getPRDetails, buildPRBody
|
|
5
|
-
|
|
6
|
-
import { execSync } from 'node:child_process';
|
|
7
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
-
import { join } from 'node:path';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Check if gh CLI is available and authenticated.
|
|
12
|
-
* @returns {{ available: boolean, authenticated: boolean }}
|
|
13
|
-
*/
|
|
14
|
-
export function hasGitHub() {
|
|
15
|
-
try {
|
|
16
|
-
execSync('gh auth status', { stdio: 'pipe', timeout: 5000 });
|
|
17
|
-
return { available: true, authenticated: true };
|
|
18
|
-
} catch {
|
|
19
|
-
try {
|
|
20
|
-
execSync('which gh', { stdio: 'pipe', timeout: 2000 });
|
|
21
|
-
return { available: true, authenticated: false };
|
|
22
|
-
} catch {
|
|
23
|
-
return { available: false, authenticated: false };
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Get current branch info including distance from default branch.
|
|
30
|
-
* @param {string} [cwd]
|
|
31
|
-
* @returns {{ branch: string|null, defaultBranch: string, ahead: number, behind: number, isDefault: boolean }}
|
|
32
|
-
*/
|
|
33
|
-
export function getBranchInfo(cwd) {
|
|
34
|
-
const dir = cwd ?? process.cwd();
|
|
35
|
-
try {
|
|
36
|
-
const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: dir, encoding: 'utf8', timeout: 3000 }).trim();
|
|
37
|
-
const defaultBranch = execSync(
|
|
38
|
-
'git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || echo refs/remotes/origin/main',
|
|
39
|
-
{ cwd: dir, encoding: 'utf8', timeout: 3000 },
|
|
40
|
-
).trim().replace('refs/remotes/origin/', '');
|
|
41
|
-
const ahead = parseInt(
|
|
42
|
-
execSync(`git rev-list --count ${defaultBranch}..HEAD 2>/dev/null || echo 0`, { cwd: dir, encoding: 'utf8', timeout: 3000 }).trim(),
|
|
43
|
-
) || 0;
|
|
44
|
-
const behind = parseInt(
|
|
45
|
-
execSync(`git rev-list --count HEAD..${defaultBranch} 2>/dev/null || echo 0`, { cwd: dir, encoding: 'utf8', timeout: 3000 }).trim(),
|
|
46
|
-
) || 0;
|
|
47
|
-
return { branch, defaultBranch, ahead, behind, isDefault: branch === defaultBranch };
|
|
48
|
-
} catch {
|
|
49
|
-
return { branch: null, defaultBranch: 'main', ahead: 0, behind: 0, isDefault: true };
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Create a feature branch from a task description.
|
|
55
|
-
* Branch name is prefixed with "db/" and slugified from the description.
|
|
56
|
-
* @param {string} taskDescription
|
|
57
|
-
* @param {string} [cwd]
|
|
58
|
-
* @returns {{ success: boolean, branch: string, error?: string }}
|
|
59
|
-
*/
|
|
60
|
-
export function createBranch(taskDescription, cwd) {
|
|
61
|
-
const dir = cwd ?? process.cwd();
|
|
62
|
-
const slug = taskDescription
|
|
63
|
-
.toLowerCase()
|
|
64
|
-
.replace(/[^a-z0-9\s-]/g, '')
|
|
65
|
-
.trim()
|
|
66
|
-
.replace(/\s+/g, '-')
|
|
67
|
-
.slice(0, 50);
|
|
68
|
-
const branchName = `db/${slug}`;
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
execSync(`git checkout -b "${branchName}"`, { cwd: dir, stdio: 'pipe', timeout: 5000 });
|
|
72
|
-
return { success: true, branch: branchName };
|
|
73
|
-
} catch (err) {
|
|
74
|
-
return { success: false, branch: branchName, error: err.message };
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Get diff summary for PR description generation.
|
|
80
|
-
* @param {string} baseBranch Base branch name (e.g. 'main')
|
|
81
|
-
* @param {string} [cwd]
|
|
82
|
-
* @returns {{ stat: string, files: string[], summary: string, fileCount: number }}
|
|
83
|
-
*/
|
|
84
|
-
export function getDiffSummary(baseBranch, cwd) {
|
|
85
|
-
const dir = cwd ?? process.cwd();
|
|
86
|
-
try {
|
|
87
|
-
const stat = execSync(`git diff --stat ${baseBranch}...HEAD`, { cwd: dir, encoding: 'utf8', timeout: 10000 }).trim();
|
|
88
|
-
const files = execSync(`git diff --name-only ${baseBranch}...HEAD`, { cwd: dir, encoding: 'utf8', timeout: 5000 })
|
|
89
|
-
.trim()
|
|
90
|
-
.split('\n')
|
|
91
|
-
.filter(Boolean);
|
|
92
|
-
const summary = execSync(`git diff --shortstat ${baseBranch}...HEAD`, { cwd: dir, encoding: 'utf8', timeout: 5000 }).trim();
|
|
93
|
-
return { stat, files, summary, fileCount: files.length };
|
|
94
|
-
} catch {
|
|
95
|
-
return { stat: '', files: [], summary: '', fileCount: 0 };
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Create a PR using the gh CLI. Pushes the current branch first.
|
|
101
|
-
* @param {object} opts
|
|
102
|
-
* @param {string} opts.title
|
|
103
|
-
* @param {string} opts.body
|
|
104
|
-
* @param {string} [opts.baseBranch]
|
|
105
|
-
* @param {boolean} [opts.draft]
|
|
106
|
-
* @param {string[]} [opts.labels]
|
|
107
|
-
* @param {string} [opts.cwd]
|
|
108
|
-
* @returns {{ success: boolean, url?: string, error?: string }}
|
|
109
|
-
*/
|
|
110
|
-
export function createPR(opts) {
|
|
111
|
-
const { title, body, baseBranch, draft, labels, cwd } = opts;
|
|
112
|
-
const dir = cwd ?? process.cwd();
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
// Push current branch to origin first
|
|
116
|
-
const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: dir, encoding: 'utf8', timeout: 3000 }).trim();
|
|
117
|
-
execSync(`git push -u origin "${branch}"`, { cwd: dir, stdio: 'pipe', timeout: 30000 });
|
|
118
|
-
|
|
119
|
-
// Build gh pr create args
|
|
120
|
-
const args = ['gh', 'pr', 'create', '--title', JSON.stringify(title), '--body', JSON.stringify(body)];
|
|
121
|
-
if (baseBranch) args.push('--base', baseBranch);
|
|
122
|
-
if (draft) args.push('--draft');
|
|
123
|
-
if (labels?.length) args.push('--label', labels.join(','));
|
|
124
|
-
|
|
125
|
-
const result = execSync(args.join(' '), { cwd: dir, encoding: 'utf8', timeout: 30000 });
|
|
126
|
-
const url = result.trim();
|
|
127
|
-
return { success: true, url };
|
|
128
|
-
} catch (err) {
|
|
129
|
-
return { success: false, error: err.message };
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* List open (or other state) PRs for the current repo.
|
|
135
|
-
* @param {string} [cwd]
|
|
136
|
-
* @param {object} [opts]
|
|
137
|
-
* @param {'open'|'closed'|'merged'|'all'} [opts.state]
|
|
138
|
-
* @param {number} [opts.limit]
|
|
139
|
-
* @returns {object[]}
|
|
140
|
-
*/
|
|
141
|
-
export function listPRs(cwd, opts = {}) {
|
|
142
|
-
const dir = cwd ?? process.cwd();
|
|
143
|
-
const { state = 'open', limit = 10 } = opts;
|
|
144
|
-
try {
|
|
145
|
-
const json = execSync(
|
|
146
|
-
`gh pr list --state ${state} --limit ${limit} --json number,title,headRefName,author,createdAt,isDraft`,
|
|
147
|
-
{ cwd: dir, encoding: 'utf8', timeout: 10000 },
|
|
148
|
-
);
|
|
149
|
-
return JSON.parse(json);
|
|
150
|
-
} catch {
|
|
151
|
-
return [];
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Get PR details including diff stats, comments, and CI checks.
|
|
157
|
-
* @param {number|string} prNumber
|
|
158
|
-
* @param {string} [cwd]
|
|
159
|
-
* @returns {object|null}
|
|
160
|
-
*/
|
|
161
|
-
export function getPRDetails(prNumber, cwd) {
|
|
162
|
-
const dir = cwd ?? process.cwd();
|
|
163
|
-
try {
|
|
164
|
-
const json = execSync(
|
|
165
|
-
`gh pr view ${prNumber} --json title,body,headRefName,baseRefName,state,additions,deletions,changedFiles,reviews,comments,statusCheckRollup`,
|
|
166
|
-
{ cwd: dir, encoding: 'utf8', timeout: 10000 },
|
|
167
|
-
);
|
|
168
|
-
return JSON.parse(json);
|
|
169
|
-
} catch {
|
|
170
|
-
return null;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Build a PR body from a task description and dispatch results.
|
|
176
|
-
* @param {string} taskDescription
|
|
177
|
-
* @param {object} results Dispatch result object (filesChanged, testsRun, decisions, etc.)
|
|
178
|
-
* @returns {string}
|
|
179
|
-
*/
|
|
180
|
-
export function buildPRBody(taskDescription, results) {
|
|
181
|
-
const lines = [];
|
|
182
|
-
lines.push('## Summary');
|
|
183
|
-
lines.push(taskDescription);
|
|
184
|
-
lines.push('');
|
|
185
|
-
|
|
186
|
-
if (results.filesChanged?.length) {
|
|
187
|
-
lines.push('## Changes');
|
|
188
|
-
for (const f of results.filesChanged) {
|
|
189
|
-
lines.push(`- \`${f}\``);
|
|
190
|
-
}
|
|
191
|
-
lines.push('');
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (results.testsRun?.length) {
|
|
195
|
-
lines.push('## Tests');
|
|
196
|
-
for (const t of results.testsRun) {
|
|
197
|
-
lines.push(`- ${t}`);
|
|
198
|
-
}
|
|
199
|
-
lines.push('');
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (results.decisions?.length) {
|
|
203
|
-
lines.push('## Routing');
|
|
204
|
-
for (const d of results.decisions) {
|
|
205
|
-
lines.push(`- ${d}`);
|
|
206
|
-
}
|
|
207
|
-
lines.push('');
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
lines.push('---');
|
|
211
|
-
lines.push('Generated by [dual-brain](https://npmjs.com/package/dual-brain)');
|
|
212
|
-
|
|
213
|
-
return lines.join('\n');
|
|
214
|
-
}
|