opencode-onboard 0.2.3 → 0.2.6
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 +38 -6
- package/package.json +1 -1
- package/src/index.js +13 -4
- package/src/steps/choose-models.js +6 -0
- package/src/steps/choose-skills-provider.js +5 -1
- package/src/steps/copy-content.js +61 -0
- package/src/steps/init-openspec.js +7 -0
- package/src/steps/write-onboard-config.js +59 -0
package/content/AGENTS.md
CHANGED
|
@@ -16,6 +16,8 @@ When the user says anything resembling initialization, "init", "initialize", "se
|
|
|
16
16
|
|
|
17
17
|
Scan the codebase for any existing documentation, changelogs, ADRs, README files, or notable history that describes decisions already made in this project. Create an OpenSpec archive entry that captures this history so agents have context going forward.
|
|
18
18
|
|
|
19
|
+
Before scanning, load source roots from `.agents/source-roots.json` when present. Only scan those roots plus this repo's docs/config files.
|
|
20
|
+
|
|
19
21
|
```bash
|
|
20
22
|
openspec new change "project-history"
|
|
21
23
|
```
|
|
@@ -112,6 +114,12 @@ This is the agent orchestration layer for your project. It provides:
|
|
|
112
114
|
- OpenSpec change management
|
|
113
115
|
- Skills for platform-specific knowledge
|
|
114
116
|
|
|
117
|
+
## Source Scope
|
|
118
|
+
|
|
119
|
+
- Read source scope from `.agents/source-roots.json`.
|
|
120
|
+
- Use those roots for codebase analysis tasks (design, architecture, project-history, exploration).
|
|
121
|
+
- If missing, default to current folder.
|
|
122
|
+
|
|
115
123
|
## I Am the Lead, Full Workflow Ownership
|
|
116
124
|
|
|
117
125
|
When the user provides a work item URL or says "implement the plan" or "I've added comments to the PR", **I own the full lifecycle**. I load the appropriate skill and use ensemble tools to coordinate the agent team.
|
|
@@ -121,6 +129,7 @@ Trigger patterns, I recognize ALL of these, exact wording does not matter:
|
|
|
121
129
|
- User pastes or mentions an Azure DevOps URL → load `ob-userstory-az` skill → parse work item → run `/opsx-propose` → confirm with user → run `/opsx-apply` → ship
|
|
122
130
|
- `implement the plan` / `implement` / `start` / `go` → run `/opsx-apply` → ship
|
|
123
131
|
- `I've added comments to the PR` → read PR comments → fix → update PR
|
|
132
|
+
- Any GitHub/Azure DevOps PR URL in a feedback/fix request (e.g. "check comments", "fix PR feedback") → run PR Feedback Loop
|
|
124
133
|
|
|
125
134
|
**A GitHub or Azure DevOps URL anywhere in the user's message is always a trigger, regardless of surrounding words.**
|
|
126
135
|
|
|
@@ -149,6 +158,17 @@ Works on **all platforms** (Windows, macOS, Linux) via OpenCode's built-in workt
|
|
|
149
158
|
|
|
150
159
|
**Dashboard**: Monitor running agents at **http://localhost:4747/**
|
|
151
160
|
|
|
161
|
+
**Progress inspection commands (tell user explicitly after spawning):**
|
|
162
|
+
- `team_status` for live team snapshot
|
|
163
|
+
- `team_tasks_list` for task board state
|
|
164
|
+
- `team_view member:"<name>"` to inspect a teammate live session
|
|
165
|
+
- `team_results from:"<name>"` to fetch full teammate report text
|
|
166
|
+
|
|
167
|
+
If a teammate stalls due to model quota/rate-limit exhaustion:
|
|
168
|
+
1. `team_shutdown name:"<stuck-member>" force:true`
|
|
169
|
+
2. `team_spawn` same member/task with an available model
|
|
170
|
+
3. `team_message` start instruction with the exact next task ID
|
|
171
|
+
|
|
152
172
|
---
|
|
153
173
|
|
|
154
174
|
## Pipeline
|
|
@@ -220,12 +240,24 @@ devops-manager (ship mode)
|
|
|
220
240
|
### Phase 6, PR Feedback Loop
|
|
221
241
|
|
|
222
242
|
```
|
|
223
|
-
When user says "I've added comments to the PR":
|
|
224
|
-
1.
|
|
225
|
-
2.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
243
|
+
When user says "I've added comments to the PR" or asks to fix PR comments from PR URLs:
|
|
244
|
+
1. team_create "pr-feedback-<id>-<random>"
|
|
245
|
+
2. team_tasks_add with at least these lead-managed tasks:
|
|
246
|
+
- Parse and classify PR feedback (devops-manager)
|
|
247
|
+
- Implement Api feedback items (back-engineer, if needed)
|
|
248
|
+
- Implement App feedback items (front-engineer, if needed)
|
|
249
|
+
- Infra feedback items (infra-engineer, if needed)
|
|
250
|
+
- Verify with tests/build (quality-engineer)
|
|
251
|
+
- Push updates and post PR replies (devops-manager)
|
|
252
|
+
3. team_spawn devops-manager (feedback mode) with explicit task IDs, then team_message "Start now"
|
|
253
|
+
4. Wait for message → team_results
|
|
254
|
+
5. Add/update implementation tasks on board from parsed checklist (Api/App/Infra), then spawn needed specialists in parallel with explicit task IDs + team_message "Start now"
|
|
255
|
+
6. Wait for specialist results → team_shutdown + team_merge per specialist
|
|
256
|
+
7. team_spawn quality-engineer worktree:false with verification task ID + team_message "Start now"
|
|
257
|
+
8. Wait → team_results → fix blockers if any
|
|
258
|
+
9. team_spawn devops-manager (ship mode) with "push + update PR threads" task ID + team_message "Start now"
|
|
259
|
+
10. Wait → team_results → report what was updated
|
|
260
|
+
11. team_cleanup
|
|
229
261
|
```
|
|
230
262
|
|
|
231
263
|
---
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -11,8 +11,9 @@ import { chooseSkillsProvider } from './steps/choose-skills-provider.js'
|
|
|
11
11
|
import { cleanAiFiles } from './steps/clean-ai-files.js'
|
|
12
12
|
import { copyContentStep } from './steps/copy-content.js'
|
|
13
13
|
import { initOpenspec } from './steps/init-openspec.js'
|
|
14
|
-
import { patchAgentsMd } from './steps/patch-agents-md.js'
|
|
15
|
-
import { installBrowser } from './steps/install-browser.js'
|
|
14
|
+
import { patchAgentsMd } from './steps/patch-agents-md.js'
|
|
15
|
+
import { installBrowser } from './steps/install-browser.js'
|
|
16
|
+
import { writeOnboardConfig } from './steps/write-onboard-config.js'
|
|
16
17
|
|
|
17
18
|
if (process.stdout.isTTY) console.clear()
|
|
18
19
|
console.log()
|
|
@@ -83,16 +84,24 @@ try {
|
|
|
83
84
|
await initOpenspec()
|
|
84
85
|
|
|
85
86
|
// 8. Install skills
|
|
86
|
-
await chooseSkillsProvider()
|
|
87
|
+
const skillsSelection = await chooseSkillsProvider()
|
|
87
88
|
|
|
88
89
|
// 9. Choose models
|
|
89
|
-
await chooseModels()
|
|
90
|
+
const selectedModels = await chooseModels()
|
|
90
91
|
|
|
91
92
|
// 10. Check RTK
|
|
92
93
|
await checkRtk()
|
|
93
94
|
|
|
94
95
|
// 11. Install opencode-browser
|
|
95
96
|
await installBrowser()
|
|
97
|
+
|
|
98
|
+
// 12. Write onboarding metadata
|
|
99
|
+
await writeOnboardConfig({
|
|
100
|
+
...ctx,
|
|
101
|
+
platform,
|
|
102
|
+
...skillsSelection,
|
|
103
|
+
...selectedModels,
|
|
104
|
+
})
|
|
96
105
|
|
|
97
106
|
// Done
|
|
98
107
|
const toGenerate = [
|
|
@@ -154,4 +154,10 @@ export async function chooseModels() {
|
|
|
154
154
|
console.log()
|
|
155
155
|
warn('Make sure you have API access to the selected models.')
|
|
156
156
|
warn('Change them anytime in .agents/agents/<name>.md and .opencode/opencode.json')
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
planModel,
|
|
160
|
+
buildModel,
|
|
161
|
+
fastModel,
|
|
162
|
+
}
|
|
157
163
|
}
|
|
@@ -56,7 +56,7 @@ export async function chooseSkillsProvider() {
|
|
|
56
56
|
})
|
|
57
57
|
|
|
58
58
|
if (selected === 'none') {
|
|
59
|
-
return
|
|
59
|
+
return { additionalSkillsProvider: 'none' }
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
if (selected === 'npx-skills') {
|
|
@@ -71,5 +71,9 @@ export async function chooseSkillsProvider() {
|
|
|
71
71
|
} catch (err) {
|
|
72
72
|
warn(`npx skills failed: ${err.message}`)
|
|
73
73
|
}
|
|
74
|
+
|
|
75
|
+
return { additionalSkillsProvider: 'npx-skills' }
|
|
74
76
|
}
|
|
77
|
+
|
|
78
|
+
return { additionalSkillsProvider: selected }
|
|
75
79
|
}
|
|
@@ -7,6 +7,66 @@ import { error, header, success } from '../utils/exec.js'
|
|
|
7
7
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
8
8
|
const CONTENT_DIR = path.resolve(__dirname, '../../content')
|
|
9
9
|
|
|
10
|
+
function formatRootsForText(roots = [], cwd = process.cwd()) {
|
|
11
|
+
if (!roots.length) return ['current folder']
|
|
12
|
+
return roots.map(r => {
|
|
13
|
+
const rel = path.relative(cwd, r)
|
|
14
|
+
if (!rel || rel === '') return 'current folder'
|
|
15
|
+
if (!rel.startsWith('..')) return `./${rel.replace(/\\/g, '/')}`
|
|
16
|
+
return rel.replace(/\\/g, '/')
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function patchSourceScopeFiles(dest, ctx) {
|
|
21
|
+
const roots = formatRootsForText(ctx.sourceRoots || [dest], dest)
|
|
22
|
+
const rootsInline = roots.join(', ')
|
|
23
|
+
const rootsBullets = roots.map(r => ` - ${r}`).join('\n')
|
|
24
|
+
|
|
25
|
+
const agentsPath = path.join(dest, 'AGENTS.md')
|
|
26
|
+
if (await fse.pathExists(agentsPath)) {
|
|
27
|
+
let content = await fse.readFile(agentsPath, 'utf-8')
|
|
28
|
+
content = content.replace(
|
|
29
|
+
'Before scanning, load source roots from `.agents/source-roots.json` when present. Only scan those roots plus this repo\'s docs/config files.',
|
|
30
|
+
`Source roots selected during onboarding (scan these roots plus this repo docs/config): ${rootsInline}.`
|
|
31
|
+
)
|
|
32
|
+
content = content.replace(
|
|
33
|
+
'4. **Analyze the actual codebase**: use `.agents/source-roots.json` as source roots when present, then read CSS files, Tailwind config, component files, token definitions. Do not rely on prior knowledge, read the files.',
|
|
34
|
+
`4. **Analyze the actual codebase** using these source roots:\n${rootsBullets}\n\n Then read CSS files, Tailwind config, component files, token definitions. Do not rely on prior knowledge, read the files.`
|
|
35
|
+
)
|
|
36
|
+
content = content.replace(
|
|
37
|
+
'4. **Analyze the actual codebase**: use `.agents/source-roots.json` as source roots when present, then read folder structure, config files, route definitions, data models, integration points. Do not rely on prior knowledge, read the files.',
|
|
38
|
+
`4. **Analyze the actual codebase** using these source roots:\n${rootsBullets}\n\n Then read folder structure, config files, route definitions, data models, integration points. Do not rely on prior knowledge, read the files.`
|
|
39
|
+
)
|
|
40
|
+
content = content.replace(
|
|
41
|
+
'- Read source scope from `.agents/source-roots.json`.',
|
|
42
|
+
`- Source roots selected during onboarding: ${rootsInline}.`
|
|
43
|
+
)
|
|
44
|
+
await fse.writeFile(agentsPath, content, 'utf-8')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const designPath = path.join(dest, 'DESIGN.md')
|
|
48
|
+
if (await fse.pathExists(designPath)) {
|
|
49
|
+
let content = await fse.readFile(designPath, 'utf-8')
|
|
50
|
+
const injection = `\nSource roots selected during onboarding:\n${rootsBullets}\n\nWhen analyzing, read UI/design evidence only from these roots.\n\nIn the generated DESIGN.md, add this section near the top:\n\n## Source Roots Used\n${rootsBullets}\n`
|
|
51
|
+
content = content.replace(
|
|
52
|
+
'Analyze the design system of this codebase with the goal of creating a DESIGN.md file in the project root and giving the user a file for easy copy & pasting.',
|
|
53
|
+
'Analyze the design system of this codebase with the goal of creating a DESIGN.md file in the project root and giving the user a file for easy copy & pasting.' + injection
|
|
54
|
+
)
|
|
55
|
+
await fse.writeFile(designPath, content, 'utf-8')
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const architecturePath = path.join(dest, 'ARCHITECTURE.md')
|
|
59
|
+
if (await fse.pathExists(architecturePath)) {
|
|
60
|
+
let content = await fse.readFile(architecturePath, 'utf-8')
|
|
61
|
+
const injection = `\nSource roots selected during onboarding:\n${rootsBullets}\n\nWhen analyzing, read architecture evidence only from these roots.\n\nIn the generated ARCHITECTURE.md, add this section near the top:\n\n## Source Roots Used\n${rootsBullets}\n`
|
|
62
|
+
content = content.replace(
|
|
63
|
+
'Analyze the architecture of this codebase with the goal of creating an ARCHITECTURE.md file in the project root and giving the user a file for easy copy & pasting.',
|
|
64
|
+
'Analyze the architecture of this codebase with the goal of creating an ARCHITECTURE.md file in the project root and giving the user a file for easy copy & pasting.' + injection
|
|
65
|
+
)
|
|
66
|
+
await fse.writeFile(architecturePath, content, 'utf-8')
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
10
70
|
export async function copyContentStep(platform, ctx = {}) {
|
|
11
71
|
header('Step 6, Copying opencode-onboard files')
|
|
12
72
|
|
|
@@ -20,6 +80,7 @@ export async function copyContentStep(platform, ctx = {}) {
|
|
|
20
80
|
mode: ctx.sourceMode || 'current',
|
|
21
81
|
roots: ctx.sourceRoots || [dest],
|
|
22
82
|
}, { spaces: 2 })
|
|
83
|
+
await patchSourceScopeFiles(dest, ctx)
|
|
23
84
|
success('Files copied to project root')
|
|
24
85
|
} catch (err) {
|
|
25
86
|
error(`Failed to copy content: ${err.message}`)
|
|
@@ -73,11 +73,17 @@ const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
|
|
|
73
73
|
**Step 6e.** After sending start messages, tell the user what is running, then STOP and wait.
|
|
74
74
|
Do NOT call team_results, team_status, or team_broadcast in a loop.
|
|
75
75
|
Teammates will message you when done or blocked. Wait for those messages.
|
|
76
|
+
Tell the user exactly how to inspect progress:
|
|
77
|
+
- \`team_status\` for team snapshot
|
|
78
|
+
- \`team_tasks_list\` for board state
|
|
79
|
+
- \`team_view member:"<name>"\` for a teammate live session
|
|
80
|
+
- \`team_results from:"<name>"\` for full teammate report text
|
|
76
81
|
|
|
77
82
|
**Step 6f.** When a teammate messages back, you receive a ping only, the full content is NOT in the notification.
|
|
78
83
|
Call team_results to read the full message and mark it read. Then for each teammate: team_shutdown -> team_merge.
|
|
79
84
|
If team_merge blocks ("overlapping local changes"), commit or stash your local changes first, then retry.
|
|
80
85
|
Fix any other blockers reported.
|
|
86
|
+
If a teammate reports rate-limit/quota/token exhaustion, immediately shutdown that teammate and respawn with an available model.
|
|
81
87
|
|
|
82
88
|
7. **Quality check**
|
|
83
89
|
|
|
@@ -120,6 +126,7 @@ const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
|
|
|
120
126
|
- Pause on errors, blockers, or unclear requirements. Do not guess
|
|
121
127
|
- Use contextFiles from CLI output, do not assume specific file paths
|
|
122
128
|
- Use \`rtk\` wrapper for ALL CLI commands. Never run openspec, git, gh, or az directly
|
|
129
|
+
- If model quota/rate-limit is exhausted, tell lead immediately via team_message and stop claiming new tasks until respawned
|
|
123
130
|
`
|
|
124
131
|
|
|
125
132
|
const STEP_6_START = /^6\.\s+\*\*Implement\b/im
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { execa } from 'execa'
|
|
2
|
+
import fse from 'fs-extra'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { createRequire } from 'node:module'
|
|
5
|
+
import { header, success, warn } from '../utils/exec.js'
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url)
|
|
8
|
+
const { version: onboardVersion } = require('../../package.json')
|
|
9
|
+
|
|
10
|
+
async function detectOpencodeVersion() {
|
|
11
|
+
try {
|
|
12
|
+
const result = await execa('opencode', ['--version'], { reject: false })
|
|
13
|
+
if (result.exitCode !== 0) return null
|
|
14
|
+
const output = (result.stdout || result.stderr || '').trim()
|
|
15
|
+
return output || null
|
|
16
|
+
} catch {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function writeOnboardConfig(data) {
|
|
22
|
+
header('Step 12, Writing onboarding metadata')
|
|
23
|
+
|
|
24
|
+
const opencodeVersion = await detectOpencodeVersion()
|
|
25
|
+
const target = path.join(process.cwd(), '.opencode', 'opencode-onboard.json')
|
|
26
|
+
|
|
27
|
+
const payload = {
|
|
28
|
+
schema: 1,
|
|
29
|
+
generatedAt: new Date().toISOString(),
|
|
30
|
+
onboardVersion,
|
|
31
|
+
opencodeVersion,
|
|
32
|
+
wizard: {
|
|
33
|
+
platform: data.platform,
|
|
34
|
+
sourceMode: data.sourceMode,
|
|
35
|
+
sourceRoots: data.sourceRoots,
|
|
36
|
+
preserved: {
|
|
37
|
+
design: !!data.hasDesign,
|
|
38
|
+
architecture: !!data.hasArchitecture,
|
|
39
|
+
openspec: !!data.hasOpenspec,
|
|
40
|
+
},
|
|
41
|
+
additionalSkillsProvider: data.additionalSkillsProvider,
|
|
42
|
+
models: {
|
|
43
|
+
plan: data.planModel,
|
|
44
|
+
build: data.buildModel,
|
|
45
|
+
fast: data.fastModel,
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
note: 'Informational file only. Editing this file does not change runtime behavior.',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
await fse.ensureDir(path.dirname(target))
|
|
53
|
+
await fse.writeJson(target, payload, { spaces: 2 })
|
|
54
|
+
success('Wrote .opencode/opencode-onboard.json')
|
|
55
|
+
if (!opencodeVersion) warn('Could not detect opencode version, saved as null')
|
|
56
|
+
} catch (err) {
|
|
57
|
+
warn(`Could not write onboarding metadata: ${err.message}`)
|
|
58
|
+
}
|
|
59
|
+
}
|