dual-brain 4.6.0 → 4.7.1
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/CLAUDE.md +35 -130
- package/README.md +34 -179
- package/hooks/control-panel.mjs +379 -8
- package/hooks/cost-logger.mjs +11 -53
- package/hooks/cost-report.mjs +126 -65
- package/hooks/decision-ledger.mjs +3 -53
- package/hooks/dual-brain-review.mjs +25 -261
- package/hooks/dual-brain-think.mjs +37 -300
- package/hooks/enforce-tier.mjs +93 -265
- package/hooks/failure-detector.mjs +1 -3
- package/hooks/gpt-work-dispatcher.mjs +153 -12
- package/hooks/health-check.mjs +25 -17
- package/hooks/quality-gate.mjs +11 -6
- package/hooks/risk-classifier.mjs +2 -135
- package/hooks/session-report.mjs +71 -41
- package/hooks/summary-checkpoint.mjs +8 -35
- package/hooks/test-orchestrator.mjs +31 -2080
- package/install.mjs +628 -1557
- package/orchestrator.json +96 -73
- package/package.json +2 -7
- package/hooks/agent-chains.mjs +0 -369
- package/hooks/agent-templates.mjs +0 -441
- package/hooks/atomic-write.mjs +0 -109
- package/hooks/config-validator.mjs +0 -156
- package/hooks/confirmation-policy.mjs +0 -167
- package/hooks/error-channel.mjs +0 -68
- package/hooks/ship-captain.mjs +0 -1176
- package/hooks/ship-gate.mjs +0 -971
package/orchestrator.json
CHANGED
|
@@ -3,87 +3,66 @@
|
|
|
3
3
|
"claude": {
|
|
4
4
|
"plan": "$100",
|
|
5
5
|
"models": {
|
|
6
|
-
"opus":
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"output_per_mtok": 25.0,
|
|
10
|
-
"context_window": 1000000,
|
|
11
|
-
"max_output": 128000,
|
|
12
|
-
"model_id": "claude-opus-4-6",
|
|
13
|
-
"strengths": ["agentic coding", "complex reasoning", "tool use", "error recovery", "architecture decisions"],
|
|
14
|
-
"weaknesses": ["higher latency", "tokenizer tax on 4.7 (12-35% more tokens)"],
|
|
15
|
-
"best_for": "architecture, security review, complex debugging, multi-step planning",
|
|
16
|
-
"avoid_for": "simple file reads, grep, formatting — wasteful at this tier"
|
|
17
|
-
},
|
|
18
|
-
"sonnet": {
|
|
19
|
-
"tier": "execute",
|
|
20
|
-
"input_per_mtok": 3.0,
|
|
21
|
-
"output_per_mtok": 15.0,
|
|
22
|
-
"context_window": 1000000,
|
|
23
|
-
"max_output": 64000,
|
|
24
|
-
"model_id": "claude-sonnet-4-6",
|
|
25
|
-
"strengths": ["best speed/intelligence ratio", "precise minimal diffs", "1M context", "extended thinking"],
|
|
26
|
-
"weaknesses": ["less reliable on complex multi-step reasoning than Opus"],
|
|
27
|
-
"best_for": "implementation, refactoring, test writing, code edits, git operations",
|
|
28
|
-
"avoid_for": "architecture decisions, security audits — upgrade to think tier"
|
|
29
|
-
},
|
|
30
|
-
"haiku": {
|
|
31
|
-
"tier": "search",
|
|
32
|
-
"input_per_mtok": 1.0,
|
|
33
|
-
"output_per_mtok": 5.0,
|
|
34
|
-
"context_window": 200000,
|
|
35
|
-
"max_output": 64000,
|
|
36
|
-
"model_id": "claude-haiku-4-5-20251001",
|
|
37
|
-
"strengths": ["fastest latency", "cheapest", "good enough for read-only tasks"],
|
|
38
|
-
"weaknesses": ["200k context (vs 1M for others)", "weaker reasoning", "older knowledge cutoff"],
|
|
39
|
-
"best_for": "file lookups, grep, explore, read-only research, listing files",
|
|
40
|
-
"avoid_for": "any task requiring edits, reasoning, or judgment"
|
|
41
|
-
}
|
|
6
|
+
"opus": { "tier": "think", "input_per_mtok": 5.0, "output_per_mtok": 25.0, "context_window": 1000000, "max_output": 128000 },
|
|
7
|
+
"sonnet": { "tier": "execute", "input_per_mtok": 3.0, "output_per_mtok": 15.0, "context_window": 1000000, "max_output": 64000 },
|
|
8
|
+
"haiku": { "tier": "search", "input_per_mtok": 1.0, "output_per_mtok": 5.0, "context_window": 200000, "max_output": 64000 }
|
|
42
9
|
}
|
|
43
10
|
},
|
|
44
11
|
"openai": {
|
|
45
12
|
"plan": "$100",
|
|
46
13
|
"models": {
|
|
47
|
-
"gpt-5.5":
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"output_per_mtok": 30.0,
|
|
51
|
-
"context_window": 1000000,
|
|
52
|
-
"max_output": 128000,
|
|
53
|
-
"model_id": "gpt-5.5",
|
|
54
|
-
"strengths": ["complex reasoning", "fast interactive responses", "strong code review", "independent perspective from Claude"],
|
|
55
|
-
"weaknesses": ["different failure modes than Claude — feature not bug for dual-brain"],
|
|
56
|
-
"best_for": "independent code review (dual-brain), second opinions on architecture",
|
|
57
|
-
"codex_compatible": true
|
|
58
|
-
},
|
|
59
|
-
"gpt-5.4": {
|
|
60
|
-
"tier": "execute",
|
|
61
|
-
"input_per_mtok": 2.5,
|
|
62
|
-
"output_per_mtok": 15.0,
|
|
63
|
-
"context_window": 1000000,
|
|
64
|
-
"max_output": 128000,
|
|
65
|
-
"model_id": "gpt-5.4",
|
|
66
|
-
"strengths": ["good speed/cost ratio", "1M context", "strong instruction following"],
|
|
67
|
-
"weaknesses": ["less capable reasoning than gpt-5.5"],
|
|
68
|
-
"best_for": "implementation and execution tasks via Codex",
|
|
69
|
-
"codex_compatible": true
|
|
70
|
-
},
|
|
71
|
-
"gpt-4.1-mini": {
|
|
72
|
-
"tier": "search",
|
|
73
|
-
"input_per_mtok": 0.40,
|
|
74
|
-
"output_per_mtok": 1.60,
|
|
75
|
-
"context_window": 1047576,
|
|
76
|
-
"max_output": 32768,
|
|
77
|
-
"model_id": "gpt-4.1-mini",
|
|
78
|
-
"strengths": ["extremely cheap", "good instruction following", "1M context"],
|
|
79
|
-
"weaknesses": ["32k max output", "no reasoning chain"],
|
|
80
|
-
"best_for": "cheap search/lookup tasks via Codex",
|
|
81
|
-
"codex_compatible": true
|
|
82
|
-
}
|
|
14
|
+
"gpt-5.5": { "tier": "think", "input_per_mtok": 5.0, "output_per_mtok": 30.0, "context_window": 1000000, "max_output": 128000 },
|
|
15
|
+
"gpt-5.4": { "tier": "execute", "input_per_mtok": 2.5, "output_per_mtok": 15.0, "context_window": 1000000, "max_output": 128000 },
|
|
16
|
+
"gpt-4.1-mini": { "tier": "search", "input_per_mtok": 0.40, "output_per_mtok": 1.60, "context_window": 1047576, "max_output": 32768 }
|
|
83
17
|
}
|
|
84
18
|
}
|
|
85
19
|
},
|
|
86
20
|
|
|
21
|
+
"model_intelligence": {
|
|
22
|
+
"opus": {
|
|
23
|
+
"model_id": "claude-opus-4-6",
|
|
24
|
+
"strengths": ["agentic coding", "complex reasoning", "tool use", "error recovery", "architecture decisions"],
|
|
25
|
+
"weaknesses": ["higher latency", "tokenizer tax on 4.7 (12-35% more tokens)"],
|
|
26
|
+
"best_for": "architecture, security review, complex debugging, multi-step planning",
|
|
27
|
+
"avoid_for": "simple file reads, grep, formatting — wasteful at this tier"
|
|
28
|
+
},
|
|
29
|
+
"sonnet": {
|
|
30
|
+
"model_id": "claude-sonnet-4-6",
|
|
31
|
+
"strengths": ["best speed/intelligence ratio", "precise minimal diffs", "1M context", "extended thinking"],
|
|
32
|
+
"weaknesses": ["less reliable on complex multi-step reasoning than Opus"],
|
|
33
|
+
"best_for": "implementation, refactoring, test writing, code edits, git operations",
|
|
34
|
+
"avoid_for": "architecture decisions, security audits — upgrade to think tier"
|
|
35
|
+
},
|
|
36
|
+
"haiku": {
|
|
37
|
+
"model_id": "claude-haiku-4-5-20251001",
|
|
38
|
+
"strengths": ["fastest latency", "cheapest", "good enough for read-only tasks"],
|
|
39
|
+
"weaknesses": ["200k context (vs 1M for others)", "weaker reasoning", "older knowledge cutoff"],
|
|
40
|
+
"best_for": "file lookups, grep, explore, read-only research, listing files",
|
|
41
|
+
"avoid_for": "any task requiring edits, reasoning, or judgment"
|
|
42
|
+
},
|
|
43
|
+
"gpt-5.5": {
|
|
44
|
+
"model_id": "gpt-5.5",
|
|
45
|
+
"strengths": ["complex reasoning", "fast interactive responses", "strong code review", "independent perspective from Claude"],
|
|
46
|
+
"weaknesses": ["different failure modes than Claude — feature not bug for dual-brain"],
|
|
47
|
+
"best_for": "independent code review (dual-brain), second opinions on architecture",
|
|
48
|
+
"codex_compatible": true
|
|
49
|
+
},
|
|
50
|
+
"gpt-5.4": {
|
|
51
|
+
"model_id": "gpt-5.4",
|
|
52
|
+
"strengths": ["good speed/cost ratio", "1M context", "strong instruction following"],
|
|
53
|
+
"weaknesses": ["less capable reasoning than gpt-5.5"],
|
|
54
|
+
"best_for": "implementation and execution tasks via Codex",
|
|
55
|
+
"codex_compatible": true
|
|
56
|
+
},
|
|
57
|
+
"gpt-4.1-mini": {
|
|
58
|
+
"model_id": "gpt-4.1-mini",
|
|
59
|
+
"strengths": ["extremely cheap", "good instruction following", "1M context"],
|
|
60
|
+
"weaknesses": ["32k max output", "no reasoning chain"],
|
|
61
|
+
"best_for": "cheap search/lookup tasks via Codex",
|
|
62
|
+
"codex_compatible": true
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
|
|
87
66
|
"tiers": {
|
|
88
67
|
"search": {
|
|
89
68
|
"description": "Read-only lookups, exploration, grep, find, file reads",
|
|
@@ -121,6 +100,46 @@
|
|
|
121
100
|
"model_routing_note": "Claude Code model: param may be silently ignored (issue #43869). Set CLAUDE_CODE_SUBAGENT_MODEL env var as fallback if subagents all run on parent model."
|
|
122
101
|
},
|
|
123
102
|
|
|
103
|
+
"codex_skills": {
|
|
104
|
+
"review": {
|
|
105
|
+
"description": "Independent GPT code review via ChatGPT subscription",
|
|
106
|
+
"command": "node .claude/hooks/dual-brain-review.mjs",
|
|
107
|
+
"requires": "codex_auth"
|
|
108
|
+
},
|
|
109
|
+
"quality_gate": {
|
|
110
|
+
"description": "Config-driven quality gate with review artifacts",
|
|
111
|
+
"command": "node .claude/hooks/quality-gate.mjs"
|
|
112
|
+
},
|
|
113
|
+
"cost_report": {
|
|
114
|
+
"description": "Session activity and cost estimate by model tier",
|
|
115
|
+
"command": "node .claude/hooks/cost-report.mjs"
|
|
116
|
+
},
|
|
117
|
+
"test": {
|
|
118
|
+
"description": "Self-test harness for all orchestrator hooks",
|
|
119
|
+
"command": "node .claude/hooks/test-orchestrator.mjs"
|
|
120
|
+
},
|
|
121
|
+
"health_check": {
|
|
122
|
+
"description": "Verify all hooks are wired and system is healthy",
|
|
123
|
+
"command": "node .claude/hooks/health-check.mjs"
|
|
124
|
+
},
|
|
125
|
+
"session_report": {
|
|
126
|
+
"description": "Comprehensive session-end summary: activity by tier, routing compliance, quality gate status, data quality, drift warnings",
|
|
127
|
+
"command": "node .claude/hooks/session-report.mjs"
|
|
128
|
+
},
|
|
129
|
+
"setup_wizard": {
|
|
130
|
+
"description": "Interactive setup wizard — configures Claude Code hooks, dual-provider routing (Claude + OpenAI/Codex), subscription tiers, and cost-tracking settings",
|
|
131
|
+
"command": "node .claude/hooks/setup-wizard.mjs"
|
|
132
|
+
},
|
|
133
|
+
"gpt_dispatch": {
|
|
134
|
+
"description": "Dispatch execution tasks to GPT via Codex CLI",
|
|
135
|
+
"command": "node .claude/hooks/gpt-work-dispatcher.mjs"
|
|
136
|
+
},
|
|
137
|
+
"budget_balance": {
|
|
138
|
+
"description": "Show provider balance status and routing recommendations",
|
|
139
|
+
"command": "node .claude/hooks/budget-balancer.mjs"
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
|
|
124
143
|
"pricing_verified": "2026-05-13",
|
|
125
144
|
|
|
126
145
|
"budgets": {
|
|
@@ -134,7 +153,11 @@
|
|
|
134
153
|
"routing": {
|
|
135
154
|
"strategy": "hybrid-specialized-balanced",
|
|
136
155
|
"codex_startup_penalty_ms": 20000,
|
|
137
|
-
"min_codex_task_ms": 180000
|
|
156
|
+
"min_codex_task_ms": 180000,
|
|
157
|
+
"dual_thinking_permission_threshold": {
|
|
158
|
+
"estimated_tokens": 50000,
|
|
159
|
+
"agent_count": 2
|
|
160
|
+
}
|
|
138
161
|
},
|
|
139
162
|
|
|
140
163
|
"providers": {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dual-brain",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.7.1",
|
|
4
|
+
"description": "Dual-provider orchestration for Claude Code — tiered routing, budget balancing, and GPT dual-brain review across Claude + OpenAI subscriptions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"dual-brain": "./install.mjs"
|
|
@@ -25,11 +25,6 @@
|
|
|
25
25
|
"scripts": {
|
|
26
26
|
"test": "node hooks/test-orchestrator.mjs"
|
|
27
27
|
},
|
|
28
|
-
"dataTools": {
|
|
29
|
-
"extension": true,
|
|
30
|
-
"requires": "replit-tools",
|
|
31
|
-
"credit": "Built on data-tools by Steve Moraco"
|
|
32
|
-
},
|
|
33
28
|
"engines": {
|
|
34
29
|
"node": ">=20.0.0"
|
|
35
30
|
},
|
package/hooks/agent-chains.mjs
DELETED
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* agent-chains.mjs — Opinionated multi-step agent workflows.
|
|
4
|
-
*
|
|
5
|
-
* Three built-in chains that map to how developers actually work:
|
|
6
|
-
*
|
|
7
|
-
* explore-then-fix — Understand the problem, then fix it
|
|
8
|
-
* review-and-test — Review the code, then write tests for weak spots
|
|
9
|
-
* audit-and-plan — Audit the architecture, then create an execution plan
|
|
10
|
-
*
|
|
11
|
-
* Each chain:
|
|
12
|
-
* 1. Prints a banner showing the steps
|
|
13
|
-
* 2. Runs step 1 (capture output)
|
|
14
|
-
* 3. Prints step 1 results
|
|
15
|
-
* 4. Asks for confirmation (or auto-continues with --yes)
|
|
16
|
-
* 5. Runs step 2 with step 1 output as context
|
|
17
|
-
* 6. Prints final results
|
|
18
|
-
*
|
|
19
|
-
* Export: getChain(name), listChains()
|
|
20
|
-
* CLI: node agent-chains.mjs --list
|
|
21
|
-
* node agent-chains.mjs --run <chain> [flags]
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import { spawnSync } from 'child_process';
|
|
25
|
-
import { createInterface } from 'readline';
|
|
26
|
-
import { dirname, resolve } from 'path';
|
|
27
|
-
import { fileURLToPath } from 'url';
|
|
28
|
-
|
|
29
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
|
-
const TEMPLATES_SCRIPT = resolve(__dirname, 'agent-templates.mjs');
|
|
31
|
-
|
|
32
|
-
// ─── Chain Definitions ─────────────────────────────────────────────────────
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Built-in chains.
|
|
36
|
-
*
|
|
37
|
-
* Each chain has:
|
|
38
|
-
* name - unique identifier
|
|
39
|
-
* description - one-line description
|
|
40
|
-
* steps - array of step descriptors
|
|
41
|
-
* step.label - human-readable step label
|
|
42
|
-
* step.template - agent-templates.mjs template name
|
|
43
|
-
* step.tier - search | execute | think
|
|
44
|
-
* step.model - haiku | sonnet | opus
|
|
45
|
-
* step.args - function(flags, prevOutput) → args object for the template
|
|
46
|
-
* step.stop_after - if true, pause here for confirmation before continuing
|
|
47
|
-
* step.stop_label - label for the stop-point prompt
|
|
48
|
-
*/
|
|
49
|
-
export const CHAINS = {
|
|
50
|
-
'explore-then-fix': {
|
|
51
|
-
name: 'explore-then-fix',
|
|
52
|
-
description: 'Understand the problem, then fix it',
|
|
53
|
-
steps: [
|
|
54
|
-
{
|
|
55
|
-
label: 'Step 1: Explore',
|
|
56
|
-
template: 'explorer',
|
|
57
|
-
tier: 'search',
|
|
58
|
-
model: 'haiku',
|
|
59
|
-
args: (flags) => ({
|
|
60
|
-
question: flags.question,
|
|
61
|
-
scope: flags.scope,
|
|
62
|
-
}),
|
|
63
|
-
stop_after: true,
|
|
64
|
-
stop_label: 'Exploration complete. Review findings above, then confirm the fix.',
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
label: 'Step 2: Fix',
|
|
68
|
-
template: 'bug-hunter',
|
|
69
|
-
tier: 'execute',
|
|
70
|
-
model: 'sonnet',
|
|
71
|
-
args: (flags, prevOutput) => ({
|
|
72
|
-
question: flags.question,
|
|
73
|
-
scope: flags.scope,
|
|
74
|
-
context: prevOutput,
|
|
75
|
-
}),
|
|
76
|
-
},
|
|
77
|
-
],
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
'review-and-test': {
|
|
81
|
-
name: 'review-and-test',
|
|
82
|
-
description: 'Review the code, then write tests for weak spots',
|
|
83
|
-
steps: [
|
|
84
|
-
{
|
|
85
|
-
label: 'Step 1: Security Review',
|
|
86
|
-
template: 'security-review',
|
|
87
|
-
tier: 'think',
|
|
88
|
-
model: 'opus',
|
|
89
|
-
args: (flags) => ({
|
|
90
|
-
scope: flags.scope,
|
|
91
|
-
file: flags.file,
|
|
92
|
-
}),
|
|
93
|
-
stop_after: true,
|
|
94
|
-
stop_label: 'Review complete. Review findings above, then confirm test writing.',
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
label: 'Step 2: Write Tests',
|
|
98
|
-
template: 'test-writer',
|
|
99
|
-
tier: 'execute',
|
|
100
|
-
model: 'sonnet',
|
|
101
|
-
args: (flags, prevOutput) => ({
|
|
102
|
-
scope: flags.scope,
|
|
103
|
-
file: flags.file,
|
|
104
|
-
context: prevOutput,
|
|
105
|
-
}),
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
},
|
|
109
|
-
|
|
110
|
-
'audit-and-plan': {
|
|
111
|
-
name: 'audit-and-plan',
|
|
112
|
-
description: 'Audit the architecture, then create an execution plan',
|
|
113
|
-
steps: [
|
|
114
|
-
{
|
|
115
|
-
label: 'Step 1: Architecture Audit',
|
|
116
|
-
template: 'explorer',
|
|
117
|
-
tier: 'search',
|
|
118
|
-
model: 'haiku',
|
|
119
|
-
args: (flags) => ({
|
|
120
|
-
question: flags.question,
|
|
121
|
-
scope: flags.scope,
|
|
122
|
-
}),
|
|
123
|
-
stop_after: true,
|
|
124
|
-
stop_label: 'Audit complete. Review findings above, then confirm planning.',
|
|
125
|
-
},
|
|
126
|
-
{
|
|
127
|
-
label: 'Step 2: Create Plan',
|
|
128
|
-
template: null, // Think tier — runs via plan-generator concept inline
|
|
129
|
-
tier: 'think',
|
|
130
|
-
model: 'opus',
|
|
131
|
-
args: (flags, prevOutput) => ({
|
|
132
|
-
question: flags.question,
|
|
133
|
-
scope: flags.scope,
|
|
134
|
-
context: prevOutput,
|
|
135
|
-
}),
|
|
136
|
-
// Uses a custom prompt builder instead of an agent template
|
|
137
|
-
custom_prompt: (flags, prevOutput) => {
|
|
138
|
-
const scopeLine = flags.scope ? `\nScope: ${flags.scope}` : '';
|
|
139
|
-
return [
|
|
140
|
-
`You are an architect. Based on the audit findings below, create a detailed execution plan.`,
|
|
141
|
-
``,
|
|
142
|
-
`Question / Goal: ${flags.question || 'Improve the architecture based on audit findings'}`,
|
|
143
|
-
scopeLine,
|
|
144
|
-
``,
|
|
145
|
-
`Audit findings:`,
|
|
146
|
-
prevOutput,
|
|
147
|
-
``,
|
|
148
|
-
`Produce a structured plan with:`,
|
|
149
|
-
`- Decision: the recommended approach`,
|
|
150
|
-
`- Rationale: why this approach over alternatives`,
|
|
151
|
-
`- Alternatives considered (and why rejected)`,
|
|
152
|
-
`- Risks and mitigations`,
|
|
153
|
-
`- Verification plan: how to confirm success`,
|
|
154
|
-
`- Task table: dependency-ordered list of concrete tasks with tier/risk`,
|
|
155
|
-
].join('\n');
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
],
|
|
159
|
-
},
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
// ─── Exports ───────────────────────────────────────────────────────────────
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Get a chain by name. Returns null if not found.
|
|
166
|
-
*/
|
|
167
|
-
export function getChain(name) {
|
|
168
|
-
return CHAINS[name] || null;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* List all chains as { name, description, steps }.
|
|
173
|
-
*/
|
|
174
|
-
export function listChains() {
|
|
175
|
-
return Object.values(CHAINS).map(({ name, description, steps }) => ({
|
|
176
|
-
name,
|
|
177
|
-
description,
|
|
178
|
-
steps: steps.map(s => ({
|
|
179
|
-
label: s.label,
|
|
180
|
-
template: s.template,
|
|
181
|
-
tier: s.tier,
|
|
182
|
-
model: s.model,
|
|
183
|
-
})),
|
|
184
|
-
}));
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ─── Chain Execution ───────────────────────────────────────────────────────
|
|
188
|
-
|
|
189
|
-
function banner(chain) {
|
|
190
|
-
const width = 60;
|
|
191
|
-
const line = '─'.repeat(width);
|
|
192
|
-
console.log(`\n ${line}`);
|
|
193
|
-
console.log(` Chain: ${chain.name} — ${chain.description}`);
|
|
194
|
-
console.log(` ${line}`);
|
|
195
|
-
for (let i = 0; i < chain.steps.length; i++) {
|
|
196
|
-
const s = chain.steps[i];
|
|
197
|
-
const model = s.model || (s.tier === 'think' ? 'opus' : s.tier === 'search' ? 'haiku' : 'sonnet');
|
|
198
|
-
const tmpl = s.template || 'custom';
|
|
199
|
-
console.log(` ${i + 1}. ${s.label} [${s.tier} / ${model} / ${tmpl}]`);
|
|
200
|
-
}
|
|
201
|
-
console.log(` ${line}\n`);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function separator(label) {
|
|
205
|
-
const width = 60;
|
|
206
|
-
const line = '─'.repeat(width);
|
|
207
|
-
console.log(`\n ${line}`);
|
|
208
|
-
if (label) console.log(` ${label}`);
|
|
209
|
-
console.log(` ${line}\n`);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function runTemplate(templateName, templateArgs) {
|
|
213
|
-
const argsList = ['--run', templateName];
|
|
214
|
-
if (templateArgs.question) argsList.push('--question', templateArgs.question);
|
|
215
|
-
if (templateArgs.scope) argsList.push('--scope', templateArgs.scope);
|
|
216
|
-
if (templateArgs.file) argsList.push('--file', templateArgs.file);
|
|
217
|
-
if (templateArgs.context) argsList.push('--context', templateArgs.context);
|
|
218
|
-
|
|
219
|
-
const result = spawnSync(process.execPath, [TEMPLATES_SCRIPT, ...argsList], {
|
|
220
|
-
encoding: 'utf8',
|
|
221
|
-
stdio: ['pipe', 'pipe', 'inherit'],
|
|
222
|
-
timeout: 30_000,
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
output: result.stdout || '',
|
|
227
|
-
status: result.status,
|
|
228
|
-
stderr: result.stderr || '',
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function runCustomStep(step, flags, prevOutput) {
|
|
233
|
-
const prompt = step.custom_prompt(flags, prevOutput);
|
|
234
|
-
const model = step.model || 'opus';
|
|
235
|
-
const tier = step.tier || 'think';
|
|
236
|
-
|
|
237
|
-
console.log(`\n [${step.label}] tier=${tier} model=${model} custom\n`);
|
|
238
|
-
console.log('─'.repeat(60));
|
|
239
|
-
console.log(prompt);
|
|
240
|
-
console.log('─'.repeat(60));
|
|
241
|
-
|
|
242
|
-
return {
|
|
243
|
-
output: prompt,
|
|
244
|
-
status: 0,
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
async function askConfirmation(stopLabel) {
|
|
249
|
-
console.log(`\n ${stopLabel}`);
|
|
250
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
251
|
-
return new Promise((resolve) => {
|
|
252
|
-
rl.question('\n Continue to step 2? [Y/n] ', (answer) => {
|
|
253
|
-
rl.close();
|
|
254
|
-
const ans = answer.trim().toLowerCase();
|
|
255
|
-
resolve(ans === '' || ans === 'y' || ans === 'yes');
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
async function executeChain(chain, flags) {
|
|
261
|
-
banner(chain);
|
|
262
|
-
|
|
263
|
-
let prevOutput = '';
|
|
264
|
-
|
|
265
|
-
for (let i = 0; i < chain.steps.length; i++) {
|
|
266
|
-
const step = chain.steps[i];
|
|
267
|
-
const isLast = i === chain.steps.length - 1;
|
|
268
|
-
|
|
269
|
-
console.log(`\n Running ${step.label}...`);
|
|
270
|
-
|
|
271
|
-
let result;
|
|
272
|
-
if (step.template) {
|
|
273
|
-
const templateArgs = step.args(flags, prevOutput);
|
|
274
|
-
result = runTemplate(step.template, templateArgs);
|
|
275
|
-
} else if (step.custom_prompt) {
|
|
276
|
-
result = runCustomStep(step, flags, prevOutput);
|
|
277
|
-
} else {
|
|
278
|
-
console.error(` Step "${step.label}" has no template or custom_prompt.`);
|
|
279
|
-
process.exit(1);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
if (result.status !== 0) {
|
|
283
|
-
console.error(`\n Step failed (exit ${result.status}). Chain aborted.`);
|
|
284
|
-
process.exit(result.status || 1);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Print step output
|
|
288
|
-
separator(`Results: ${step.label}`);
|
|
289
|
-
if (result.output.trim()) {
|
|
290
|
-
console.log(result.output);
|
|
291
|
-
} else {
|
|
292
|
-
console.log(' (no output)');
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
prevOutput = result.output;
|
|
296
|
-
|
|
297
|
-
// Stop point: ask for confirmation before next step
|
|
298
|
-
if (step.stop_after && !isLast) {
|
|
299
|
-
if (flags.yes) {
|
|
300
|
-
console.log(`\n [--yes] Auto-continuing to next step...`);
|
|
301
|
-
} else {
|
|
302
|
-
const confirmed = await askConfirmation(step.stop_label || `Step ${i + 1} complete.`);
|
|
303
|
-
if (!confirmed) {
|
|
304
|
-
console.log('\n Aborted by user. Findings saved above.\n');
|
|
305
|
-
process.exit(0);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
separator('Chain Complete');
|
|
312
|
-
console.log(' All steps finished. Review results above.\n');
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// ─── CLI ───────────────────────────────────────────────────────────────────
|
|
316
|
-
|
|
317
|
-
function parseArgs(argv) {
|
|
318
|
-
const flags = { yes: false };
|
|
319
|
-
for (let i = 0; i < argv.length; i++) {
|
|
320
|
-
const a = argv[i];
|
|
321
|
-
if (a === '--list') flags.list = true;
|
|
322
|
-
else if (a === '--run') flags.run = argv[++i];
|
|
323
|
-
else if (a === '--question') flags.question = argv[++i];
|
|
324
|
-
else if (a === '--scope') flags.scope = argv[++i];
|
|
325
|
-
else if (a === '--file') flags.file = argv[++i];
|
|
326
|
-
else if (a === '--yes' || a === '-y') flags.yes = true;
|
|
327
|
-
}
|
|
328
|
-
return flags;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
|
|
332
|
-
const flags = parseArgs(process.argv.slice(2));
|
|
333
|
-
|
|
334
|
-
if (flags.list) {
|
|
335
|
-
console.log('\n Agent Chains:\n');
|
|
336
|
-
for (const c of listChains()) {
|
|
337
|
-
console.log(` ${c.name.padEnd(22)} ${c.description}`);
|
|
338
|
-
for (const s of c.steps) {
|
|
339
|
-
const tmpl = s.template || 'custom';
|
|
340
|
-
console.log(` ${s.label.padEnd(30)} [${s.tier}/${s.model}/${tmpl}]`);
|
|
341
|
-
}
|
|
342
|
-
console.log('');
|
|
343
|
-
}
|
|
344
|
-
process.exit(0);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (flags.run) {
|
|
348
|
-
const chain = getChain(flags.run);
|
|
349
|
-
if (!chain) {
|
|
350
|
-
console.error(` Unknown chain: ${flags.run}`);
|
|
351
|
-
console.error(` Available: ${Object.keys(CHAINS).join(', ')}`);
|
|
352
|
-
process.exit(1);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
executeChain(chain, flags).catch((err) => {
|
|
356
|
-
console.error(` Chain error: ${err.message}`);
|
|
357
|
-
process.exit(1);
|
|
358
|
-
});
|
|
359
|
-
} else {
|
|
360
|
-
console.log(`
|
|
361
|
-
Usage:
|
|
362
|
-
node agent-chains.mjs --list
|
|
363
|
-
node agent-chains.mjs --run explore-then-fix --question "what's wrong with auth" [--scope "src/auth"] [--yes]
|
|
364
|
-
node agent-chains.mjs --run review-and-test [--scope "src/api"] [--file "src/api.ts"] [--yes]
|
|
365
|
-
node agent-chains.mjs --run audit-and-plan --question "how should we restructure auth" [--scope "src/"] [--yes]
|
|
366
|
-
`);
|
|
367
|
-
process.exit(0);
|
|
368
|
-
}
|
|
369
|
-
}
|