opencode-mad 0.3.0 → 0.3.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/README.md +238 -235
- package/install.js +1 -1
- package/package.json +36 -36
- package/plugins/mad-plugin.ts +54 -40
package/README.md
CHANGED
|
@@ -1,235 +1,238 @@
|
|
|
1
|
-
# opencode-mad
|
|
2
|
-
|
|
3
|
-
**Multi-Agent Dev (MAD)** - Parallel development orchestration plugin for [OpenCode](https://opencode.ai).
|
|
4
|
-
|
|
5
|
-
Decompose complex tasks into parallelizable subtasks, each running in isolated git worktrees with dedicated AI subagents.
|
|
6
|
-
|
|
7
|
-
## Features
|
|
8
|
-
|
|
9
|
-
- **Smart Planning** - Orchestrator asks clarifying questions before coding
|
|
10
|
-
- **File Ownership** - Each agent has exclusive files, preventing merge conflicts
|
|
11
|
-
- **Parallel Execution** - Multiple developers work simultaneously in git worktrees
|
|
12
|
-
- **Automated Testing** - Tester agent validates code before merge
|
|
13
|
-
- **Conflict Resolution** - Dedicated merger agent handles git conflicts
|
|
14
|
-
- **Integration Fixes** - Fixer agent ensures everything works together
|
|
15
|
-
|
|
16
|
-
## Installation
|
|
17
|
-
|
|
18
|
-
### Option 1: npx (Recommended)
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
# Install to current project
|
|
22
|
-
npx opencode-mad install
|
|
23
|
-
|
|
24
|
-
# Or install globally (all projects)
|
|
25
|
-
npx opencode-mad install -g
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
#
|
|
41
|
-
cp -r opencode-mad/agents
|
|
42
|
-
cp -r opencode-mad/commands
|
|
43
|
-
cp -r opencode-mad/plugins
|
|
44
|
-
cp -r opencode-mad/skills
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
│ ├──
|
|
59
|
-
│ ├──
|
|
60
|
-
│ │
|
|
61
|
-
│
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
│
|
|
122
|
-
|
|
123
|
-
│
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
│
|
|
129
|
-
│
|
|
130
|
-
|
|
131
|
-
│
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
│
|
|
139
|
-
│
|
|
140
|
-
|
|
141
|
-
│
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
│
|
|
148
|
-
│ -
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
│
|
|
155
|
-
│ -
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
|
184
|
-
|
|
185
|
-
| `
|
|
186
|
-
| `
|
|
187
|
-
| `
|
|
188
|
-
| `
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
1
|
+
# opencode-mad
|
|
2
|
+
|
|
3
|
+
**Multi-Agent Dev (MAD)** - Parallel development orchestration plugin for [OpenCode](https://opencode.ai).
|
|
4
|
+
|
|
5
|
+
Decompose complex tasks into parallelizable subtasks, each running in isolated git worktrees with dedicated AI subagents.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Smart Planning** - Orchestrator asks clarifying questions before coding
|
|
10
|
+
- **File Ownership** - Each agent has exclusive files, preventing merge conflicts
|
|
11
|
+
- **Parallel Execution** - Multiple developers work simultaneously in git worktrees
|
|
12
|
+
- **Automated Testing** - Tester agent validates code before merge
|
|
13
|
+
- **Conflict Resolution** - Dedicated merger agent handles git conflicts
|
|
14
|
+
- **Integration Fixes** - Fixer agent ensures everything works together
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
### Option 1: npx (Recommended)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Install to current project
|
|
22
|
+
npx opencode-mad install
|
|
23
|
+
|
|
24
|
+
# Or install globally (all projects)
|
|
25
|
+
npx opencode-mad install -g
|
|
26
|
+
|
|
27
|
+
# Update existing installation
|
|
28
|
+
npx opencode-mad update -g
|
|
29
|
+
|
|
30
|
+
# Check version
|
|
31
|
+
npx opencode-mad version
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Option 2: Manual copy
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# Clone the repo
|
|
38
|
+
git clone https://github.com/Nistro-dev/opencode-mad.git
|
|
39
|
+
|
|
40
|
+
# Copy to your project
|
|
41
|
+
cp -r opencode-mad/agents your-project/.opencode/
|
|
42
|
+
cp -r opencode-mad/commands your-project/.opencode/
|
|
43
|
+
cp -r opencode-mad/plugins your-project/.opencode/
|
|
44
|
+
cp -r opencode-mad/skills your-project/.opencode/
|
|
45
|
+
|
|
46
|
+
# Or copy globally
|
|
47
|
+
cp -r opencode-mad/agents ~/.config/opencode/agents/
|
|
48
|
+
cp -r opencode-mad/commands ~/.config/opencode/commands/
|
|
49
|
+
cp -r opencode-mad/plugins ~/.config/opencode/plugins/
|
|
50
|
+
cp -r opencode-mad/skills ~/.config/opencode/skills/
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Project structure after installation
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
your-project/
|
|
57
|
+
├── .opencode/
|
|
58
|
+
│ ├── agents/
|
|
59
|
+
│ │ ├── orchestrator.md # Main coordinator
|
|
60
|
+
│ │ ├── mad-developer.md # Implements features
|
|
61
|
+
│ │ ├── mad-tester.md # Tests before merge
|
|
62
|
+
│ │ ├── mad-merger.md # Resolves conflicts
|
|
63
|
+
│ │ └── mad-fixer.md # Fixes integration
|
|
64
|
+
│ ├── commands/
|
|
65
|
+
│ ├── plugins/
|
|
66
|
+
│ │ └── mad-plugin.ts
|
|
67
|
+
│ └── skills/
|
|
68
|
+
└── ... your code
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Usage
|
|
72
|
+
|
|
73
|
+
Once installed, just talk to the orchestrator naturally:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
You: Create a Task Timer app with Express backend and React frontend
|
|
77
|
+
|
|
78
|
+
Orchestrator: Before I create the development plan, I need to clarify:
|
|
79
|
+
1. Database: SQLite, PostgreSQL, or in-memory?
|
|
80
|
+
2. Authentication needed?
|
|
81
|
+
3. Dark mode or light mode?
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
You: SQLite, no auth, dark mode
|
|
85
|
+
|
|
86
|
+
Orchestrator: Here's the development plan:
|
|
87
|
+
[Shows plan with file ownership]
|
|
88
|
+
|
|
89
|
+
Ready to proceed? Reply "GO"
|
|
90
|
+
|
|
91
|
+
You: GO
|
|
92
|
+
|
|
93
|
+
Orchestrator: [Creates worktrees, spawns developers in parallel...]
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Commands (Optional)
|
|
97
|
+
|
|
98
|
+
| Command | Description |
|
|
99
|
+
|---------|-------------|
|
|
100
|
+
| `/mad <task>` | Start parallel orchestration |
|
|
101
|
+
| `/mad-status` | Show worktree status |
|
|
102
|
+
| `/mad-visualize` | ASCII dashboard |
|
|
103
|
+
| `/mad-fix <worktree>` | Fix errors in a worktree |
|
|
104
|
+
| `/mad-merge-all` | Merge all completed worktrees |
|
|
105
|
+
|
|
106
|
+
### Reporting Bugs
|
|
107
|
+
|
|
108
|
+
Just tell the orchestrator about the bug - it will delegate to a fixer:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
You: There's a CORS error, the frontend can't reach the backend
|
|
112
|
+
|
|
113
|
+
Orchestrator: I'll spawn a fixer to resolve this.
|
|
114
|
+
[Delegates to mad-fixer]
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## How It Works
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
121
|
+
│ You: "Create a full-stack app..." │
|
|
122
|
+
└─────────────────────────────────────────────────────────────┘
|
|
123
|
+
│
|
|
124
|
+
▼
|
|
125
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
126
|
+
│ ORCHESTRATOR (primary agent) │
|
|
127
|
+
│ - Asks clarifying questions │
|
|
128
|
+
│ - Creates plan with file ownership │
|
|
129
|
+
│ - Waits for "GO" │
|
|
130
|
+
└─────────────────────────────────────────────────────────────┘
|
|
131
|
+
│ "GO"
|
|
132
|
+
▼
|
|
133
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
134
|
+
│ DEVELOPERS (parallel in git worktrees) │
|
|
135
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
136
|
+
│ │ Backend │ │ Frontend │ │ Config │ │
|
|
137
|
+
│ │ /backend │ │ /frontend│ │ /root │ │
|
|
138
|
+
│ └──────────┘ └──────────┘ └──────────┘ │
|
|
139
|
+
│ Each owns exclusive files - no conflicts! │
|
|
140
|
+
└─────────────────────────────────────────────────────────────┘
|
|
141
|
+
│
|
|
142
|
+
▼
|
|
143
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
144
|
+
│ TESTERS (parallel) │
|
|
145
|
+
│ - Test APIs with curl │
|
|
146
|
+
│ - Check frontend for errors │
|
|
147
|
+
│ - Verify integration │
|
|
148
|
+
│ - Fix simple bugs or block if major issues │
|
|
149
|
+
└─────────────────────────────────────────────────────────────┘
|
|
150
|
+
│
|
|
151
|
+
▼
|
|
152
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
153
|
+
│ MERGER (if conflicts) │
|
|
154
|
+
│ - Understands both branches' intent │
|
|
155
|
+
│ - Combines functionality intelligently │
|
|
156
|
+
└─────────────────────────────────────────────────────────────┘
|
|
157
|
+
│
|
|
158
|
+
▼
|
|
159
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
160
|
+
│ FIXER (if integration issues) │
|
|
161
|
+
│ - Fixes cross-component bugs │
|
|
162
|
+
│ - Ensures frontend + backend work together │
|
|
163
|
+
└─────────────────────────────────────────────────────────────┘
|
|
164
|
+
│
|
|
165
|
+
▼
|
|
166
|
+
DONE!
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Agents
|
|
170
|
+
|
|
171
|
+
| Agent | Mode | Description |
|
|
172
|
+
|-------|------|-------------|
|
|
173
|
+
| `orchestrator` | primary | Coordinates workflow, asks questions, creates plans. **Never codes directly.** |
|
|
174
|
+
| `mad-developer` | subagent | Implements tasks in isolated worktrees |
|
|
175
|
+
| `mad-tester` | subagent | Tests code before merge |
|
|
176
|
+
| `mad-merger` | subagent | Resolves git merge conflicts |
|
|
177
|
+
| `mad-fixer` | subagent | Fixes integration issues |
|
|
178
|
+
|
|
179
|
+
## Custom Tools
|
|
180
|
+
|
|
181
|
+
The plugin provides these tools:
|
|
182
|
+
|
|
183
|
+
| Tool | Description |
|
|
184
|
+
|------|-------------|
|
|
185
|
+
| `mad_worktree_create` | Create isolated git worktree |
|
|
186
|
+
| `mad_status` | Get status of all worktrees |
|
|
187
|
+
| `mad_visualize` | ASCII art dashboard |
|
|
188
|
+
| `mad_test` | Run tests on a worktree |
|
|
189
|
+
| `mad_merge` | Merge completed worktree |
|
|
190
|
+
| `mad_cleanup` | Remove finished worktree |
|
|
191
|
+
| `mad_done` | Mark task as completed |
|
|
192
|
+
| `mad_blocked` | Mark task as blocked |
|
|
193
|
+
| `mad_read_task` | Read task description |
|
|
194
|
+
| `mad_log` | Log orchestration events |
|
|
195
|
+
| `mad_check_update` | Check for plugin updates |
|
|
196
|
+
|
|
197
|
+
## Updates
|
|
198
|
+
|
|
199
|
+
opencode-mad checks for updates automatically and notifies you when a new version is available.
|
|
200
|
+
|
|
201
|
+
To update manually:
|
|
202
|
+
```bash
|
|
203
|
+
npx opencode-mad update -g
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
To check for updates:
|
|
207
|
+
```bash
|
|
208
|
+
npx opencode-mad version
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Requirements
|
|
212
|
+
|
|
213
|
+
- [OpenCode](https://opencode.ai) 1.0+
|
|
214
|
+
- Git (for worktrees)
|
|
215
|
+
- Node.js 18+
|
|
216
|
+
|
|
217
|
+
## Configuration
|
|
218
|
+
|
|
219
|
+
The orchestrator uses these defaults:
|
|
220
|
+
- Model: `anthropic/claude-opus-4-5`
|
|
221
|
+
- Never pushes automatically (only commits)
|
|
222
|
+
- Always asks questions before planning
|
|
223
|
+
|
|
224
|
+
To change the model, edit `.opencode/agents/orchestrator.md`:
|
|
225
|
+
|
|
226
|
+
```yaml
|
|
227
|
+
---
|
|
228
|
+
model: anthropic/claude-sonnet-4-20250514
|
|
229
|
+
---
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT
|
|
235
|
+
|
|
236
|
+
## Contributing
|
|
237
|
+
|
|
238
|
+
Issues and PRs welcome at [github.com/Nistro-dev/opencode-mad](https://github.com/Nistro-dev/opencode-mad)
|
package/install.js
CHANGED
package/package.json
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "opencode-mad",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "Multi-Agent Dev - Parallel development orchestration plugin for OpenCode",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "plugins/mad-plugin.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"opencode-mad": "./install.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"agents",
|
|
12
|
-
"commands",
|
|
13
|
-
"plugins",
|
|
14
|
-
"skills",
|
|
15
|
-
"install.js"
|
|
16
|
-
],
|
|
17
|
-
"dependencies": {
|
|
18
|
-
"@opencode-ai/plugin": "^1.1.34"
|
|
19
|
-
},
|
|
20
|
-
"keywords": [
|
|
21
|
-
"opencode",
|
|
22
|
-
"plugin",
|
|
23
|
-
"multi-agent",
|
|
24
|
-
"parallel-development",
|
|
25
|
-
"git-worktree",
|
|
26
|
-
"ai",
|
|
27
|
-
"orchestration"
|
|
28
|
-
],
|
|
29
|
-
"author": "Nistro-dev",
|
|
30
|
-
"license": "MIT",
|
|
31
|
-
"repository": {
|
|
32
|
-
"type": "git",
|
|
33
|
-
"url": "https://github.com/Nistro-dev/opencode-mad"
|
|
34
|
-
},
|
|
35
|
-
"homepage": "https://github.com/Nistro-dev/opencode-mad#readme"
|
|
36
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-mad",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Multi-Agent Dev - Parallel development orchestration plugin for OpenCode",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "plugins/mad-plugin.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"opencode-mad": "./install.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"agents",
|
|
12
|
+
"commands",
|
|
13
|
+
"plugins",
|
|
14
|
+
"skills",
|
|
15
|
+
"install.js"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@opencode-ai/plugin": "^1.1.34"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"opencode",
|
|
22
|
+
"plugin",
|
|
23
|
+
"multi-agent",
|
|
24
|
+
"parallel-development",
|
|
25
|
+
"git-worktree",
|
|
26
|
+
"ai",
|
|
27
|
+
"orchestration"
|
|
28
|
+
],
|
|
29
|
+
"author": "Nistro-dev",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/Nistro-dev/opencode-mad"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/Nistro-dev/opencode-mad#readme"
|
|
36
|
+
}
|
package/plugins/mad-plugin.ts
CHANGED
|
@@ -13,7 +13,11 @@ import { execSync } from "child_process"
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
// Current version of opencode-mad
|
|
16
|
-
const CURRENT_VERSION = "0.3.
|
|
16
|
+
const CURRENT_VERSION = "0.3.1"
|
|
17
|
+
|
|
18
|
+
// Update notification state (shown only once per session)
|
|
19
|
+
let updateNotificationShown = false
|
|
20
|
+
let pendingUpdateMessage: string | null = null
|
|
17
21
|
|
|
18
22
|
export const MADPlugin: Plugin = async ({ project, client, $, directory, worktree }) => {
|
|
19
23
|
|
|
@@ -97,18 +101,28 @@ export const MADPlugin: Plugin = async ({ project, client, $, directory, worktre
|
|
|
97
101
|
return { hasUpdate: false, current: CURRENT_VERSION, latest: CURRENT_VERSION }
|
|
98
102
|
}
|
|
99
103
|
|
|
100
|
-
// Check for updates at plugin initialization
|
|
104
|
+
// Check for updates at plugin initialization and store message for first tool response
|
|
101
105
|
try {
|
|
102
106
|
const updateInfo = await checkForUpdates()
|
|
103
107
|
if (updateInfo.hasUpdate) {
|
|
104
|
-
|
|
105
|
-
console.log(` Run: npx opencode-mad install -g\n`)
|
|
108
|
+
pendingUpdateMessage = `🔄 **Update available!** opencode-mad ${updateInfo.current} → ${updateInfo.latest}\n Run: \`npx opencode-mad install -g\`\n\n`
|
|
106
109
|
logEvent("info", "Update available", { current: updateInfo.current, latest: updateInfo.latest })
|
|
107
110
|
}
|
|
108
111
|
} catch (e) {
|
|
109
112
|
// Silent fail - don't break plugin initialization
|
|
110
113
|
}
|
|
111
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Helper to get update notification (returns message only once)
|
|
117
|
+
*/
|
|
118
|
+
const getUpdateNotification = (): string => {
|
|
119
|
+
if (pendingUpdateMessage && !updateNotificationShown) {
|
|
120
|
+
updateNotificationShown = true
|
|
121
|
+
return pendingUpdateMessage
|
|
122
|
+
}
|
|
123
|
+
return ""
|
|
124
|
+
}
|
|
125
|
+
|
|
112
126
|
return {
|
|
113
127
|
// Custom tools for MAD workflow
|
|
114
128
|
tool: {
|
|
@@ -131,12 +145,12 @@ Each worktree has its own branch and working directory.`,
|
|
|
131
145
|
// Validate inputs
|
|
132
146
|
if (!branch || branch.trim() === "") {
|
|
133
147
|
logEvent("error", "mad_worktree_create failed: empty branch name")
|
|
134
|
-
return "❌ Error: Branch name cannot be empty"
|
|
148
|
+
return getUpdateNotification() + "❌ Error: Branch name cannot be empty"
|
|
135
149
|
}
|
|
136
150
|
|
|
137
151
|
if (!task || task.trim() === "") {
|
|
138
152
|
logEvent("error", "mad_worktree_create failed: empty task description")
|
|
139
|
-
return "❌ Error: Task description cannot be empty"
|
|
153
|
+
return getUpdateNotification() + "❌ Error: Task description cannot be empty"
|
|
140
154
|
}
|
|
141
155
|
|
|
142
156
|
const gitRoot = getGitRoot()
|
|
@@ -148,7 +162,7 @@ Each worktree has its own branch and working directory.`,
|
|
|
148
162
|
// Check if worktree already exists
|
|
149
163
|
if (existsSync(worktreePath)) {
|
|
150
164
|
logEvent("warn", "Worktree already exists", { branch, path: worktreePath })
|
|
151
|
-
return `⚠️ Worktree already exists at ${worktreePath}\nUse a different branch name or clean up with mad_cleanup.`
|
|
165
|
+
return getUpdateNotification() + `⚠️ Worktree already exists at ${worktreePath}\nUse a different branch name or clean up with mad_cleanup.`
|
|
152
166
|
}
|
|
153
167
|
|
|
154
168
|
logEvent("info", "Creating worktree", { branch, baseBranch })
|
|
@@ -177,7 +191,7 @@ worktrees/
|
|
|
177
191
|
mkdirSync(worktreeDir, { recursive: true })
|
|
178
192
|
} catch (e: any) {
|
|
179
193
|
logEvent("error", "Failed to create worktree directory", { error: e.message })
|
|
180
|
-
return `❌ Error creating worktree directory: ${e.message}`
|
|
194
|
+
return getUpdateNotification() + `❌ Error creating worktree directory: ${e.message}`
|
|
181
195
|
}
|
|
182
196
|
|
|
183
197
|
// Check if branch exists
|
|
@@ -196,7 +210,7 @@ worktrees/
|
|
|
196
210
|
command: worktreeCmd,
|
|
197
211
|
error: worktreeResult.error
|
|
198
212
|
})
|
|
199
|
-
return `❌ Error creating git worktree: ${worktreeResult.error}`
|
|
213
|
+
return getUpdateNotification() + `❌ Error creating git worktree: ${worktreeResult.error}`
|
|
200
214
|
}
|
|
201
215
|
|
|
202
216
|
// Write task file using Node.js
|
|
@@ -215,7 +229,7 @@ ${task}
|
|
|
215
229
|
|
|
216
230
|
logEvent("info", "Worktree created successfully", { branch, path: worktreePath })
|
|
217
231
|
|
|
218
|
-
return `✅ Worktree created successfully!
|
|
232
|
+
return getUpdateNotification() + `✅ Worktree created successfully!
|
|
219
233
|
- Path: ${worktreePath}
|
|
220
234
|
- Branch: ${branch}
|
|
221
235
|
- Base: ${baseBranch}
|
|
@@ -224,7 +238,7 @@ ${task}
|
|
|
224
238
|
The developer subagent can now work in this worktree using the Task tool.`
|
|
225
239
|
} catch (e: any) {
|
|
226
240
|
logEvent("error", "mad_worktree_create exception", { error: e.message, stack: e.stack })
|
|
227
|
-
return `❌ Unexpected error creating worktree: ${e.message}`
|
|
241
|
+
return getUpdateNotification() + `❌ Unexpected error creating worktree: ${e.message}`
|
|
228
242
|
}
|
|
229
243
|
},
|
|
230
244
|
}),
|
|
@@ -241,15 +255,15 @@ Shows which tasks are done, in progress, blocked, or have errors.`,
|
|
|
241
255
|
const worktreeDir = join(gitRoot, "worktrees")
|
|
242
256
|
|
|
243
257
|
if (!existsSync(worktreeDir)) {
|
|
244
|
-
return "No active MAD worktrees. Use mad_worktree_create to create one."
|
|
258
|
+
return getUpdateNotification() + "No active MAD worktrees. Use mad_worktree_create to create one."
|
|
245
259
|
}
|
|
246
260
|
|
|
247
261
|
const entries = readdirSync(worktreeDir)
|
|
248
262
|
if (entries.length === 0) {
|
|
249
|
-
return "No active MAD worktrees. Use mad_worktree_create to create one."
|
|
263
|
+
return getUpdateNotification() + "No active MAD worktrees. Use mad_worktree_create to create one."
|
|
250
264
|
}
|
|
251
265
|
|
|
252
|
-
let status = "# MAD Status Dashboard\n\n"
|
|
266
|
+
let status = getUpdateNotification() + "# MAD Status Dashboard\n\n"
|
|
253
267
|
let total = 0, done = 0, blocked = 0, errors = 0, wip = 0
|
|
254
268
|
|
|
255
269
|
for (const entry of entries) {
|
|
@@ -330,10 +344,10 @@ Returns the results and creates an error file if tests fail.`,
|
|
|
330
344
|
const worktreePath = join(gitRoot, "worktrees", args.worktree)
|
|
331
345
|
|
|
332
346
|
if (!existsSync(worktreePath)) {
|
|
333
|
-
return `Worktree not found: ${worktreePath}`
|
|
347
|
+
return getUpdateNotification() + `Worktree not found: ${worktreePath}`
|
|
334
348
|
}
|
|
335
349
|
|
|
336
|
-
let results = `# Test Results for ${args.worktree}\n\n`
|
|
350
|
+
let results = getUpdateNotification() + `# Test Results for ${args.worktree}\n\n`
|
|
337
351
|
let hasError = false
|
|
338
352
|
let errorMessages = ""
|
|
339
353
|
|
|
@@ -413,22 +427,22 @@ Handles merge conflicts by reporting them.`,
|
|
|
413
427
|
const branch = args.worktree.replace(/-/g, "/")
|
|
414
428
|
|
|
415
429
|
if (!existsSync(worktreePath)) {
|
|
416
|
-
return `Worktree not found: ${worktreePath}`
|
|
430
|
+
return getUpdateNotification() + `Worktree not found: ${worktreePath}`
|
|
417
431
|
}
|
|
418
432
|
|
|
419
433
|
if (!existsSync(doneFile)) {
|
|
420
|
-
return `Cannot merge: worktree ${args.worktree} is not marked as done. Complete the task first.`
|
|
434
|
+
return getUpdateNotification() + `Cannot merge: worktree ${args.worktree} is not marked as done. Complete the task first.`
|
|
421
435
|
}
|
|
422
436
|
|
|
423
437
|
const result = runCommand(`git merge ${branch} --no-edit`, gitRoot)
|
|
424
438
|
if (result.success) {
|
|
425
|
-
return `✅ Successfully merged ${branch}!\n\n${result.output}`
|
|
439
|
+
return getUpdateNotification() + `✅ Successfully merged ${branch}!\n\n${result.output}`
|
|
426
440
|
} else {
|
|
427
441
|
const output = result.error || "Unknown error"
|
|
428
442
|
if (output.includes("CONFLICT")) {
|
|
429
|
-
return `⚠️ Merge conflict detected!\n\n${output}\n\nResolve conflicts manually or use the fixer agent.`
|
|
443
|
+
return getUpdateNotification() + `⚠️ Merge conflict detected!\n\n${output}\n\nResolve conflicts manually or use the fixer agent.`
|
|
430
444
|
}
|
|
431
|
-
return `❌ Merge failed:\n${output}`
|
|
445
|
+
return getUpdateNotification() + `❌ Merge failed:\n${output}`
|
|
432
446
|
}
|
|
433
447
|
},
|
|
434
448
|
}),
|
|
@@ -449,19 +463,19 @@ Removes the worktree directory and prunes git worktree references.`,
|
|
|
449
463
|
const doneFile = join(worktreePath, ".agent-done")
|
|
450
464
|
|
|
451
465
|
if (!existsSync(worktreePath)) {
|
|
452
|
-
return `Worktree not found: ${worktreePath}`
|
|
466
|
+
return getUpdateNotification() + `Worktree not found: ${worktreePath}`
|
|
453
467
|
}
|
|
454
468
|
|
|
455
469
|
if (!args.force && !existsSync(doneFile)) {
|
|
456
|
-
return `Worktree ${args.worktree} is not marked as done. Use force=true to cleanup anyway.`
|
|
470
|
+
return getUpdateNotification() + `Worktree ${args.worktree} is not marked as done. Use force=true to cleanup anyway.`
|
|
457
471
|
}
|
|
458
472
|
|
|
459
473
|
try {
|
|
460
474
|
await $`git worktree remove ${worktreePath} --force`
|
|
461
475
|
await $`git worktree prune`
|
|
462
|
-
return `✅ Cleaned up worktree: ${args.worktree}`
|
|
476
|
+
return getUpdateNotification() + `✅ Cleaned up worktree: ${args.worktree}`
|
|
463
477
|
} catch (e: any) {
|
|
464
|
-
return `❌ Cleanup failed: ${e.message}`
|
|
478
|
+
return getUpdateNotification() + `❌ Cleanup failed: ${e.message}`
|
|
465
479
|
}
|
|
466
480
|
},
|
|
467
481
|
}),
|
|
@@ -481,14 +495,14 @@ Use this when you've finished implementing the task in a worktree.`,
|
|
|
481
495
|
const worktreePath = join(gitRoot, "worktrees", args.worktree)
|
|
482
496
|
|
|
483
497
|
if (!existsSync(worktreePath)) {
|
|
484
|
-
return `Worktree not found: ${worktreePath}`
|
|
498
|
+
return getUpdateNotification() + `Worktree not found: ${worktreePath}`
|
|
485
499
|
}
|
|
486
500
|
|
|
487
501
|
await $`echo ${args.summary} > ${join(worktreePath, ".agent-done")}`
|
|
488
502
|
// Remove error/blocked files
|
|
489
503
|
await $`rm -f ${join(worktreePath, ".agent-error")} ${join(worktreePath, ".agent-blocked")}`
|
|
490
504
|
|
|
491
|
-
return `✅ Marked ${args.worktree} as done: ${args.summary}`
|
|
505
|
+
return getUpdateNotification() + `✅ Marked ${args.worktree} as done: ${args.summary}`
|
|
492
506
|
},
|
|
493
507
|
}),
|
|
494
508
|
|
|
@@ -507,12 +521,12 @@ Use this when you cannot proceed due to missing information or dependencies.`,
|
|
|
507
521
|
const worktreePath = join(gitRoot, "worktrees", args.worktree)
|
|
508
522
|
|
|
509
523
|
if (!existsSync(worktreePath)) {
|
|
510
|
-
return `Worktree not found: ${worktreePath}`
|
|
524
|
+
return getUpdateNotification() + `Worktree not found: ${worktreePath}`
|
|
511
525
|
}
|
|
512
526
|
|
|
513
527
|
await $`echo ${args.reason} > ${join(worktreePath, ".agent-blocked")}`
|
|
514
528
|
|
|
515
|
-
return `🚫 Marked ${args.worktree} as blocked: ${args.reason}`
|
|
529
|
+
return getUpdateNotification() + `🚫 Marked ${args.worktree} as blocked: ${args.reason}`
|
|
516
530
|
},
|
|
517
531
|
}),
|
|
518
532
|
|
|
@@ -530,10 +544,10 @@ Use this to understand what needs to be done in a specific worktree.`,
|
|
|
530
544
|
const taskFile = join(gitRoot, "worktrees", args.worktree, ".agent-task")
|
|
531
545
|
|
|
532
546
|
if (!existsSync(taskFile)) {
|
|
533
|
-
return `Task file not found: ${taskFile}`
|
|
547
|
+
return getUpdateNotification() + `Task file not found: ${taskFile}`
|
|
534
548
|
}
|
|
535
549
|
|
|
536
|
-
return readFileSync(taskFile, "utf-8")
|
|
550
|
+
return getUpdateNotification() + readFileSync(taskFile, "utf-8")
|
|
537
551
|
},
|
|
538
552
|
}),
|
|
539
553
|
|
|
@@ -551,9 +565,9 @@ Creates structured logs in .mad-logs.jsonl for tracking the workflow.`,
|
|
|
551
565
|
async execute(args, context) {
|
|
552
566
|
try {
|
|
553
567
|
await logEvent(args.level as "info" | "warn" | "error" | "debug", args.message, args.context)
|
|
554
|
-
return `📝 Logged [${args.level.toUpperCase()}]: ${args.message}`
|
|
568
|
+
return getUpdateNotification() + `📝 Logged [${args.level.toUpperCase()}]: ${args.message}`
|
|
555
569
|
} catch (e: any) {
|
|
556
|
-
return `⚠️ Failed to write log: ${e.message}`
|
|
570
|
+
return getUpdateNotification() + `⚠️ Failed to write log: ${e.message}`
|
|
557
571
|
}
|
|
558
572
|
},
|
|
559
573
|
}),
|
|
@@ -571,12 +585,12 @@ Shows progress, worktree statuses, timeline, and statistics in a beautiful dashb
|
|
|
571
585
|
const worktreeDir = join(gitRoot, "worktrees")
|
|
572
586
|
|
|
573
587
|
if (!existsSync(worktreeDir)) {
|
|
574
|
-
return "No active MAD worktrees. Use mad_worktree_create to create one."
|
|
588
|
+
return getUpdateNotification() + "No active MAD worktrees. Use mad_worktree_create to create one."
|
|
575
589
|
}
|
|
576
590
|
|
|
577
591
|
const entries = readdirSync(worktreeDir)
|
|
578
592
|
if (entries.length === 0) {
|
|
579
|
-
return "No active MAD worktrees. Use mad_worktree_create to create one."
|
|
593
|
+
return getUpdateNotification() + "No active MAD worktrees. Use mad_worktree_create to create one."
|
|
580
594
|
}
|
|
581
595
|
|
|
582
596
|
let total = 0, done = 0, blocked = 0, errors = 0, wip = 0
|
|
@@ -636,7 +650,7 @@ Shows progress, worktree statuses, timeline, and statistics in a beautiful dashb
|
|
|
636
650
|
const progressBar = "█".repeat(Math.floor(progress / 5)) + "░".repeat(20 - Math.floor(progress / 5))
|
|
637
651
|
|
|
638
652
|
// Build visualization
|
|
639
|
-
let output = `
|
|
653
|
+
let output = getUpdateNotification() + `
|
|
640
654
|
┌────────────────────────────────────────────────────────────────┐
|
|
641
655
|
│ MAD ORCHESTRATION DASHBOARD │
|
|
642
656
|
└────────────────────────────────────────────────────────────────┘
|
|
@@ -679,7 +693,7 @@ Shows progress, worktree statuses, timeline, and statistics in a beautiful dashb
|
|
|
679
693
|
|
|
680
694
|
return output
|
|
681
695
|
} catch (e: any) {
|
|
682
|
-
return `❌ Error generating visualization: ${e.message}`
|
|
696
|
+
return getUpdateNotification() + `❌ Error generating visualization: ${e.message}`
|
|
683
697
|
}
|
|
684
698
|
},
|
|
685
699
|
}),
|
|
@@ -696,7 +710,7 @@ Returns the current version, latest version, and whether an update is available.
|
|
|
696
710
|
const updateInfo = await checkForUpdates()
|
|
697
711
|
|
|
698
712
|
if (updateInfo.hasUpdate) {
|
|
699
|
-
return `🔄 Update available!
|
|
713
|
+
return getUpdateNotification() + `🔄 Update available!
|
|
700
714
|
|
|
701
715
|
Current version: ${updateInfo.current}
|
|
702
716
|
Latest version: ${updateInfo.latest}
|
|
@@ -704,13 +718,13 @@ Latest version: ${updateInfo.latest}
|
|
|
704
718
|
To update, run:
|
|
705
719
|
npx opencode-mad install -g`
|
|
706
720
|
} else {
|
|
707
|
-
return `✅ You're up to date!
|
|
721
|
+
return getUpdateNotification() + `✅ You're up to date!
|
|
708
722
|
|
|
709
723
|
Current version: ${updateInfo.current}
|
|
710
724
|
Latest version: ${updateInfo.latest}`
|
|
711
725
|
}
|
|
712
726
|
} catch (e: any) {
|
|
713
|
-
return `❌ Failed to check for updates: ${e.message}`
|
|
727
|
+
return getUpdateNotification() + `❌ Failed to check for updates: ${e.message}`
|
|
714
728
|
}
|
|
715
729
|
},
|
|
716
730
|
}),
|