opencode-onboard 0.2.14 → 0.3.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/AGENTS.md CHANGED
@@ -299,6 +299,8 @@ Skills are located in `.agents/skills/`. Each skill has a `SKILL.md` with a desc
299
299
  Format: `feature/{issue-id}-{slug}`
300
300
  Example: `feature/42-add-user-auth`
301
301
 
302
+ When `## Source Roots` lists multiple roots, each root is an independent git repository. The same branch name must be created in every repo that will have changes. Git operations (`branch`, `commit`, `push`) run once per repository — there is no shared git history.
303
+
302
304
  ---
303
305
 
304
306
  ## Project Structure
@@ -349,6 +351,8 @@ Agents CANNOT:
349
351
  - ❌ Merge PRs, human-only
350
352
  - ❌ Create or delete branches other than `feature/*`
351
353
 
354
+ **Multi-repo**: When `## Source Roots` lists multiple roots, each is an independent git repository with its own history. All `rtk git` commands must be issued per repository. Never assume one `git` context covers all repos. Create the feature branch in each repo, commit per repo, push per repo, open one PR per repo.
355
+
352
356
  ### Platform CLI
353
357
 
354
358
  ALL platform interactions via CLI only. Browser MCP and webfetch FORBIDDEN for any DevOps or GitHub operation, use `gh` or `az` CLI exclusively, never fall back to HTTP requests.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-onboard",
3
- "version": "0.2.14",
3
+ "version": "0.3.3",
4
4
  "description": "Prepare any brownfield codebase for AI agent workflows using OpenCode, OpenSpec, and ensemble orchestration.",
5
5
  "keywords": [
6
6
  "opencode",
package/src/index.js CHANGED
@@ -1,26 +1,25 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  import chalk from 'chalk'
3
+ import fse from 'fs-extra'
3
4
  import { createRequire } from 'node:module'
4
5
  import path from 'node:path'
5
- import fse from 'fs-extra'
6
6
  import { checkEnv } from './steps/check-env.js'
7
7
  import { checkPlatform } from './steps/check-platform.js'
8
8
  import { checkRtk } from './steps/check-rtk.js'
9
- import { chooseModels } from './steps/choose-models.js'
9
+ import { chooseModels } from './steps/choose-models.js'
10
10
  import { choosePlatform } from './steps/choose-platform.js'
11
- import { chooseSourceScope } from './steps/choose-source-scope.js'
12
11
  import { chooseSkillsProvider } from './steps/choose-skills-provider.js'
13
- import { cleanAiFiles } from './steps/clean-ai-files.js'
14
- import { copyContentStep } from './steps/copy-content.js'
15
- import { initOpenspec } from './steps/init-openspec.js'
16
- import { patchAgentsMd } from './steps/patch-agents-md.js'
17
- import { installQuota } from './steps/install-quota.js'
18
- import { installCaveman } from './steps/install-caveman.js'
12
+ import { chooseSourceScope } from './steps/choose-source-scope.js'
13
+ import { cleanAiFiles } from './steps/clean-ai-files.js'
14
+ import { copyContentStep } from './steps/copy-content.js'
19
15
  import { enableCavemanGuidance } from './steps/enable-caveman-guidance.js'
16
+ import { initOpenspec } from './steps/init-openspec.js'
20
17
  import { installBrowser } from './steps/install-browser.js'
21
- import { writeOnboardConfig } from './steps/write-onboard-config.js'
22
- import { loading } from './utils/exec.js'
18
+ import { installCaveman } from './steps/install-caveman.js'
19
+ import { installQuota } from './steps/install-quota.js'
20
+ import { patchAgentsMd } from './steps/patch-agents-md.js'
23
21
  import { tokenOptimizationStep } from './steps/token-optimization.js'
22
+ import { writeOnboardConfig } from './steps/write-onboard-config.js'
24
23
 
25
24
  function printHelp(version) {
26
25
  console.log(`opencode-onboard v${version}`)
@@ -70,78 +69,60 @@ async function runSingleCommand(command) {
70
69
  const platform = savedWizard?.platform
71
70
  const resolvedPlatform = platform === 'azure' || platform === 'github' ? platform : 'github'
72
71
 
73
- if (command === 'clean') {
74
- await cleanAiFiles()
75
- return true
76
- }
77
-
78
- if (command === 'platform') {
79
- await choosePlatform()
80
- return true
81
- }
82
-
83
- if (command === 'copy') {
84
- await copyContentStep(resolvedPlatform, ctx)
85
- await patchAgentsMd(ctx)
86
- return true
87
- }
88
-
89
- if (command === 'openspec') {
90
- await initOpenspec()
91
- return true
92
- }
93
-
94
- if (command === 'skills') {
95
- await chooseSkillsProvider()
96
- return true
97
- }
98
-
99
- if (command === 'models') {
100
- await chooseModels()
101
- return true
102
- }
103
-
104
- if (command === 'optimization') {
105
- await tokenOptimizationStep({ skillsProvider: savedWizard?.additionalSkillsProvider })
106
- return true
107
- }
108
-
109
- if (command === 'quota') {
110
- await installQuota()
111
- return true
112
- }
113
-
114
- if (command === 'rtk') {
115
- await checkRtk()
116
- return true
117
- }
118
-
119
- if (command === 'caveman') {
120
- const caveman = await installCaveman({ skillsProvider: savedWizard?.additionalSkillsProvider })
121
- await enableCavemanGuidance(caveman)
122
- return true
123
- }
124
-
125
- if (command === 'browser') {
126
- await installBrowser()
127
- return true
128
- }
129
-
130
- if (command === 'metadata') {
131
- await writeOnboardConfig({
132
- ...ctx,
133
- platform: resolvedPlatform,
134
- additionalSkillsProvider: savedWizard?.additionalSkillsProvider ?? 'none',
135
- planModel: savedWizard?.models?.plan ?? null,
136
- buildModel: savedWizard?.models?.build ?? null,
137
- fastModel: savedWizard?.models?.fast ?? null,
138
- optionalTools: savedWizard?.optionalTools ?? null,
139
- cavemanGuidance: savedWizard?.cavemanGuidance ?? null,
140
- })
141
- return true
142
- }
143
-
144
- return false
72
+ const handlers = {
73
+ clean: async () => {
74
+ await cleanAiFiles()
75
+ },
76
+ platform: async () => {
77
+ await choosePlatform()
78
+ },
79
+ copy: async () => {
80
+ await copyContentStep(resolvedPlatform, ctx)
81
+ await patchAgentsMd(ctx)
82
+ },
83
+ openspec: async () => {
84
+ await initOpenspec()
85
+ },
86
+ skills: async () => {
87
+ await chooseSkillsProvider()
88
+ },
89
+ models: async () => {
90
+ await chooseModels()
91
+ },
92
+ optimization: async () => {
93
+ await tokenOptimizationStep({ skillsProvider: savedWizard?.additionalSkillsProvider })
94
+ },
95
+ quota: async () => {
96
+ await installQuota()
97
+ },
98
+ rtk: async () => {
99
+ await checkRtk()
100
+ },
101
+ caveman: async () => {
102
+ const caveman = await installCaveman({ skillsProvider: savedWizard?.additionalSkillsProvider })
103
+ await enableCavemanGuidance(caveman)
104
+ },
105
+ browser: async () => {
106
+ await installBrowser()
107
+ },
108
+ metadata: async () => {
109
+ await writeOnboardConfig({
110
+ ...ctx,
111
+ platform: resolvedPlatform,
112
+ additionalSkillsProvider: savedWizard?.additionalSkillsProvider ?? 'none',
113
+ planModel: savedWizard?.models?.plan ?? null,
114
+ buildModel: savedWizard?.models?.build ?? null,
115
+ fastModel: savedWizard?.models?.fast ?? null,
116
+ optionalTools: savedWizard?.optionalTools ?? null,
117
+ cavemanGuidance: savedWizard?.cavemanGuidance ?? null,
118
+ })
119
+ },
120
+ }
121
+
122
+ const handler = handlers[command]
123
+ if (!handler) return false
124
+ await handler()
125
+ return true
145
126
  }
146
127
 
147
128
  if (process.stdout.isTTY) console.clear()
@@ -168,94 +149,69 @@ if (args.length > 0) {
168
149
 
169
150
  const logo = chalk.hex('#fe3d57')
170
151
  const bannerLines = [
171
- logo(' '),
172
- logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒ '),
173
- logo(' ▒▒▓ ▓▒▓ '),
174
- logo(' ▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▒ '),
175
- logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
176
- logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓ '),
177
- logo(' ▓▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒░░░▒▒▒▓▓ '),
178
- logo(' ▓▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓▓▓ '),
179
- logo(' ▓▓▒▒▒▒▒▒░▒▒▒▒▒▒▒░▒▒▒▒▒▒▓▓ '),
180
- logo(' ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
181
- logo(' ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
182
- logo(' ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ '),
183
- '',
184
- chalk.bold(' 🧰 opencode-onboard') + chalk.dim(` v${version}`),
185
- chalk.dim(' Prepare your codebase for AI agents'),
186
- ]
187
-
188
- for (const line of bannerLines) console.log(line)
189
- console.log()
190
- console.log(' This tool will set up your project with a team of AI agents,')
191
- console.log(' install skills, select models, and configure OpenCode.')
192
- console.log()
193
-
194
- // Only wait for Enter in a real interactive TTY
195
- if (process.stdin.isTTY) {
196
- console.log(chalk.bold(' Press Enter to begin...'))
197
- console.log()
198
- await new Promise(resolve => {
199
- process.stdin.resume()
200
- process.stdin.once('data', () => {
201
- process.stdin.pause()
202
- resolve()
203
- })
204
- })
205
- }
206
-
207
- try {
208
- // 1. Check Node + pnpm
152
+ logo(' '),
153
+ logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒ '),
154
+ logo(' ▒▒▓ ▓▒▓ '),
155
+ logo(' ▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▒ '),
156
+ logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
157
+ logo(' ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓ '),
158
+ logo(' ▓▒▒▒▒░░░▒▒▒▒▒▒▒▒▒▒▒░░░▒▒▒▓▓ '),
159
+ logo(' ▓▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓▓▓ '),
160
+ logo(' ▓▓▒▒▒▒▒▒░▒▒▒▒▒▒▒░▒▒▒▒▒▒▓▓ '),
161
+ logo(' ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
162
+ logo(' ▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓ '),
163
+ logo(' ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ '),
164
+ '',
165
+ chalk.bold(' 🧰 opencode-onboard') + chalk.dim(` v${version}`),
166
+ chalk.dim(' Prepare your codebase for AI agents'),
167
+ ]
168
+
169
+ for (const line of bannerLines) console.log(line)
170
+ console.log()
171
+ console.log(' This tool will set up your project with a team of AI agents,')
172
+ console.log(' install skills, select models, and configure OpenCode.')
173
+ console.log()
174
+
175
+ // Only wait for Enter in a real interactive TTY
176
+ if (process.stdin.isTTY) {
177
+ console.log(chalk.bold(' Press Enter to begin...'))
178
+ console.log()
179
+ await new Promise(resolve => {
180
+ process.stdin.resume()
181
+ process.stdin.once('data', () => {
182
+ process.stdin.pause()
183
+ resolve()
184
+ })
185
+ })
186
+ }
187
+
188
+ try {
209
189
  await checkEnv()
210
- loading('preparing next step...')
211
190
 
212
- // 2. Choose source code scope for init analysis
213
191
  const scope = await chooseSourceScope()
214
- loading('preparing next step...')
215
192
 
216
- // 3. Clean existing AI config files, detect preserved state
217
193
  const preserve = await cleanAiFiles()
218
194
  const ctx = { ...preserve, ...scope }
219
- loading('preparing next step...')
220
195
 
221
- // 4. Choose platform
222
196
  const platform = await choosePlatform()
223
- loading('preparing next step...')
224
-
225
- // 5. Check platform CLI (az or gh)
197
+
226
198
  await checkPlatform(platform)
227
- loading('preparing next step...')
228
199
 
229
- // 6. Copy content
230
200
  await copyContentStep(platform, ctx)
231
- loading('preparing next step...')
232
201
 
233
- // 6b. Patch AGENTS.md to skip steps for already-existing files
234
202
  await patchAgentsMd(ctx)
235
- loading('preparing next step...')
236
203
 
237
- // 7. Init OpenSpec
238
204
  await initOpenspec()
239
- loading('preparing next step...')
240
205
 
241
- // 8. Install skills
242
206
  const skillsSelection = await chooseSkillsProvider()
243
- loading('preparing next step...')
244
-
245
- // 9. Choose models
207
+
246
208
  const selectedModels = await chooseModels()
247
- loading('preparing next step...')
248
209
 
249
- // 10. Token optimization tools
250
210
  const tokenOpt = await tokenOptimizationStep({ skillsProvider: skillsSelection.additionalSkillsProvider })
251
211
  const { rtk, quota, caveman, cavemanGuidance } = tokenOpt
252
- loading('preparing next step...')
253
212
 
254
- // 11. Install opencode-browser
255
213
  await installBrowser()
256
- loading('preparing next step...')
257
214
 
258
- // 12. Write onboarding metadata
259
215
  await writeOnboardConfig({
260
216
  ...ctx,
261
217
  platform,
@@ -264,21 +220,20 @@ try {
264
220
  optionalTools: { rtk, quota, caveman },
265
221
  cavemanGuidance,
266
222
  })
267
-
268
- // Done
269
- const toGenerate = [
270
- !ctx.hasDesign && 'DESIGN.md',
271
- !ctx.hasArchitecture && 'ARCHITECTURE.md',
272
- ].filter(Boolean)
273
-
274
- console.log()
275
- console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
276
- console.log(chalk.bold.green(' Onboarding complete!'))
277
- console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
278
- console.log()
279
- console.log(' Open this project in OpenCode and type:')
280
- console.log(chalk.bold(' "init"'))
281
- console.log()
223
+
224
+ const toGenerate = [
225
+ !ctx.hasDesign && 'DESIGN.md',
226
+ !ctx.hasArchitecture && 'ARCHITECTURE.md',
227
+ ].filter(Boolean)
228
+
229
+ console.log()
230
+ console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
231
+ console.log(chalk.bold.green(' Onboarding complete!'))
232
+ console.log(chalk.bold.green('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'))
233
+ console.log()
234
+ console.log(' Open this project in OpenCode and type:')
235
+ console.log(chalk.bold(' "init"'))
236
+ console.log()
282
237
  if (toGenerate.length > 0) {
283
238
  console.log(` OpenCode will generate ${toGenerate.join(' and ')}`)
284
239
  console.log(' from your actual codebase, then activate the agent team.')
@@ -287,12 +242,12 @@ try {
287
242
  }
288
243
  console.log(` Source scope: ${ctx.sourceMode === 'parent-selected' ? ctx.sourceRoots.map(p => `../${p.split(/[/\\]/).pop()}`).join(', ') : 'current folder'}`)
289
244
  console.log()
290
- } catch (err) {
291
- if (err.name === 'ExitPromptError') {
292
- console.log()
293
- console.log(chalk.yellow('Cancelled.'))
294
- } else {
295
- console.error(chalk.red('\nUnexpected error:'), err.message)
296
- process.exit(1)
297
- }
298
- }
245
+ } catch (err) {
246
+ if (err.name === 'ExitPromptError') {
247
+ console.log()
248
+ console.log(chalk.yellow('Cancelled.'))
249
+ } else {
250
+ console.error(chalk.red('\nUnexpected error:'), err.message)
251
+ process.exit(1)
252
+ }
253
+ }
@@ -131,9 +131,9 @@ export async function chooseModels() {
131
131
  const opencodeJsonPath = path.join(process.cwd(), '.opencode', 'opencode.json')
132
132
  if (await fse.pathExists(opencodeJsonPath)) {
133
133
  const config = await fse.readJson(opencodeJsonPath)
134
- config.model = planModel
134
+ config.model = buildModel
135
135
  await fse.writeJson(opencodeJsonPath, config, { spaces: 2 })
136
- success(`plan model -> ${planModel} (written to .opencode/opencode.json)`)
136
+ success(`default model -> ${buildModel} (written to .opencode/opencode.json)`)
137
137
  }
138
138
 
139
139
  // Write build and fast models to ensemble.json
@@ -143,10 +143,12 @@ export async function chooseModels() {
143
143
  delete ensemble.defaultModel
144
144
  ensemble.modelsByAgent = {
145
145
  ...ensemble.modelsByAgent,
146
+ plan: planModel,
146
147
  build: buildModel,
147
148
  explore: fastModel,
148
149
  }
149
150
  await fse.writeJson(ensembleJsonPath, ensemble, { spaces: 2 })
151
+ success(`plan model -> ${planModel} (written to .opencode/ensemble.json)`)
150
152
  success(`build model -> ${buildModel} (written to .opencode/ensemble.json)`)
151
153
  success(`fast model -> ${fastModel} (written to .opencode/ensemble.json)`)
152
154
  }
@@ -1,6 +1,6 @@
1
+ import fse from 'fs-extra'
1
2
  import path from 'path'
2
3
  import { fileURLToPath } from 'url'
3
- import fse from 'fs-extra'
4
4
  import { copyContent } from '../utils/copy.js'
5
5
  import { error, header, success } from '../utils/exec.js'
6
6
 
@@ -12,7 +12,7 @@ function formatRootsForText(roots = [], cwd = process.cwd()) {
12
12
  return roots.map(r => {
13
13
  const rel = path.relative(cwd, r)
14
14
  if (!rel || rel === '') return 'current folder'
15
- if (!rel.startsWith('..')) return `./${rel.replace(/\\/g, '/')}`
15
+ if (!rel.startsWith('..')) return rel
16
16
  return rel.replace(/\\/g, '/')
17
17
  })
18
18
  }
@@ -6,30 +6,15 @@ const MARKER_START = '<!-- CAVEMAN-GUIDANCE-START -->'
6
6
  const MARKER_END = '<!-- CAVEMAN-GUIDANCE-END -->'
7
7
 
8
8
  const AGENTS_BLOCK = `${MARKER_START}
9
- ## Caveman Mode (Optional)
9
+ ## Caveman Mode
10
10
 
11
- If caveman is installed, prefer concise responses by default to reduce token usage while keeping full technical accuracy.
12
-
13
- Treat any of these as a direct trigger to enable concise style:
14
- - "talk like caveman"
15
- - "caveman mode"
16
- - "less tokens please"
17
-
18
- When enabled:
19
- - Keep explanations short and direct
20
- - Prioritize exact commands, paths, and outcomes
21
- - Avoid filler, greetings, and repeated context
22
-
23
- The user can disable it with phrases like: "normal mode" or "stop caveman".
11
+ caveman mode. Active now. Every response. No revert.
24
12
  ${MARKER_END}`
25
13
 
26
14
  const OPSX_BLOCK = `${MARKER_START}
27
- ## Caveman Output Style
15
+ ## Caveman Mode
28
16
 
29
- If the user requests "talk like caveman", "caveman mode", or "less tokens please", keep all status updates concise:
30
- - Report only key actions, blockers, and next commands
31
- - Keep completion updates brief and factual
32
- - Preserve technical precision; compress wording only
17
+ caveman mode. Active now. Every response. No revert.
33
18
  ${MARKER_END}`
34
19
 
35
20
  async function appendBlockIfMissing(filePath, block) {
@@ -38,19 +38,27 @@ const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
38
38
  DO NOT call team_claim yourself, only agents claim tasks.
39
39
  DO NOT proceed to 6d until team_tasks_add succeeds.
40
40
 
41
- **Step 6d.** Spawn all needed specialists, then kick them off in parallel.
41
+ **Step 6d.** Discover relevant skills, then spawn specialists.
42
+
43
+ Before spawning, scan \`.agents/skills/\` and read each \`SKILL.md\` description line.
44
+ Match skills to agents by domain:
45
+ - front-engineer: UI, components, framework skills (e.g. next-best-practices, browser-automation)
46
+ - back-engineer: API, data, service skills
47
+ - infra-engineer: cloud, pipeline, deployment skills
48
+ - quality-engineer: testing, coverage skills
42
49
 
43
50
  Each team_spawn MUST include the agent field (required, causes NOT NULL error if omitted).
44
51
 
45
52
  The spawn prompt must contain exactly:
46
53
  1. Their name and role on this team
47
- 2. Which tasks are theirs, list the task IDs and content from the board
54
+ 2. Which tasks are theirs include the LITERAL task IDs (e.g. "task-abc123") AND the task content for each. Copy them verbatim from the IDs returned by team_tasks_add. Do NOT paraphrase or omit IDs.
48
55
  3. Key context they need (summarized from context files, do NOT tell them to read files themselves)
49
56
  4. The 6 OpenCode tools they have available (these are OpenCode tools, NOT shell commands, call them directly as tools, never via bash):
50
57
  team_claim, team_tasks_complete, team_tasks_list, team_tasks_add, team_message, team_broadcast
51
- 5. How to proceed: call team_claim tool with the task_id to claim a task before starting it, call team_tasks_complete tool after finishing it, repeat until all their tasks are done, then call team_message tool to notify lead with results or blockers
58
+ 5. How to proceed: for EACH task ID listed above, call team_claim tool with that exact task_id before starting it, call team_tasks_complete tool with that task_id after finishing it, then move to the next task. When all tasks are done or blocked, call team_message to notify lead with results or blockers.
59
+ 6. Which skills to load: list the skill names and paths they MUST read before implementing. Example: "Before starting, read \`.agents/skills/next-best-practices/SKILL.md\` and follow its rules for all Next.js code."
52
60
 
53
- Keep spawn prompts under 500 tokens. Do not describe team internals or how ensemble works.
61
+ Keep spawn prompts under 600 tokens. Do not describe team internals or how ensemble works.
54
62
  Only spawn agents whose tasks are actually needed by this change. Skip agents with no tasks.
55
63
 
56
64
  First spawn all agents (wait for each team_spawn to confirm before the next):
@@ -63,12 +71,13 @@ const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
63
71
  (wait for result)
64
72
  \`\`\`
65
73
 
66
- Then immediately send each spawned agent a start message to kick them off:
74
+ Then immediately send each spawned agent a start message that repeats their task IDs:
67
75
  \`\`\`
68
- team_message to:"back" text:"Start now. Claim your first task with team_claim and begin implementing."
69
- team_message to:"front" text:"Start now. Claim your first task with team_claim and begin implementing."
70
- team_message to:"infra" text:"Start now. Claim your first task with team_claim and begin implementing."
76
+ team_message to:"back" text:"Start now. Load skills first. Your tasks: [task-<id1>] <task1 text>, [task-<id2>] <task2 text>. Call team_claim task_id:<id> for each before starting it."
77
+ team_message to:"front" text:"Start now. Load skills first. Your tasks: [task-<id3>] <task3 text>. Call team_claim task_id:<id> before starting it."
78
+ team_message to:"infra" text:"Start now. Load skills first. Your tasks: [task-<id4>] <task4 text>. Call team_claim task_id:<id> before starting it."
71
79
  \`\`\`
80
+ Replace placeholders with REAL task IDs and content. Never send a generic "claim your first task" message without the actual IDs.
72
81
 
73
82
  **Step 6e.** After sending start messages, tell the user what is running, then STOP and wait.
74
83
  Do NOT call team_results, team_status, or team_broadcast in a loop.
@@ -89,7 +98,7 @@ const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
89
98
 
90
99
  Spawn quality engineer with worktree:false (read-only, no file edits):
91
100
  \`\`\`
92
- team_spawn name:"quality" agent:"quality-engineer" worktree:false prompt:"<task list, context summary, run tests + build + lint + verify acceptance criteria, send results to lead when done>"
101
+ team_spawn name:"quality" agent:"quality-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>"
93
102
  \`\`\`
94
103
  Wait for message -> team_results -> fix blockers -> team_shutdown (no team_merge needed, worktree:false)
95
104
 
@@ -116,7 +125,9 @@ const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
116
125
  - NEVER call team_spawn before team_tasks_add, tasks must exist before agents are spawned
117
126
  - NEVER poll team_results or team_status in a loop, wait for teammates to message you
118
127
  - NEVER call team_claim or team_tasks_complete as lead, only agents call these tools
119
- - ALWAYS pass the task IDs returned by team_tasks_add to each agent's spawn prompt
128
+ - ALWAYS pass the LITERAL task IDs returned by team_tasks_add into each agent's spawn prompt — copy the exact IDs, never paraphrase
129
+ - ALWAYS repeat the same literal task IDs in the team_message start trigger — never send a generic "claim your first task" without the actual IDs
130
+ - NEVER send a start message that omits task IDs; if a task ID is missing from the start message, the agent cannot claim
120
131
  - NEVER edit files between team_spawn and team_merge, team_merge blocks on overlapping local changes
121
132
  - ALWAYS add every task to the board with team_tasks_add before spawning
122
133
  - ALWAYS spawn agents sequentially (wait for each team_spawn result before the next), then send start messages to all of them together
@@ -1,9 +1,9 @@
1
1
  import { confirm } from '@inquirer/prompts'
2
2
  import fse from 'fs-extra'
3
3
  import path from 'node:path'
4
- import { header, success, warn, error, loading, info } from '../utils/exec.js'
4
+ import { error, header, info, loading, success, warn } from '../utils/exec.js'
5
5
 
6
- const PLUGIN = '@slkiser/opencode-quota'
6
+ const PLUGIN = '@slkiser/opencode-quota@latest'
7
7
 
8
8
  function ensurePlugin(config) {
9
9
  if (!Array.isArray(config.plugin)) config.plugin = []
@@ -2,6 +2,70 @@ import fse from 'fs-extra'
2
2
  import path from 'path'
3
3
  import { info, success } from '../utils/exec.js'
4
4
 
5
+ // Agent files that receive a Source Roots injection when parent folders are selected
6
+ const AGENT_FILES = [
7
+ '.agents/agents/front-engineer.md',
8
+ '.agents/agents/back-engineer.md',
9
+ '.agents/agents/infra-engineer.md',
10
+ '.agents/agents/quality-engineer.md',
11
+ '.agents/agents/security-auditor.md',
12
+ '.agents/agents/devops-manager.md',
13
+ ]
14
+
15
+ /**
16
+ * Build a markdown Source Roots block from an array of absolute paths.
17
+ */
18
+ function buildSourceRootsBlock(sourceRoots, cwd) {
19
+ const bullets = sourceRoots.map(r => {
20
+ const rel = path.relative(cwd, r).replace(/\\/g, '/')
21
+ return `- \`${rel}\``
22
+ }).join('\n')
23
+ const multiRepoNote = sourceRoots.length > 1
24
+ ? `\nEach root is an independent git repository. When branching, committing, or pushing, ALL repositories must be operated separately — create the feature branch in each repo, commit changes per repo, and push each repo independently. There is no single repo; \`rtk git\` commands must be run once per repository root.\n`
25
+ : ''
26
+ return `## Source Roots\n\nThe user selected these source repositories during onboarding.\nSearch and read code ONLY from these roots — do not assume code lives in the current folder.\n${multiRepoNote}\n${bullets}\n`
27
+ }
28
+
29
+ /**
30
+ * Inject source roots into AGENTS.md (replaces the generic Source Scope section)
31
+ * and into every agent file (inserts after the ## Domain section).
32
+ */
33
+ async function patchSourceRootsIntoAgents(cwd, sourceRoots) {
34
+ const block = buildSourceRootsBlock(sourceRoots, cwd)
35
+
36
+ // --- AGENTS.md: replace the ## Source Scope section ---
37
+ const agentsMdPath = path.join(cwd, 'AGENTS.md')
38
+ if (await fse.pathExists(agentsMdPath)) {
39
+ let content = await fse.readFile(agentsMdPath, 'utf-8')
40
+ // Replace the generic section between ## Source Scope and the next ## heading
41
+ content = content.replace(
42
+ /## Source Scope\n[\s\S]*?(?=\n## )/,
43
+ `## Source Roots\n\n${block.replace('## Source Roots\n\n', '')}\n`
44
+ )
45
+ await fse.writeFile(agentsMdPath, content, 'utf-8')
46
+ info('AGENTS.md: Source Roots section injected')
47
+ }
48
+
49
+ // --- Agent files: insert ## Source Roots after ## Domain section ---
50
+ for (const relFile of AGENT_FILES) {
51
+ const agentPath = path.join(cwd, relFile)
52
+ if (!await fse.pathExists(agentPath)) continue
53
+
54
+ let content = await fse.readFile(agentPath, 'utf-8')
55
+
56
+ // Skip if already patched
57
+ if (content.includes('## Source Roots')) continue
58
+
59
+ // Insert after the ## Domain section (after its paragraph block, before next ##)
60
+ content = content.replace(
61
+ /(## Domain\n[\s\S]*?)(\n## )/,
62
+ `$1\n${block}\n## `
63
+ )
64
+ await fse.writeFile(agentPath, content, 'utf-8')
65
+ info(`${path.basename(agentPath)}: Source Roots section injected`)
66
+ }
67
+ }
68
+
5
69
  // Each block is identified by its heading line. We remove from the heading up to (and including) the next `---` separator.
6
70
  const STEP1_HEADING = '### Step 1, Archive project history into OpenSpec'
7
71
  const STEP2_HEADING = '### Step 2, Generate DESIGN.md'
@@ -75,11 +139,15 @@ export async function patchAgentsMd(ctx) {
75
139
  patches.push('Step 3 (ARCHITECTURE.md) removed, ARCHITECTURE.md already exists')
76
140
  }
77
141
 
78
- if (patches.length === 0) return
79
-
80
- content = renumberSteps(content)
81
- await fse.writeFile(agentsMdPath, content, 'utf-8')
142
+ if (patches.length > 0) {
143
+ content = renumberSteps(content)
144
+ await fse.writeFile(agentsMdPath, content, 'utf-8')
145
+ for (const msg of patches) info(msg)
146
+ success('AGENTS.md patched for existing project state')
147
+ }
82
148
 
83
- for (const msg of patches) info(msg)
84
- success('AGENTS.md patched for existing project state')
149
+ if (ctx.sourceMode === 'parent-selected' && Array.isArray(ctx.sourceRoots) && ctx.sourceRoots.length > 0) {
150
+ await patchSourceRootsIntoAgents(process.cwd(), ctx.sourceRoots)
151
+ success('Source roots injected into AGENTS.md and agent files')
152
+ }
85
153
  }