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.
@@ -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
- - never spawn an engineer name that is not present in `.opencode/agents/`
126
-
127
- REQUIRED assignment algorithm (do not skip):
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
- Spawn sequentially, waiting for each result:
178
- ```
179
- team_spawn name:"ui1" agent:"frontend-engineer" prompt:"..."
180
- (wait for result)
181
- team_spawn name:"api1" agent:"backend-engineer" prompt:"..."
182
- (wait for result)
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 before all tasks are on the board; use multiple `team_tasks_add` calls when dependencies require real IDs from earlier calls
257
- - NEVER poll team_results or team_status in a loop, wait for teammates to message you
258
- - NEVER call team_claim or team_tasks_complete as lead, only agents call these tools
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
- - never spawn an engineer name that is not present in `.opencode/agents/`
131
+ - never spawn an engineer name that is not present in `.opencode/agents/`
132
132
 
133
- REQUIRED assignment algorithm (do not skip):
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
- Spawn sequentially, waiting for each result:
185
- ```
186
- team_spawn name:"ui1" agent:"frontend-engineer" prompt:"..."
187
- (wait for result)
188
- team_spawn name:"api1" agent:"backend-engineer" prompt:"..."
189
- (wait for result)
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. `team_spawn` same member/task with an available model
252
- 3. `team_message` start instruction with the exact next task ID
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-onboard",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Prepare any codebase for AI agent workflows using OpenCode, OpenSpec, and ensemble orchestration.",
5
5
  "keywords": [
6
6
  "opencode",
@@ -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
- import { readOnboardConfig } from './shared.js'
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
+ })
@@ -1,7 +1,8 @@
1
1
  import { select as wizardSelect } from '@inquirer/prompts'
2
- import chalk from 'chalk'
3
- import { chooseSourceScope } from '../steps/source/index.js'
4
- import { cleanAiFiles } from '../steps/clean/index.js'
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 scope = await chooseSourceScope()
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 an available model.
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