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/agents/back-engineer.md +8 -5
- package/content/.agents/agents/devops-manager.md +6 -2
- package/content/.agents/agents/front-engineer.md +8 -5
- package/content/.agents/agents/infra-engineer.md +15 -4
- package/content/.agents/agents/quality-engineer.md +9 -6
- package/content/.agents/agents/security-auditor.md +6 -4
- package/content/.agents/skills/ob-pullrequest-gh/SKILL.md +8 -3
- package/content/.agents/skills/ob-userstory-gh/SKILL.md +5 -5
- package/content/.opencode/commands/opsx-apply.md +14 -6
- package/content/.opencode/opencode.json +3 -3
- package/content/.opencode/plugins/session-log.js +141 -13
- package/content/.opencode/skills/openspec-apply-change/SKILL.md +14 -6
- package/content/AGENTS.md +4 -0
- package/package.json +1 -1
- package/src/index.js +127 -172
- package/src/steps/choose-models.js +4 -2
- package/src/steps/copy-content.js +2 -2
- package/src/steps/enable-caveman-guidance.js +4 -19
- package/src/steps/init-openspec.js +21 -10
- package/src/steps/install-quota.js +2 -2
- package/src/steps/patch-agents-md.js +74 -6
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
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 {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
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 {
|
|
22
|
-
import {
|
|
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
|
-
|
|
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
|
-
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
!ctx.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
console.log()
|
|
275
|
-
console.log(chalk.bold.green('
|
|
276
|
-
console.log(chalk.bold.green('
|
|
277
|
-
console.log(
|
|
278
|
-
console.log()
|
|
279
|
-
console.log('
|
|
280
|
-
console.log(
|
|
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 =
|
|
134
|
+
config.model = buildModel
|
|
135
135
|
await fse.writeJson(opencodeJsonPath, config, { spaces: 2 })
|
|
136
|
-
success(`
|
|
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
|
|
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
|
|
9
|
+
## Caveman Mode
|
|
10
10
|
|
|
11
|
-
|
|
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
|
|
15
|
+
## Caveman Mode
|
|
28
16
|
|
|
29
|
-
|
|
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.**
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
69
|
-
team_message to:"front" text:"Start now.
|
|
70
|
-
team_message to:"infra" text:"Start now.
|
|
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:"<
|
|
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
|
|
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 {
|
|
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
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
}
|