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 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. team_spawn devops-manager (feedback mode) → read & classify comments
225
- 2. Wait team_results spawn front/back/infra for code-change items (parallel)
226
- 3. Wait team_results spawn quality-engineer → verify fixes
227
- 4. Wait team_results spawn devops-manager (ship mode) → push & update PR
228
- 5. team_cleanup
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-onboard",
3
- "version": "0.2.3",
3
+ "version": "0.2.6",
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
@@ -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
+ }