opencode-onboard 0.5.2 → 0.5.3
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/content/.opencode/commands/opsx-apply.md +22 -14
- package/content/.opencode/skills/openspec-apply-change/SKILL.md +18 -10
- package/content/AGENTS.md +3 -2
- package/package.json +1 -1
- package/src/commands/shared.js +13 -0
- package/src/commands/shared.test.js +42 -2
- package/src/commands/wizard.js +12 -8
- package/src/steps/openspec/ensemble.js +12 -4
|
@@ -122,9 +122,16 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
122
122
|
- read each engineer's description and abilities
|
|
123
123
|
- prefer the most specialized custom engineer whose description and abilities match the task
|
|
124
124
|
- use `basic-engineer` only when no custom engineer is a clear fit or as a recovery fallback
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
- never spawn an engineer name that is not present in `.opencode/agents/`
|
|
126
|
+
|
|
127
|
+
**Model resolution for spawned agents (do not skip):**
|
|
128
|
+
Before each `team_spawn`, resolve the model for that agent using this priority:
|
|
129
|
+
1. **Agent file frontmatter** — Read `.opencode/agents/<engineer-file>` and check for a `model:` field in the YAML frontmatter. This is the user's explicit choice for this specific agent.
|
|
130
|
+
2. **`ensemble.json` `modelsByAgent`** — Read `.opencode/ensemble.json`. If `modelsByAgent` contains a key matching this agent's role (build agents → `build`, devops-manager → `explore`), use that value.
|
|
131
|
+
3. **Active chat model** — Fall back to the model currently running this conversation (your own model).
|
|
132
|
+
Never hardcode a model ID. Never skip this resolution. Log the result: `[model: <id> ← <source: agent frontmatter / ensemble.json modelsByAgent / active chat>]`
|
|
133
|
+
|
|
134
|
+
REQUIRED assignment algorithm (do not skip):
|
|
128
135
|
1. Build candidate list from `.opencode/agents/*.md` excluding `devops-manager`.
|
|
129
136
|
2. Classify each task by domain using task text (api/backend, ui/frontend, infra/devops, testing/qa).
|
|
130
137
|
3. For each task, score every candidate agent:
|
|
@@ -174,13 +181,13 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
174
181
|
ALWAYS set `claim_task` to the first unblocked task in that agent's initial batch.
|
|
175
182
|
Only spawn agents whose tasks are actually needed by this change. Skip agents with no tasks.
|
|
176
183
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
+
Spawn sequentially, waiting for each result:
|
|
185
|
+
```
|
|
186
|
+
team_spawn name:"ui1" agent:"frontend-engineer" model:"<resolved-model>" prompt:"..."
|
|
187
|
+
(wait for result)
|
|
188
|
+
team_spawn name:"api1" agent:"backend-engineer" model:"<resolved-model>" prompt:"..."
|
|
189
|
+
(wait for result)
|
|
190
|
+
```
|
|
184
191
|
Replace example agent names with REAL engineers that exist in this project.
|
|
185
192
|
|
|
186
193
|
Then send each spawned worker a short start message that repeats their exact task IDs if needed:
|
|
@@ -226,7 +233,7 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
226
233
|
|
|
227
234
|
Spawn the best available verification-capable engineer with `worktree:false` (for example, a testing-focused custom engineer or `basic-engineer` if no better verifier exists):
|
|
228
235
|
```
|
|
229
|
-
team_spawn name:"verify" agent:"<real-verifier-engineer>" worktree:false prompt:"<verification scope, context summary, run tests + build + lint + verify acceptance criteria, no task claiming required in this phase, send results to lead when done>"
|
|
236
|
+
team_spawn name:"verify" agent:"<real-verifier-engineer>" worktree:false model:"<resolved-model>" prompt:"<verification scope, context summary, run tests + build + lint + verify acceptance criteria, no task claiming required in this phase, send results to lead when done>"
|
|
230
237
|
```
|
|
231
238
|
Wait for message -> team_results -> fix blockers -> team_shutdown (no team_merge needed, worktree:false)
|
|
232
239
|
|
|
@@ -253,9 +260,10 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
253
260
|
- NEVER implement tasks directly. Always use team_create + team_spawn, no exceptions
|
|
254
261
|
- NEVER touch source files before team_create is called, not even one edit
|
|
255
262
|
- NEVER call team_spawn without the agent field, it is required and will fail without it
|
|
256
|
-
- NEVER call team_spawn
|
|
257
|
-
- NEVER
|
|
258
|
-
- NEVER
|
|
263
|
+
- NEVER call team_spawn without a resolved `model:` field; always pass the model resolved from the priority chain
|
|
264
|
+
- NEVER call team_spawn before all tasks are on the board; use multiple `team_tasks_add` calls when dependencies require real IDs from earlier calls
|
|
265
|
+
- NEVER poll team_results or team_status in a loop, wait for teammates to message you
|
|
266
|
+
- NEVER call team_claim or team_tasks_complete as lead, only agents call these tools
|
|
259
267
|
- NEVER edit files between team_spawn and team_merge, team_merge blocks on overlapping local changes
|
|
260
268
|
- NEVER leave pending tasks orphaned, always verify board is empty before proceeding to step 7
|
|
261
269
|
- ALWAYS pass the LITERAL task IDs returned by team_tasks_add into each agent's spawn prompt, copy the exact IDs, never paraphrase
|
|
@@ -128,9 +128,16 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
128
128
|
- read each engineer's description and abilities
|
|
129
129
|
- prefer the most specialized custom engineer whose description and abilities match the task
|
|
130
130
|
- use `basic-engineer` only when no custom engineer is a clear fit or as a recovery fallback
|
|
131
|
-
|
|
131
|
+
- never spawn an engineer name that is not present in `.opencode/agents/`
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
**Model resolution for spawned agents (do not skip):**
|
|
134
|
+
Before each `team_spawn`, resolve the model for that agent using this priority:
|
|
135
|
+
1. **Agent file frontmatter** — Read `.opencode/agents/<engineer-file>` and check for a `model:` field in the YAML frontmatter. This is the user's explicit choice for this specific agent.
|
|
136
|
+
2. **`ensemble.json` `modelsByAgent`** — Read `.opencode/ensemble.json`. If `modelsByAgent` contains a key matching this agent's role (build agents → `build`, devops-manager → `explore`), use that value.
|
|
137
|
+
3. **Active chat model** — Fall back to the model currently running this conversation (your own model).
|
|
138
|
+
Never hardcode a model ID. Never skip this resolution. Log the result: `[model: <id> ← <source: agent frontmatter / ensemble.json modelsByAgent / active chat>]`
|
|
139
|
+
|
|
140
|
+
REQUIRED assignment algorithm (do not skip):
|
|
134
141
|
1. Build candidate list from `.opencode/agents/*.md` excluding `devops-manager`.
|
|
135
142
|
2. Classify each task by domain using task text (api/backend, ui/frontend, infra/devops, testing/qa).
|
|
136
143
|
3. For each task, score every candidate agent:
|
|
@@ -181,13 +188,13 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
181
188
|
5. Send a short team_message with files changed and checks run
|
|
182
189
|
```
|
|
183
190
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
+
Spawn sequentially, waiting for each result:
|
|
192
|
+
```
|
|
193
|
+
team_spawn name:"ui1" agent:"frontend-engineer" model:"<resolved-model>" prompt:"..."
|
|
194
|
+
(wait for result)
|
|
195
|
+
team_spawn name:"api1" agent:"backend-engineer" model:"<resolved-model>" prompt:"..."
|
|
196
|
+
(wait for result)
|
|
197
|
+
```
|
|
191
198
|
Replace example agent names with REAL engineers that exist in this project.
|
|
192
199
|
|
|
193
200
|
Then send each spawned worker a short start message that repeats their exact task IDs if needed:
|
|
@@ -232,7 +239,7 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
232
239
|
|
|
233
240
|
Spawn the best available verification-capable engineer with `worktree:false` (for example, a testing-focused custom engineer or `basic-engineer` if no better verifier exists):
|
|
234
241
|
```
|
|
235
|
-
team_spawn name:"verify" agent:"<real-verifier-engineer>" worktree:false prompt:"<verification scope, context summary, run tests + build + lint + verify acceptance criteria, no task claiming required in this phase, send results to lead when done>"
|
|
242
|
+
team_spawn name:"verify" agent:"<real-verifier-engineer>" worktree:false model:"<resolved-model>" prompt:"<verification scope, context summary, run tests + build + lint + verify acceptance criteria, no task claiming required in this phase, send results to lead when done>"
|
|
236
243
|
```
|
|
237
244
|
Wait for message -> team_results -> fix blockers -> team_shutdown (no team_merge needed, worktree:false)
|
|
238
245
|
|
|
@@ -259,6 +266,7 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
|
|
|
259
266
|
- NEVER implement tasks directly. Always use team_create + team_spawn, no exceptions
|
|
260
267
|
- NEVER touch source files before team_create is called, not even one edit
|
|
261
268
|
- NEVER call team_spawn without the agent field, it is required and will fail without it
|
|
269
|
+
- NEVER call team_spawn without a resolved `model:` field; always pass the model resolved from the priority chain
|
|
262
270
|
- NEVER call team_spawn before all tasks are on the board; use multiple `team_tasks_add` calls when dependencies require real IDs from earlier calls
|
|
263
271
|
- NEVER poll team_results or team_status in a loop, wait for teammates to message you
|
|
264
272
|
- NEVER call team_claim or team_tasks_complete as lead, only agents call these tools
|
package/content/AGENTS.md
CHANGED
|
@@ -248,8 +248,9 @@ Core tools used in this workflow:
|
|
|
248
248
|
|
|
249
249
|
If a teammate stalls due to model quota/rate-limit exhaustion:
|
|
250
250
|
1. `team_shutdown name:"<stuck-member>" force:true`
|
|
251
|
-
2. `
|
|
252
|
-
3. `
|
|
251
|
+
2. Resolve a new model using the model resolution priority (agent file frontmatter → `ensemble.json` `modelsByAgent` → active chat model). Avoid the model that hit the rate limit.
|
|
252
|
+
3. `team_spawn` same member/task with the resolved model
|
|
253
|
+
4. `team_message` start instruction with the exact next task ID
|
|
253
254
|
|
|
254
255
|
---
|
|
255
256
|
|
package/package.json
CHANGED
package/src/commands/shared.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { execa } from 'execa'
|
|
1
2
|
import fse from 'fs-extra'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
|
|
@@ -10,3 +11,15 @@ export async function readOnboardConfig() {
|
|
|
10
11
|
return null
|
|
11
12
|
}
|
|
12
13
|
}
|
|
14
|
+
|
|
15
|
+
export async function ensureGitLongpaths(cwd = process.cwd()) {
|
|
16
|
+
try {
|
|
17
|
+
const repoCheck = await execa('git', ['rev-parse', '--is-inside-work-tree'], { cwd, reject: false })
|
|
18
|
+
if (repoCheck.exitCode !== 0 || repoCheck.stdout.trim() !== 'true') return false
|
|
19
|
+
|
|
20
|
+
const configResult = await execa('git', ['config', 'core.longpaths', 'true'], { cwd, reject: false })
|
|
21
|
+
return configResult.exitCode === 0
|
|
22
|
+
} catch {
|
|
23
|
+
return false
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
|
2
2
|
import fs from 'node:fs'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import os from 'node:os'
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
vi.mock('execa', () => ({
|
|
7
|
+
execa: vi.fn(),
|
|
8
|
+
}))
|
|
9
|
+
|
|
10
|
+
import { execa } from 'execa'
|
|
11
|
+
import { ensureGitLongpaths, readOnboardConfig } from './shared.js'
|
|
6
12
|
|
|
7
13
|
describe('readOnboardConfig()', () => {
|
|
8
14
|
let tmpDir, originalCwd
|
|
9
15
|
|
|
10
16
|
beforeEach(() => {
|
|
17
|
+
vi.clearAllMocks()
|
|
11
18
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'shared-test-'))
|
|
12
19
|
originalCwd = process.cwd()
|
|
13
20
|
process.chdir(tmpDir)
|
|
@@ -54,3 +61,36 @@ describe('readOnboardConfig()', () => {
|
|
|
54
61
|
expect(result).toBeNull()
|
|
55
62
|
})
|
|
56
63
|
})
|
|
64
|
+
|
|
65
|
+
describe('ensureGitLongpaths()', () => {
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
vi.clearAllMocks()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('configures core.longpaths in a git repository', async () => {
|
|
71
|
+
execa
|
|
72
|
+
.mockResolvedValueOnce({ exitCode: 0, stdout: 'true' })
|
|
73
|
+
.mockResolvedValueOnce({ exitCode: 0, stdout: '', stderr: '' })
|
|
74
|
+
|
|
75
|
+
const result = await ensureGitLongpaths('C:/repo')
|
|
76
|
+
|
|
77
|
+
expect(result).toBe(true)
|
|
78
|
+
expect(execa).toHaveBeenNthCalledWith(1, 'git', ['rev-parse', '--is-inside-work-tree'], {
|
|
79
|
+
cwd: 'C:/repo',
|
|
80
|
+
reject: false,
|
|
81
|
+
})
|
|
82
|
+
expect(execa).toHaveBeenNthCalledWith(2, 'git', ['config', 'core.longpaths', 'true'], {
|
|
83
|
+
cwd: 'C:/repo',
|
|
84
|
+
reject: false,
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('skips configuration outside a git repository', async () => {
|
|
89
|
+
execa.mockResolvedValueOnce({ exitCode: 1, stdout: '', stderr: '' })
|
|
90
|
+
|
|
91
|
+
const result = await ensureGitLongpaths('C:/not-a-repo')
|
|
92
|
+
|
|
93
|
+
expect(result).toBe(false)
|
|
94
|
+
expect(execa).toHaveBeenCalledTimes(1)
|
|
95
|
+
})
|
|
96
|
+
})
|
package/src/commands/wizard.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { select as wizardSelect } from '@inquirer/prompts'
|
|
2
|
-
import chalk from 'chalk'
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import { ensureGitLongpaths } from './shared.js'
|
|
4
|
+
import { chooseSourceScope } from '../steps/source/index.js'
|
|
5
|
+
import { cleanAiFiles } from '../steps/clean/index.js'
|
|
5
6
|
import { choosePlatform } from '../steps/platform/index.js'
|
|
6
7
|
import { copyContentStep } from '../steps/copy/index.js'
|
|
7
8
|
import { initOpenspec } from '../steps/openspec/index.js'
|
|
@@ -37,7 +38,7 @@ export async function runWizard(version) {
|
|
|
37
38
|
console.log()
|
|
38
39
|
|
|
39
40
|
// Only wait for Enter in a real interactive TTY
|
|
40
|
-
if (process.stdin.isTTY) {
|
|
41
|
+
if (process.stdin.isTTY) {
|
|
41
42
|
console.log(chalk.bold(' Press Enter to begin...'))
|
|
42
43
|
console.log()
|
|
43
44
|
await new Promise(resolve => {
|
|
@@ -46,10 +47,13 @@ export async function runWizard(version) {
|
|
|
46
47
|
process.stdin.pause()
|
|
47
48
|
resolve()
|
|
48
49
|
})
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const longpathsConfigured = await ensureGitLongpaths()
|
|
54
|
+
if (longpathsConfigured) console.log(chalk.dim(' Git long paths enabled for this repository.'))
|
|
55
|
+
|
|
56
|
+
const scope = await chooseSourceScope()
|
|
53
57
|
|
|
54
58
|
const maxConcurrentAgents = await wizardSelect({
|
|
55
59
|
message: 'Max concurrent agents:',
|
|
@@ -58,6 +58,13 @@ export const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
|
|
|
58
58
|
- use \`basic-engineer\` only when no custom engineer is a clear fit or as a recovery fallback
|
|
59
59
|
- never spawn an engineer name that is not present in \`.opencode/agents/\`
|
|
60
60
|
|
|
61
|
+
**Model resolution for spawned agents (do not skip):**
|
|
62
|
+
Before each \`team_spawn\`, resolve the model for that agent using this priority:
|
|
63
|
+
1. **Agent file frontmatter** — Read \`.opencode/agents/<engineer-file>\` and check for a \`model:\` field in the YAML frontmatter. This is the user's explicit choice for this specific agent.
|
|
64
|
+
2. **\`ensemble.json\` \`modelsByAgent\`** — Read \`.opencode/ensemble.json\`. If \`modelsByAgent\` contains a key matching this agent's role (build agents → \`build\`, devops-manager → \`explore\`), use that value.
|
|
65
|
+
3. **Active chat model** — Fall back to the model currently running this conversation (your own model).
|
|
66
|
+
Never hardcode a model ID. Never skip this resolution. Log the result: \`[model: <id> ← <source: agent frontmatter / ensemble.json modelsByAgent / active chat>]\`
|
|
67
|
+
|
|
61
68
|
Each \`team_spawn\` MUST include the agent field (required, causes NOT NULL error if omitted).
|
|
62
69
|
|
|
63
70
|
The spawn prompt must be short and operational. It must contain:
|
|
@@ -91,9 +98,9 @@ export const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
|
|
|
91
98
|
|
|
92
99
|
Spawn sequentially, waiting for each result:
|
|
93
100
|
\`\`\`
|
|
94
|
-
team_spawn name:"ui1" agent:"frontend-engineer" prompt:"..."
|
|
101
|
+
team_spawn name:"ui1" agent:"frontend-engineer" model:"<resolved-model>" prompt:"..."
|
|
95
102
|
(wait for result)
|
|
96
|
-
team_spawn name:"api1" agent:"backend-engineer" prompt:"..."
|
|
103
|
+
team_spawn name:"api1" agent:"backend-engineer" model:"<resolved-model>" prompt:"..."
|
|
97
104
|
(wait for result)
|
|
98
105
|
\`\`\`
|
|
99
106
|
Replace example agent names with REAL engineers that exist in this project.
|
|
@@ -133,7 +140,7 @@ export const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
|
|
|
133
140
|
6. **If ALL agents are shut down and tasks remain unassigned** (new domain, dependencies unblocked):
|
|
134
141
|
- Discover the remaining matching engineers from \`.opencode/agents/\` and spawn a new wave (back to step 6d).
|
|
135
142
|
7. **If ALL tasks are done:** proceed to step 7.
|
|
136
|
-
If a teammate reports rate-limit/quota/token exhaustion, immediately shutdown that teammate and respawn with
|
|
143
|
+
If a teammate reports rate-limit/quota/token exhaustion, immediately shutdown that teammate and respawn with a different model using the model resolution priority above.
|
|
137
144
|
|
|
138
145
|
**ZERO PENDING TASKS GUARANTEE:** Before proceeding to step 7, call \`team_tasks_list\` and verify EVERY task is either \`done\` or \`blocked\`. If any task is \`pending\` and unassigned, assign it to an agent or spawn a new one. Never leave pending tasks orphaned.
|
|
139
146
|
|
|
@@ -141,7 +148,7 @@ export const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
|
|
|
141
148
|
|
|
142
149
|
Spawn the best available verification-capable engineer with \`worktree:false\` (for example, a testing-focused custom engineer or \`basic-engineer\` if no better verifier exists):
|
|
143
150
|
\`\`\`
|
|
144
|
-
team_spawn name:"verify" agent:"<real-verifier-engineer>" worktree:false prompt:"<verification scope, context summary, run tests + build + lint + verify acceptance criteria, no task claiming required in this phase, send results to lead when done>"
|
|
151
|
+
team_spawn name:"verify" agent:"<real-verifier-engineer>" worktree:false model:"<resolved-model>" prompt:"<verification scope, context summary, run tests + build + lint + verify acceptance criteria, no task claiming required in this phase, send results to lead when done>"
|
|
145
152
|
\`\`\`
|
|
146
153
|
Wait for message -> team_results -> fix blockers -> team_shutdown (no team_merge needed, worktree:false)
|
|
147
154
|
|
|
@@ -165,6 +172,7 @@ export const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
|
|
|
165
172
|
- NEVER implement tasks directly. Always use team_create + team_spawn, no exceptions
|
|
166
173
|
- NEVER touch source files before team_create is called, not even one edit
|
|
167
174
|
- NEVER call team_spawn without the agent field, it is required and will fail without it
|
|
175
|
+
- NEVER call team_spawn without a resolved \`model:\` field; always pass the model resolved from the priority chain
|
|
168
176
|
- NEVER call team_spawn before all tasks are on the board; use multiple \`team_tasks_add\` calls when dependencies require real IDs from earlier calls
|
|
169
177
|
- NEVER poll team_results or team_status in a loop, wait for teammates to message you
|
|
170
178
|
- NEVER call team_claim or team_tasks_complete as lead, only agents call these tools
|