opencode-onboard 0.2.13 → 0.3.1

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.
@@ -33,15 +33,17 @@ If `rtk` is not available, report it as a blocker. Do not run commands without i
33
33
 
34
34
  Skills are located in `.agents/skills/`. Detect and use relevant skills automatically, the user will never tell you which skill to use.
35
35
 
36
- 1. Read the task and identify domain and platform
37
- 2. Scan `.agents/skills/` for available skills
38
- 3. Read each `SKILL.md` description to assess relevance
39
- 4. Load and follow any skill that applies, even partial match warrants loading
36
+ 1. If the spawn prompt lists specific skills to load, read those `SKILL.md` files FIRST before any implementation
37
+ 2. Additionally, read the task and identify domain and platform
38
+ 3. Scan `.agents/skills/` for available skills
39
+ 4. Read each `SKILL.md` description to assess relevance
40
+ 5. Load and follow any skill that applies, even partial match warrants loading
40
41
 
41
42
  Rules:
42
43
  - Never implement directly if a skill applies
43
44
  - Follow skill instructions exactly, do not partially apply them
44
45
  - If two skills apply, follow both, resolve conflicts by asking the lead
46
+ - Skills listed in the spawn prompt are MANDATORY, not optional
45
47
 
46
48
  ## Responsibilities
47
49
 
@@ -66,8 +68,9 @@ Rules:
66
68
  ## Workflow
67
69
 
68
70
  When spawned by the lead:
71
+ 0. Read ALL skills listed in the spawn prompt FIRST. Do not proceed until every listed SKILL.md has been read. Reply to lead with `team_message` confirming which skills were loaded.
69
72
  1. For each assigned task: call `team_claim task_id:<id>` before starting
70
- 2. Implement the task
73
+ 2. Implement the task following loaded skill rules
71
74
  3. Call `team_tasks_complete task_id:<id>` after finishing
72
75
  4. When all tasks are done or blocked, send results to lead via `team_message`
73
76
 
@@ -33,6 +33,9 @@ If `rtk` is not available, report it as a blocker. Do not run commands without i
33
33
 
34
34
  Skills are located in `.agents/skills/`. Detect and use relevant skills automatically, the user will never tell you which skill to use.
35
35
 
36
+ 1. If the spawn prompt lists specific skills to load, read those `SKILL.md` files FIRST before any implementation
37
+ 2. Additionally, identify the platform from URLs or context
38
+
36
39
  Examples of intent → skill mapping:
37
40
  - URL contains `dev.azure.com` or `visualstudio.com` → look for `ob-userstory-az` or `ob-pullrequest-az`
38
41
  - URL contains `github.com` → look for `ob-userstory-gh` or `ob-pullrequest-gh`
@@ -43,6 +46,7 @@ Rules:
43
46
  - Never interact with a platform without loading the matching skill first
44
47
  - Follow skill instructions exactly, do not partially apply them
45
48
  - If no skill exists for the platform, report it as a blocker rather than improvising
49
+ - Skills listed in the spawn prompt are MANDATORY, not optional
46
50
 
47
51
  ## Two Modes
48
52
 
@@ -33,15 +33,17 @@ If `rtk` is not available, report it as a blocker. Do not run commands without i
33
33
 
34
34
  Skills are located in `.agents/skills/`. Detect and use relevant skills automatically, the user will never tell you which skill to use.
35
35
 
36
- 1. Read the task and identify domain and platform
37
- 2. Scan `.agents/skills/` for available skills
38
- 3. Read each `SKILL.md` description to assess relevance
39
- 4. Load and follow any skill that applies, even partial match warrants loading
36
+ 1. If the spawn prompt lists specific skills to load, read those `SKILL.md` files FIRST before any implementation
37
+ 2. Additionally, read the task and identify domain and platform
38
+ 3. Scan `.agents/skills/` for available skills
39
+ 4. Read each `SKILL.md` description to assess relevance
40
+ 5. Load and follow any skill that applies, even partial match warrants loading
40
41
 
41
42
  Rules:
42
43
  - Never implement directly if a skill applies
43
44
  - Follow skill instructions exactly, do not partially apply them
44
45
  - If two skills apply, follow both, resolve conflicts by asking the lead
46
+ - Skills listed in the spawn prompt are MANDATORY, not optional
45
47
 
46
48
  ## Responsibilities
47
49
 
@@ -65,8 +67,9 @@ Rules:
65
67
  ## Workflow
66
68
 
67
69
  When spawned by the lead:
70
+ 0. Read ALL skills listed in the spawn prompt FIRST. Do not proceed until every listed SKILL.md has been read. Reply to lead with `team_message` confirming which skills were loaded.
68
71
  1. For each assigned task: call `team_claim task_id:<id>` before starting
69
- 2. Implement the task
72
+ 2. Implement the task following loaded skill rules
70
73
  3. Call `team_tasks_complete task_id:<id>` after finishing
71
74
  4. When all tasks are done or blocked, send results to lead via `team_message`
72
75
 
@@ -33,15 +33,17 @@ If `rtk` is not available, report it as a blocker. Do not run commands without i
33
33
 
34
34
  Skills are located in `.agents/skills/`. Detect and use relevant skills automatically, the user will never tell you which skill to use.
35
35
 
36
- 1. Read the task and identify domain and platform
37
- 2. Scan `.agents/skills/` for available skills
38
- 3. Read each `SKILL.md` description to assess relevance
39
- 4. Load and follow any skill that applies, even partial match warrants loading
36
+ 1. If the spawn prompt lists specific skills to load, read those `SKILL.md` files FIRST before any implementation
37
+ 2. Additionally, read the task and identify domain and platform
38
+ 3. Scan `.agents/skills/` for available skills
39
+ 4. Read each `SKILL.md` description to assess relevance
40
+ 5. Load and follow any skill that applies, even partial match warrants loading
40
41
 
41
42
  Rules:
42
43
  - Never implement directly if a skill applies
43
44
  - Follow skill instructions exactly, do not partially apply them
44
45
  - If two skills apply, follow both, resolve conflicts by asking the lead
46
+ - Skills listed in the spawn prompt are MANDATORY, not optional
45
47
 
46
48
  ## Responsibilities
47
49
 
@@ -62,6 +64,15 @@ Rules:
62
64
  - Do not force push
63
65
  - Report blockers immediately rather than working around them
64
66
 
67
+ ## Workflow
68
+
69
+ When spawned by the lead:
70
+ 0. Read ALL skills listed in the spawn prompt FIRST. Do not proceed until every listed SKILL.md has been read. Reply to lead with `team_message` confirming which skills were loaded.
71
+ 1. For each assigned task: call `team_claim task_id:<id>` before starting
72
+ 2. Implement the task following loaded skill rules
73
+ 3. Call `team_tasks_complete task_id:<id>` after finishing
74
+ 4. When all tasks are done or blocked, send results to lead via `team_message`
75
+
65
76
  ## Output Format
66
77
 
67
78
  ```
@@ -33,15 +33,17 @@ If `rtk` is not available, report it as a blocker. Do not run commands without i
33
33
 
34
34
  Skills are located in `.agents/skills/`. Detect and use relevant skills automatically, the user will never tell you which skill to use.
35
35
 
36
- 1. Read the task and identify domain and platform
37
- 2. Scan `.agents/skills/` for available skills
38
- 3. Read each `SKILL.md` description to assess relevance
39
- 4. Load and follow any skill that applies, even partial match warrants loading
36
+ 1. If the spawn prompt lists specific skills to load, read those `SKILL.md` files FIRST before any implementation
37
+ 2. Additionally, read the task and identify domain and platform
38
+ 3. Scan `.agents/skills/` for available skills
39
+ 4. Read each `SKILL.md` description to assess relevance
40
+ 5. Load and follow any skill that applies, even partial match warrants loading
40
41
 
41
42
  Rules:
42
43
  - Never implement directly if a skill applies
43
44
  - Follow skill instructions exactly, do not partially apply them
44
45
  - If two skills apply, follow both, resolve conflicts by asking the lead
46
+ - Skills listed in the spawn prompt are MANDATORY, not optional
45
47
 
46
48
  ## Responsibilities
47
49
 
@@ -64,8 +66,9 @@ Rules:
64
66
 
65
67
  When spawned by the lead:
66
68
  1. Read the task list and context files provided in the spawn prompt
67
- 2. Run tests, build, lint, and verify acceptance criteria
68
- 3. When done, send results to lead via `team_message`
69
+ 2. If no board task is assigned, do not block on team_claim. Run verification directly from the spawn prompt scope.
70
+ 3. Run tests, build, lint, and verify acceptance criteria
71
+ 4. When done, send results to lead via `team_message`
69
72
 
70
73
  ## Output Format
71
74
 
@@ -31,15 +31,17 @@ If `rtk` is not available, report it as a blocker. Do not run commands without i
31
31
 
32
32
  Skills are located in `.agents/skills/`. Detect and use relevant skills automatically, the user will never tell you which skill to use.
33
33
 
34
- 1. Read the task and identify domain and platform
35
- 2. Scan `.agents/skills/` for available skills
36
- 3. Read each `SKILL.md` description to assess relevance
37
- 4. Load and follow any skill that applies, even partial match warrants loading
34
+ 1. If the spawn prompt lists specific skills to load, read those `SKILL.md` files FIRST before any implementation
35
+ 2. Additionally, read the task and identify domain and platform
36
+ 3. Scan `.agents/skills/` for available skills
37
+ 4. Read each `SKILL.md` description to assess relevance
38
+ 5. Load and follow any skill that applies, even partial match warrants loading
38
39
 
39
40
  Rules:
40
41
  - Never implement directly if a skill applies
41
42
  - Follow skill instructions exactly, do not partially apply them
42
43
  - If two skills apply, follow both, resolve conflicts by asking the lead
44
+ - Skills listed in the spawn prompt are MANDATORY, not optional
43
45
 
44
46
  ## Responsibilities
45
47
 
@@ -65,14 +65,19 @@ rtk gh pr create \
65
65
 
66
66
  ### Step 5: Post screenshot comment
67
67
 
68
- Build raw URL for each image:
68
+ Resolve commit SHA (the commit that includes screenshots):
69
+ ```bash
70
+ rtk git rev-parse HEAD
71
+ ```
72
+
73
+ Build blob URL for each image (preferred, stable in PR discussion):
69
74
  ```
70
- https://raw.githubusercontent.com/{owner}/{repo}/feature/{slug}/openspec/changes/{change}/images/{file}.png
75
+ https://github.com/{owner}/{repo}/blob/{sha}/openspec/changes/{change}/images/{file}.png
71
76
  ```
72
77
 
73
78
  Post comment:
74
79
  ```bash
75
- rtk gh pr comment {pr-number} --body $'## Screenshots\n\n![{feature}]({raw-url})'
80
+ rtk gh pr comment {pr-number} --repo {owner}/{repo} --body $'## Screenshots\n\n![{feature}]({blob-url})'
76
81
  ```
77
82
 
78
83
  ---
@@ -80,16 +80,16 @@ rtk gh issue edit <number> --repo {owner}/{repo} --add-label "in-progress"
80
80
 
81
81
  ## Screenshot / Image Strategy
82
82
 
83
- **Never embed images as attachments.** Save to openspec change folder and reference via raw GitHub URL.
83
+ **Never embed images as attachments.** Save to openspec change folder and reference via GitHub blob URL pinned to commit SHA.
84
84
 
85
85
  ### Save location
86
86
  ```
87
87
  openspec/changes/{change-name}/images/{screenshot}.png
88
88
  ```
89
89
 
90
- ### Raw URL format (renders inline in PR comments)
90
+ ### Blob URL format (preferred)
91
91
  ```
92
- https://raw.githubusercontent.com/{owner}/{repo}/{branch}/openspec/changes/{change}/images/{file}.png
92
+ https://github.com/{owner}/{repo}/blob/{sha}/openspec/changes/{change}/images/{file}.png
93
93
  ```
94
94
 
95
95
  ---
@@ -103,8 +103,8 @@ https://github.com/{owner}/{repo}/issues/{number}
103
103
  # PR
104
104
  https://github.com/{owner}/{repo}/pull/{number}
105
105
 
106
- # Raw file
107
- https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{path}
106
+ # Blob file
107
+ https://github.com/{owner}/{repo}/blob/{sha}/{path}
108
108
  ```
109
109
 
110
110
  ---
@@ -86,7 +86,14 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
86
86
  DO NOT call team_claim yourself, only agents claim tasks.
87
87
  DO NOT proceed to 6d until team_tasks_add succeeds.
88
88
 
89
- **Step 6d.** Spawn all needed specialists, then kick them off in parallel.
89
+ **Step 6d.** Discover relevant skills, then spawn specialists.
90
+
91
+ Before spawning, scan `.agents/skills/` and read each `SKILL.md` description line.
92
+ Match skills to agents by domain:
93
+ - front-engineer: UI, components, framework skills (e.g. next-best-practices, browser-automation)
94
+ - back-engineer: API, data, service skills
95
+ - infra-engineer: cloud, pipeline, deployment skills
96
+ - quality-engineer: testing, coverage skills
90
97
 
91
98
  Each team_spawn MUST include the agent field (required, causes NOT NULL error if omitted).
92
99
 
@@ -97,8 +104,9 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
97
104
  4. The 6 OpenCode tools they have available (these are OpenCode tools, NOT shell commands, call them directly as tools, never via bash):
98
105
  team_claim, team_tasks_complete, team_tasks_list, team_tasks_add, team_message, team_broadcast
99
106
  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
107
+ 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."
100
108
 
101
- Keep spawn prompts under 500 tokens. Do not describe team internals or how ensemble works.
109
+ Keep spawn prompts under 600 tokens. Do not describe team internals or how ensemble works.
102
110
  Only spawn agents whose tasks are actually needed by this change. Skip agents with no tasks.
103
111
 
104
112
  First spawn all agents (wait for each team_spawn to confirm before the next):
@@ -113,9 +121,9 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
113
121
 
114
122
  Then immediately send each spawned agent a start message to kick them off:
115
123
  ```
116
- team_message to:"back" text:"Start now. Claim your first task with team_claim and begin implementing."
117
- team_message to:"front" text:"Start now. Claim your first task with team_claim and begin implementing."
118
- team_message to:"infra" text:"Start now. Claim your first task with team_claim and begin implementing."
124
+ team_message to:"back" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
125
+ team_message to:"front" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
126
+ team_message to:"infra" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
119
127
  ```
120
128
 
121
129
  **Step 6e.** After sending start messages, tell the user what is running, then STOP and wait.
@@ -131,7 +139,7 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
131
139
 
132
140
  Spawn quality engineer with worktree:false (read-only, no file edits):
133
141
  ```
134
- 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>"
142
+ 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>"
135
143
  ```
136
144
  Wait for message → team_results → fix blockers → team_shutdown (no team_merge needed, worktree:false)
137
145
 
@@ -2,7 +2,8 @@ import fs from "node:fs"
2
2
  import path from "node:path"
3
3
 
4
4
  const LOG_FILE = ".agents/session-log.json"
5
- const SPAWN_MATCH_WINDOW_MS = 15000
5
+ const SPAWN_MATCH_WINDOW_MS = 30000
6
+ const UNMATCHED_SESSION_WARN_MS = 30000
6
7
  const DEBUG = process.env.SESSION_LOG_DEBUG === "true"
7
8
 
8
9
  // Per-session state
@@ -12,8 +13,12 @@ const sessionState = new Map()
12
13
  const leadTeamBySession = new Map()
13
14
 
14
15
  // Pending spawn records waiting for a session.created match
16
+ // Each entry: { leadSessionId, at, member, agentType, teamName, spawnedSessionId, intendedSkills }
15
17
  const pendingSpawns = []
16
18
 
19
+ // spawnedSessionId -> pending spawn (direct correlation fast path)
20
+ const pendingSpawnBySessionId = new Map()
21
+
17
22
  // Team -> completed session snapshots
18
23
  const completedByTeam = new Map()
19
24
 
@@ -47,6 +52,11 @@ function resolveAgentName(session) {
47
52
  return "lead"
48
53
  }
49
54
 
55
+ function resolveModel(session) {
56
+ // Try common field names used by OpenCode session objects
57
+ return session?.model || session?.modelId || session?.model_id || null
58
+ }
59
+
50
60
  function addSkillToState(state, skillName) {
51
61
  if (!skillName || !state) return false
52
62
  if (!state.skills) state.skills = new Set()
@@ -55,6 +65,37 @@ function addSkillToState(state, skillName) {
55
65
  return true
56
66
  }
57
67
 
68
+ // Extract skill names hinted in a spawn prompt by scanning for known skill-like tokens.
69
+ // Matches strings that look like skill directory names (kebab-case words after "skill" keyword
70
+ // or adjacent to known skill name patterns).
71
+ function extractIntendedSkills(prompt) {
72
+ if (!prompt || typeof prompt !== "string") return []
73
+ const skills = new Set()
74
+
75
+ // Match explicit skill name mentions: e.g. "ob-pullrequest-gh", "browser-automation"
76
+ const kebabPattern = /\b([a-z][a-z0-9]*(?:-[a-z0-9]+){1,})\b/g
77
+ let m
78
+ while ((m = kebabPattern.exec(prompt)) !== null) {
79
+ const candidate = m[1]
80
+ // Filter to plausible skill names: 2+ segments, known prefixes or suffixes
81
+ if (
82
+ candidate.startsWith("ob-") ||
83
+ candidate.startsWith("openspec-") ||
84
+ candidate.startsWith("browser-") ||
85
+ candidate.endsWith("-gh") ||
86
+ candidate.endsWith("-az") ||
87
+ candidate.endsWith("-automation") ||
88
+ candidate.endsWith("-change") ||
89
+ candidate.endsWith("-engineer") ||
90
+ candidate.endsWith("-manager") ||
91
+ candidate.endsWith("-auditor")
92
+ ) {
93
+ skills.add(candidate)
94
+ }
95
+ }
96
+ return Array.from(skills).sort()
97
+ }
98
+
58
99
  function toNum(v) {
59
100
  if (typeof v === "number" && Number.isFinite(v)) return v
60
101
  if (typeof v === "string" && v.trim() !== "" && !Number.isNaN(Number(v))) return Number(v)
@@ -133,7 +174,9 @@ function buildCompletedSnapshot(state, sessionId) {
133
174
  member: state.member || null,
134
175
  agentType: state.agentType || null,
135
176
  team: state.teamName || null,
177
+ model: state.model || null,
136
178
  skills: Array.from(state.skills || []).sort(),
179
+ intendedSkills: state.intendedSkills || [],
137
180
  usage: usagePayload(state),
138
181
  filesEdited: state.editCount || 0,
139
182
  }
@@ -144,14 +187,21 @@ function buildTeamSkillsSummary(teamName) {
144
187
  const byAgent = {}
145
188
  for (const row of rows) {
146
189
  const key = row.member || row.agent || "unknown"
147
- if (!byAgent[key]) byAgent[key] = { agentType: row.agentType || null, skills: new Set() }
190
+ if (!byAgent[key]) byAgent[key] = { agentType: row.agentType || null, model: row.model || null, skills: new Set(), intendedSkills: new Set() }
148
191
  for (const s of row.skills || []) byAgent[key].skills.add(s)
192
+ for (const s of row.intendedSkills || []) byAgent[key].intendedSkills.add(s)
149
193
  }
150
194
  const out = {}
151
195
  for (const [k, v] of Object.entries(byAgent)) {
196
+ const skills = Array.from(v.skills).sort()
197
+ const intendedSkills = Array.from(v.intendedSkills).sort()
198
+ const missingSkills = intendedSkills.filter(s => !v.skills.has(s))
152
199
  out[k] = {
153
200
  agentType: v.agentType,
154
- skills: Array.from(v.skills).sort(),
201
+ model: v.model,
202
+ skills,
203
+ intendedSkills,
204
+ missingSkills: missingSkills.length > 0 ? missingSkills : undefined,
155
205
  }
156
206
  }
157
207
  return out
@@ -163,21 +213,52 @@ function trackCompletedByTeam(snapshot) {
163
213
  completedByTeam.get(snapshot.team).push(snapshot)
164
214
  }
165
215
 
166
- function enqueuePendingSpawn(leadSessionId, args) {
167
- pendingSpawns.push({
216
+ function enqueuePendingSpawn(leadSessionId, args, spawnOutput) {
217
+ // Try to extract spawnedSessionId from team_spawn output for direct correlation
218
+ const spawnedSessionId =
219
+ spawnOutput?.sessionId ||
220
+ spawnOutput?.session_id ||
221
+ spawnOutput?.id ||
222
+ spawnOutput?.data?.sessionId ||
223
+ spawnOutput?.data?.session_id ||
224
+ spawnOutput?.data?.id ||
225
+ null
226
+
227
+ const record = {
168
228
  leadSessionId,
169
229
  at: nowMs(),
170
230
  member: args?.name || null,
171
231
  agentType: args?.agent || null,
172
232
  teamName: leadTeamBySession.get(leadSessionId) || null,
173
- })
233
+ spawnedSessionId,
234
+ intendedSkills: extractIntendedSkills(args?.prompt),
235
+ }
236
+
237
+ pendingSpawns.push(record)
238
+
239
+ if (spawnedSessionId) {
240
+ pendingSpawnBySessionId.set(spawnedSessionId, record)
241
+ }
174
242
  }
175
243
 
176
- function matchPendingSpawn() {
244
+ function matchPendingSpawn(sessionId) {
177
245
  const now = nowMs()
178
- // Drop expired pending spawns first
246
+
247
+ // Fast path: direct session ID correlation from team_spawn output
248
+ if (sessionId && pendingSpawnBySessionId.has(sessionId)) {
249
+ const record = pendingSpawnBySessionId.get(sessionId)
250
+ pendingSpawnBySessionId.delete(sessionId)
251
+ const idx = pendingSpawns.indexOf(record)
252
+ if (idx !== -1) pendingSpawns.splice(idx, 1)
253
+ return record
254
+ }
255
+
256
+ // Fallback: time-window heuristic (drop expired first)
179
257
  for (let i = pendingSpawns.length - 1; i >= 0; i--) {
180
- if (now - pendingSpawns[i].at > SPAWN_MATCH_WINDOW_MS) pendingSpawns.splice(i, 1)
258
+ if (now - pendingSpawns[i].at > SPAWN_MATCH_WINDOW_MS) {
259
+ pendingSpawnBySessionId.delete(pendingSpawns[i].spawnedSessionId)
260
+ pendingSpawns.splice(i, 1)
261
+ }
181
262
  }
182
263
  if (pendingSpawns.length === 0) return null
183
264
  return pendingSpawns.shift()
@@ -210,13 +291,16 @@ export const SessionLogPlugin = async ({ client, directory }) => {
210
291
  const res = await client.session.get({ path: { id: sessionId } })
211
292
  const session = res?.data
212
293
  const fallbackAgent = resolveAgentName(session)
213
- const spawnMatch = matchPendingSpawn()
294
+ const model = resolveModel(session)
295
+ const spawnMatch = matchPendingSpawn(sessionId)
214
296
 
215
297
  const state = {
216
298
  agentName: spawnMatch?.member || fallbackAgent,
217
299
  member: spawnMatch?.member || null,
218
300
  agentType: spawnMatch?.agentType || null,
219
301
  teamName: spawnMatch?.teamName || null,
302
+ model,
303
+ intendedSkills: spawnMatch?.intendedSkills || [],
220
304
  editCount: 0,
221
305
  skills: new Set(),
222
306
  startedAtMs: nowMs(),
@@ -226,18 +310,56 @@ export const SessionLogPlugin = async ({ client, directory }) => {
226
310
  reportedInputTokens: 0,
227
311
  reportedOutputTokens: 0,
228
312
  reportedTotalTokens: 0,
313
+ // Track whether this session was matched to a spawn record
314
+ spawnMatched: !!spawnMatch,
229
315
  }
230
316
 
231
317
  sessionState.set(sessionId, state)
232
- appendEntry(directory, {
318
+
319
+ const startedEntry = {
233
320
  ts: ts(),
234
321
  agent: state.agentName,
235
322
  member: state.member,
236
323
  agentType: state.agentType,
237
324
  team: state.teamName,
325
+ model: state.model,
238
326
  action: "started",
239
327
  sessionId,
240
- })
328
+ }
329
+ appendEntry(directory, startedEntry)
330
+
331
+ // Emit explicit teammate-registered entry when a spawn is matched
332
+ if (spawnMatch) {
333
+ appendEntry(directory, {
334
+ ts: ts(),
335
+ agent: state.agentName,
336
+ member: state.member,
337
+ agentType: state.agentType,
338
+ team: state.teamName,
339
+ model: state.model,
340
+ intendedSkills: state.intendedSkills,
341
+ action: "teammate-registered",
342
+ sessionId,
343
+ correlationMethod: spawnMatch.spawnedSessionId === sessionId ? "direct" : "time-window",
344
+ })
345
+ } else {
346
+ // No spawn match — schedule an unmatched-session warning
347
+ const capturedSessionId = sessionId
348
+ setTimeout(() => {
349
+ const s = sessionState.get(capturedSessionId)
350
+ if (!s || s.spawnMatched) return
351
+ appendEntry(directory, {
352
+ ts: ts(),
353
+ agent: s.agentName,
354
+ member: s.member,
355
+ team: s.teamName,
356
+ model: s.model,
357
+ action: "unmatched-session",
358
+ sessionId: capturedSessionId,
359
+ warning: "Session started with no matching team_spawn record. Agent identity may be inaccurate.",
360
+ })
361
+ }, UNMATCHED_SESSION_WARN_MS)
362
+ }
241
363
  }
242
364
 
243
365
  if (event?.type === "file.edited") {
@@ -261,9 +383,11 @@ export const SessionLogPlugin = async ({ client, directory }) => {
261
383
  member: state.member,
262
384
  agentType: state.agentType,
263
385
  team: state.teamName,
386
+ model: state.model,
264
387
  action: "completed",
265
388
  filesEdited: state.editCount,
266
389
  skills,
390
+ intendedSkills: state.intendedSkills,
267
391
  usage,
268
392
  })
269
393
 
@@ -321,6 +445,7 @@ export const SessionLogPlugin = async ({ client, directory }) => {
321
445
  member: state.member,
322
446
  agentType: state.agentType,
323
447
  team: state.teamName,
448
+ model: state.model,
324
449
  action: "skill-loaded",
325
450
  skill: skillName,
326
451
  source: "skill-tool",
@@ -343,6 +468,7 @@ export const SessionLogPlugin = async ({ client, directory }) => {
343
468
  member: state.member,
344
469
  agentType: state.agentType,
345
470
  team: state.teamName,
471
+ model: state.model,
346
472
  action: "skill-loaded",
347
473
  skill: skillName,
348
474
  source: "read-skill-file",
@@ -362,6 +488,7 @@ export const SessionLogPlugin = async ({ client, directory }) => {
362
488
  member: state.member,
363
489
  agentType: state.agentType,
364
490
  team: state.teamName,
491
+ model: state.model,
365
492
  ...ensembleHandler(args),
366
493
  }
367
494
  appendEntry(directory, entry)
@@ -372,7 +499,7 @@ export const SessionLogPlugin = async ({ client, directory }) => {
372
499
  }
373
500
 
374
501
  if (tool === "team_spawn") {
375
- enqueuePendingSpawn(sessionId, args)
502
+ enqueuePendingSpawn(sessionId, args, output)
376
503
  }
377
504
 
378
505
  if (tool === "team_cleanup") {
@@ -380,6 +507,7 @@ export const SessionLogPlugin = async ({ client, directory }) => {
380
507
  appendEntry(directory, {
381
508
  ts: ts(),
382
509
  agent: state.agentName,
510
+ model: state.model,
383
511
  action: "team-skills-summary",
384
512
  team: teamName || null,
385
513
  byAgent: teamName ? buildTeamSkillsSummary(teamName) : {},
@@ -92,7 +92,14 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
92
92
  DO NOT call team_claim yourself, only agents claim tasks.
93
93
  DO NOT proceed to 6d until team_tasks_add succeeds.
94
94
 
95
- **Step 6d.** Spawn all needed specialists, then kick them off in parallel.
95
+ **Step 6d.** Discover relevant skills, then spawn specialists.
96
+
97
+ Before spawning, scan `.agents/skills/` and read each `SKILL.md` description line.
98
+ Match skills to agents by domain:
99
+ - front-engineer: UI, components, framework skills (e.g. next-best-practices, browser-automation)
100
+ - back-engineer: API, data, service skills
101
+ - infra-engineer: cloud, pipeline, deployment skills
102
+ - quality-engineer: testing, coverage skills
96
103
 
97
104
  Each team_spawn MUST include the agent field (required, causes NOT NULL error if omitted).
98
105
 
@@ -103,8 +110,9 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
103
110
  4. The 6 OpenCode tools they have available (these are OpenCode tools, NOT shell commands, call them directly as tools, never via bash):
104
111
  team_claim, team_tasks_complete, team_tasks_list, team_tasks_add, team_message, team_broadcast
105
112
  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
113
+ 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."
106
114
 
107
- Keep spawn prompts under 500 tokens. Do not describe team internals or how ensemble works.
115
+ Keep spawn prompts under 600 tokens. Do not describe team internals or how ensemble works.
108
116
  Only spawn agents whose tasks are actually needed by this change. Skip agents with no tasks.
109
117
 
110
118
  First spawn all agents (wait for each team_spawn to confirm before the next):
@@ -119,9 +127,9 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
119
127
 
120
128
  Then immediately send each spawned agent a start message to kick them off:
121
129
  ```
122
- team_message to:"back" text:"Start now. Claim your first task with team_claim and begin implementing."
123
- team_message to:"front" text:"Start now. Claim your first task with team_claim and begin implementing."
124
- team_message to:"infra" text:"Start now. Claim your first task with team_claim and begin implementing."
130
+ team_message to:"back" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
131
+ team_message to:"front" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
132
+ team_message to:"infra" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
125
133
  ```
126
134
 
127
135
  **Step 6e.** After sending start messages, tell the user what is running, then STOP and wait.
@@ -137,7 +145,7 @@ Implement tasks from an OpenSpec change using the ensemble agent team.
137
145
 
138
146
  Spawn quality engineer with worktree:false (read-only, no file edits):
139
147
  ```
140
- 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>"
148
+ 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>"
141
149
  ```
142
150
  Wait for message → team_results → fix blockers → team_shutdown (no team_merge needed, worktree:false)
143
151
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-onboard",
3
- "version": "0.2.13",
3
+ "version": "0.3.1",
4
4
  "description": "Prepare any brownfield codebase for AI agent workflows using OpenCode, OpenSpec, and ensemble orchestration.",
5
5
  "keywords": [
6
6
  "opencode",
@@ -38,7 +38,14 @@ 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
 
@@ -49,8 +56,9 @@ const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
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
58
  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
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):
@@ -65,9 +73,9 @@ const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
65
73
 
66
74
  Then immediately send each spawned agent a start message to kick them off:
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. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
77
+ team_message to:"front" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
78
+ team_message to:"infra" text:"Start now. Read all skills listed in your prompt first, confirm loaded skills, then claim your first task with team_claim."
71
79
  \`\`\`
72
80
 
73
81
  **Step 6e.** After sending start messages, tell the user what is running, then STOP and wait.
@@ -89,7 +97,7 @@ const ENSEMBLE_SECTION = `6. **Implement via ensemble team**
89
97
 
90
98
  Spawn quality engineer with worktree:false (read-only, no file edits):
91
99
  \`\`\`
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>"
100
+ 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
101
  \`\`\`
94
102
  Wait for message -> team_results -> fix blockers -> team_shutdown (no team_merge needed, worktree:false)
95
103