@comfanion/workflow 4.38.1-dev.1 → 4.38.1-dev.11
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/package.json +1 -1
- package/src/build-info.json +2 -2
- package/src/opencode/plugins/custom-compaction.ts +298 -110
- package/src/opencode/skills/dev-epic/SKILL.md +106 -31
- package/src/opencode/skills/dev-sprint/SKILL.md +122 -29
- package/src/opencode/skills/dev-story/SKILL.md +101 -14
- package/src/opencode/skills/epic-writing/template.md +30 -6
- package/src/opencode/skills/story-writing/SKILL.md +20 -0
package/package.json
CHANGED
package/src/build-info.json
CHANGED
|
@@ -39,9 +39,20 @@ interface StoryContext {
|
|
|
39
39
|
fullContent: string
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
interface SessionState {
|
|
43
|
+
command: string | null // /dev-sprint, /dev-epic, /dev-story
|
|
44
|
+
agent: string | null
|
|
45
|
+
sprint: { number: number; status: string } | null
|
|
46
|
+
epic: { id: string; title: string; file: string; progress: string } | null
|
|
47
|
+
story: { id: string; title: string; file: string; current_task: string | null; completed_tasks: string[]; pending_tasks: string[] } | null
|
|
48
|
+
next_action: string | null
|
|
49
|
+
key_decisions: string[]
|
|
50
|
+
}
|
|
51
|
+
|
|
42
52
|
interface SessionContext {
|
|
43
53
|
todos: TaskStatus[]
|
|
44
54
|
story: StoryContext | null
|
|
55
|
+
sessionState: SessionState | null
|
|
45
56
|
relevantFiles: string[]
|
|
46
57
|
activeAgent: string | null
|
|
47
58
|
activeCommand: string | null // /dev-story, /dev-epic, /dev-sprint
|
|
@@ -169,7 +180,7 @@ export const CustomCompactionPlugin: Plugin = async (ctx) => {
|
|
|
169
180
|
/**
|
|
170
181
|
* Generate Read commands that agent MUST execute after compaction
|
|
171
182
|
*/
|
|
172
|
-
async function generateReadCommands(agent: string | null, story: StoryContext | null, activeCommand: string | null): Promise<string> {
|
|
183
|
+
async function generateReadCommands(agent: string | null, story: StoryContext | null, activeCommand: string | null, sessionState: SessionState | null): Promise<string> {
|
|
173
184
|
const agentKey = (typeof agent === 'string' ? agent.toLowerCase() : null) || "default"
|
|
174
185
|
const filesToRead = [...(MUST_READ_FILES[agentKey] || MUST_READ_FILES.default)]
|
|
175
186
|
|
|
@@ -179,17 +190,23 @@ export const CustomCompactionPlugin: Plugin = async (ctx) => {
|
|
|
179
190
|
filesToRead.unshift(`.opencode/commands/${commandFile}`)
|
|
180
191
|
}
|
|
181
192
|
|
|
182
|
-
// For dev/coder: add
|
|
193
|
+
// For dev/coder: add session-state.yaml as priority read
|
|
183
194
|
if ((agentKey === "dev" || agentKey === "coder")) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
// Epic state file (has all context)
|
|
187
|
-
filesToRead.unshift(epicState.statePath.replace(directory + "/", ""))
|
|
188
|
-
}
|
|
195
|
+
// Session state is most important — has everything
|
|
196
|
+
filesToRead.unshift(".opencode/session-state.yaml")
|
|
189
197
|
|
|
190
|
-
// Then story file
|
|
191
|
-
|
|
192
|
-
|
|
198
|
+
// Then story file (from session state or StoryContext)
|
|
199
|
+
const storyFile = sessionState?.story?.file || story?.path
|
|
200
|
+
if (storyFile) {
|
|
201
|
+
filesToRead.unshift(storyFile) // Story first!
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Epic state as backup (only if no session state)
|
|
205
|
+
if (!sessionState) {
|
|
206
|
+
const epicState = await getActiveEpicState()
|
|
207
|
+
if (epicState) {
|
|
208
|
+
filesToRead.unshift(epicState.statePath.replace(directory + "/", ""))
|
|
209
|
+
}
|
|
193
210
|
}
|
|
194
211
|
}
|
|
195
212
|
|
|
@@ -304,85 +321,113 @@ DO NOT skip this step. DO NOT ask user what to do. Just read these files first.`
|
|
|
304
321
|
}
|
|
305
322
|
}
|
|
306
323
|
|
|
324
|
+
/**
|
|
325
|
+
* PRIMARY source: session-state.yaml written by AI agent.
|
|
326
|
+
* Falls back to getActiveEpicState() + getActiveStory() if missing.
|
|
327
|
+
*/
|
|
328
|
+
async function getSessionState(): Promise<SessionState | null> {
|
|
329
|
+
try {
|
|
330
|
+
const statePath = join(directory, ".opencode", "session-state.yaml")
|
|
331
|
+
const content = await readFile(statePath, "utf-8")
|
|
332
|
+
await log(directory, ` session-state.yaml found, parsing...`)
|
|
333
|
+
|
|
334
|
+
// Simple regex YAML parser for flat/nested fields
|
|
335
|
+
const str = (key: string): string | null => {
|
|
336
|
+
const m = content.match(new RegExp(`^${key}:\\s*["']?(.+?)["']?\\s*$`, 'm'))
|
|
337
|
+
return m ? m[1].trim() : null
|
|
338
|
+
}
|
|
339
|
+
const nested = (parent: string, key: string): string | null => {
|
|
340
|
+
const section = content.match(new RegExp(`^${parent}:\\s*\\n((?: .+\\n?)*)`, 'm'))
|
|
341
|
+
if (!section) return null
|
|
342
|
+
const m = section[1].match(new RegExp(`^\\s+${key}:\\s*["']?(.+?)["']?\\s*$`, 'm'))
|
|
343
|
+
return m ? m[1].trim() : null
|
|
344
|
+
}
|
|
345
|
+
const list = (parent: string, key: string): string[] => {
|
|
346
|
+
const val = nested(parent, key)
|
|
347
|
+
if (!val) return []
|
|
348
|
+
// Handle [T1, T2] or T1, T2
|
|
349
|
+
const clean = val.replace(/^\[/, '').replace(/\]$/, '')
|
|
350
|
+
return clean.split(',').map(s => s.trim()).filter(Boolean)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Parse key_decisions as list
|
|
354
|
+
const decisions: string[] = []
|
|
355
|
+
const decMatch = content.match(/^key_decisions:\s*\n((?:\s+-\s*.+\n?)*)/m)
|
|
356
|
+
if (decMatch) {
|
|
357
|
+
const items = decMatch[1].matchAll(/^\s+-\s*["']?(.+?)["']?\s*$/gm)
|
|
358
|
+
for (const item of items) {
|
|
359
|
+
decisions.push(item[1])
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const state: SessionState = {
|
|
364
|
+
command: str('command'),
|
|
365
|
+
agent: str('agent'),
|
|
366
|
+
sprint: nested('sprint', 'number') ? {
|
|
367
|
+
number: parseInt(nested('sprint', 'number') || '0'),
|
|
368
|
+
status: nested('sprint', 'status') || 'unknown'
|
|
369
|
+
} : null,
|
|
370
|
+
epic: nested('epic', 'id') ? {
|
|
371
|
+
id: nested('epic', 'id') || '',
|
|
372
|
+
title: nested('epic', 'title') || '',
|
|
373
|
+
file: nested('epic', 'file') || '',
|
|
374
|
+
progress: nested('epic', 'progress') || ''
|
|
375
|
+
} : null,
|
|
376
|
+
story: nested('story', 'id') ? {
|
|
377
|
+
id: nested('story', 'id') || '',
|
|
378
|
+
title: nested('story', 'title') || '',
|
|
379
|
+
file: nested('story', 'file') || '',
|
|
380
|
+
current_task: nested('story', 'current_task'),
|
|
381
|
+
completed_tasks: list('story', 'completed_tasks'),
|
|
382
|
+
pending_tasks: list('story', 'pending_tasks')
|
|
383
|
+
} : null,
|
|
384
|
+
next_action: str('next_action'),
|
|
385
|
+
key_decisions: decisions
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
await log(directory, ` parsed: command=${state.command}, epic=${state.epic?.id}, story=${state.story?.id}`)
|
|
389
|
+
return state
|
|
390
|
+
} catch {
|
|
391
|
+
await log(directory, ` session-state.yaml not found, using fallback`)
|
|
392
|
+
return null
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
307
396
|
async function getActiveStory(): Promise<StoryContext | null> {
|
|
308
397
|
try {
|
|
309
|
-
|
|
398
|
+
let storyPath: string | null = null
|
|
399
|
+
|
|
400
|
+
// First, try epic state file for story path
|
|
310
401
|
const epicState = await getActiveEpicState()
|
|
311
|
-
if (epicState) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (storyPathMatch) {
|
|
315
|
-
const storyFileName = storyPathMatch[1]
|
|
316
|
-
// Find story file
|
|
317
|
-
const sprintMatch = epicState.statePath.match(/sprint-(\d+)/)
|
|
318
|
-
if (sprintMatch) {
|
|
319
|
-
const storyPath = `docs/sprint-artifacts/sprint-${sprintMatch[1]}/stories/${storyFileName}`
|
|
320
|
-
const storyContent = await readFile(join(directory, storyPath), "utf-8")
|
|
321
|
-
|
|
322
|
-
const titleMatch = storyContent.match(/^#\s+(.+)/m)
|
|
323
|
-
const statusMatch = storyContent.match(/\*\*Status:\*\*\s*(\w+)/i)
|
|
324
|
-
|
|
325
|
-
const completedTasks: string[] = []
|
|
326
|
-
const pendingTasks: string[] = []
|
|
327
|
-
let currentTask: string | null = null
|
|
328
|
-
|
|
329
|
-
// Parse tasks with more detail
|
|
330
|
-
const taskRegex = /- \[([ x])\]\s+\*\*T(\d+)\*\*[:\s]+(.+?)(?=\n|$)/g
|
|
331
|
-
let match
|
|
332
|
-
while ((match = taskRegex.exec(storyContent)) !== null) {
|
|
333
|
-
const [, checked, taskId, taskName] = match
|
|
334
|
-
const taskInfo = `T${taskId}: ${taskName.trim()}`
|
|
335
|
-
if (checked === "x") {
|
|
336
|
-
completedTasks.push(taskInfo)
|
|
337
|
-
} else {
|
|
338
|
-
if (!currentTask) currentTask = taskInfo
|
|
339
|
-
pendingTasks.push(taskInfo)
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Parse acceptance criteria
|
|
344
|
-
const acceptanceCriteria: string[] = []
|
|
345
|
-
const acSection = storyContent.match(/## Acceptance Criteria[\s\S]*?(?=##|$)/i)
|
|
346
|
-
if (acSection) {
|
|
347
|
-
const acRegex = /- \[([ x])\]\s+(.+?)(?=\n|$)/g
|
|
348
|
-
while ((match = acRegex.exec(acSection[0])) !== null) {
|
|
349
|
-
const [, checked, criteria] = match
|
|
350
|
-
acceptanceCriteria.push(`${checked === "x" ? "✅" : "⬜"} ${criteria.trim()}`)
|
|
351
|
-
}
|
|
352
|
-
}
|
|
402
|
+
if (epicState?.nextStoryPath) {
|
|
403
|
+
storyPath = epicState.nextStoryPath
|
|
404
|
+
}
|
|
353
405
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
fullContent: storyContent
|
|
363
|
-
}
|
|
406
|
+
// Fallback: try sprint-status.yaml
|
|
407
|
+
if (!storyPath) {
|
|
408
|
+
try {
|
|
409
|
+
const sprintStatusPath = join(directory, "docs", "sprint-artifacts", "sprint-status.yaml")
|
|
410
|
+
const content = await readFile(sprintStatusPath, "utf-8")
|
|
411
|
+
const inProgressMatch = content.match(/status:\s*in-progress[\s\S]*?path:\s*["']?([^"'\n]+)["']?/i)
|
|
412
|
+
if (inProgressMatch) {
|
|
413
|
+
storyPath = inProgressMatch[1]
|
|
364
414
|
}
|
|
415
|
+
} catch {
|
|
416
|
+
// No sprint-status.yaml
|
|
365
417
|
}
|
|
366
418
|
}
|
|
367
|
-
|
|
368
|
-
// Fallback: try old sprint-status.yaml format
|
|
369
|
-
const sprintStatusPath = join(directory, "docs", "sprint-artifacts", "sprint-status.yaml")
|
|
370
|
-
const content = await readFile(sprintStatusPath, "utf-8")
|
|
371
|
-
|
|
372
|
-
const inProgressMatch = content.match(/status:\s*in-progress[\s\S]*?path:\s*["']?([^"'\n]+)["']?/i)
|
|
373
|
-
if (!inProgressMatch) return null
|
|
374
419
|
|
|
375
|
-
|
|
420
|
+
if (!storyPath) return null
|
|
421
|
+
|
|
422
|
+
// Parse story file
|
|
376
423
|
const storyContent = await readFile(join(directory, storyPath), "utf-8")
|
|
377
|
-
|
|
378
424
|
const titleMatch = storyContent.match(/^#\s+(.+)/m)
|
|
379
425
|
const statusMatch = storyContent.match(/\*\*Status:\*\*\s*(\w+)/i)
|
|
380
|
-
|
|
426
|
+
|
|
381
427
|
const completedTasks: string[] = []
|
|
382
428
|
const pendingTasks: string[] = []
|
|
383
429
|
let currentTask: string | null = null
|
|
384
|
-
|
|
385
|
-
// Parse tasks with more detail
|
|
430
|
+
|
|
386
431
|
const taskRegex = /- \[([ x])\]\s+\*\*T(\d+)\*\*[:\s]+(.+?)(?=\n|$)/g
|
|
387
432
|
let match
|
|
388
433
|
while ((match = taskRegex.exec(storyContent)) !== null) {
|
|
@@ -395,8 +440,7 @@ DO NOT skip this step. DO NOT ask user what to do. Just read these files first.`
|
|
|
395
440
|
pendingTasks.push(taskInfo)
|
|
396
441
|
}
|
|
397
442
|
}
|
|
398
|
-
|
|
399
|
-
// Parse acceptance criteria
|
|
443
|
+
|
|
400
444
|
const acceptanceCriteria: string[] = []
|
|
401
445
|
const acSection = storyContent.match(/## Acceptance Criteria[\s\S]*?(?=##|$)/i)
|
|
402
446
|
if (acSection) {
|
|
@@ -478,31 +522,118 @@ DO NOT skip this step. DO NOT ask user what to do. Just read these files first.`
|
|
|
478
522
|
}
|
|
479
523
|
|
|
480
524
|
async function buildContext(agent: string | null): Promise<SessionContext> {
|
|
525
|
+
// PRIMARY: try session-state.yaml (written by AI agent)
|
|
526
|
+
const sessionState = await getSessionState()
|
|
527
|
+
|
|
481
528
|
const [todos, story] = await Promise.all([
|
|
482
529
|
getTodoList(),
|
|
483
|
-
|
|
530
|
+
// If session state has story path, use it; otherwise parse files
|
|
531
|
+
sessionState?.story?.file
|
|
532
|
+
? readStoryFromPath(sessionState.story.file)
|
|
533
|
+
: getActiveStory()
|
|
484
534
|
])
|
|
485
535
|
|
|
486
536
|
const epicState = await getActiveEpicState()
|
|
487
537
|
const relevantFiles = await getRelevantFiles(agent, story)
|
|
488
|
-
|
|
538
|
+
|
|
539
|
+
// Command: from session state or detected from TODOs
|
|
540
|
+
const activeCommand = sessionState?.command || await detectActiveCommand(todos, epicState)
|
|
489
541
|
|
|
490
|
-
return { todos, story, relevantFiles, activeAgent: agent, activeCommand }
|
|
542
|
+
return { todos, story, sessionState, relevantFiles, activeAgent: agent, activeCommand }
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** Read and parse a story file by path */
|
|
546
|
+
async function readStoryFromPath(storyPath: string): Promise<StoryContext | null> {
|
|
547
|
+
try {
|
|
548
|
+
const storyContent = await readFile(join(directory, storyPath), "utf-8")
|
|
549
|
+
const titleMatch = storyContent.match(/^#\s+(.+)/m)
|
|
550
|
+
const statusMatch = storyContent.match(/\*\*Status:\*\*\s*(\w+)/i)
|
|
551
|
+
|
|
552
|
+
const completedTasks: string[] = []
|
|
553
|
+
const pendingTasks: string[] = []
|
|
554
|
+
let currentTask: string | null = null
|
|
555
|
+
|
|
556
|
+
const taskRegex = /- \[([ x])\]\s+\*\*T(\d+)\*\*[:\s]+(.+?)(?=\n|$)/g
|
|
557
|
+
let match
|
|
558
|
+
while ((match = taskRegex.exec(storyContent)) !== null) {
|
|
559
|
+
const [, checked, taskId, taskName] = match
|
|
560
|
+
const taskInfo = `T${taskId}: ${taskName.trim()}`
|
|
561
|
+
if (checked === "x") {
|
|
562
|
+
completedTasks.push(taskInfo)
|
|
563
|
+
} else {
|
|
564
|
+
if (!currentTask) currentTask = taskInfo
|
|
565
|
+
pendingTasks.push(taskInfo)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const acceptanceCriteria: string[] = []
|
|
570
|
+
const acSection = storyContent.match(/## Acceptance Criteria[\s\S]*?(?=##|$)/i)
|
|
571
|
+
if (acSection) {
|
|
572
|
+
const acRegex = /- \[([ x])\]\s+(.+?)(?=\n|$)/g
|
|
573
|
+
while ((match = acRegex.exec(acSection[0])) !== null) {
|
|
574
|
+
const [, checked, criteria] = match
|
|
575
|
+
acceptanceCriteria.push(`${checked === "x" ? "✅" : "⬜"} ${criteria.trim()}`)
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return {
|
|
580
|
+
path: storyPath,
|
|
581
|
+
title: titleMatch?.[1] || "Unknown Story",
|
|
582
|
+
status: statusMatch?.[1] || "unknown",
|
|
583
|
+
currentTask,
|
|
584
|
+
completedTasks,
|
|
585
|
+
pendingTasks,
|
|
586
|
+
acceptanceCriteria,
|
|
587
|
+
fullContent: storyContent
|
|
588
|
+
}
|
|
589
|
+
} catch {
|
|
590
|
+
return null
|
|
591
|
+
}
|
|
491
592
|
}
|
|
492
593
|
|
|
493
594
|
async function formatDevContext(ctx: SessionContext): Promise<string> {
|
|
494
595
|
const sections: string[] = []
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
596
|
+
const ss = ctx.sessionState
|
|
597
|
+
|
|
598
|
+
// SESSION STATE available — use it (primary source)
|
|
599
|
+
if (ss) {
|
|
600
|
+
let header = `## 🎯 Session State (from session-state.yaml)\n`
|
|
601
|
+
header += `**Command:** ${ss.command || "unknown"}\n`
|
|
602
|
+
|
|
603
|
+
if (ss.sprint) {
|
|
604
|
+
header += `**Sprint:** #${ss.sprint.number} (${ss.sprint.status})\n`
|
|
605
|
+
}
|
|
606
|
+
if (ss.epic) {
|
|
607
|
+
header += `**Epic:** ${ss.epic.id} — ${ss.epic.title}\n`
|
|
608
|
+
header += `**Epic File:** \`${ss.epic.file}\`\n`
|
|
609
|
+
header += `**Epic Progress:** ${ss.epic.progress}\n`
|
|
610
|
+
}
|
|
611
|
+
if (ss.story) {
|
|
612
|
+
header += `\n**Story:** ${ss.story.id} — ${ss.story.title}\n`
|
|
613
|
+
header += `**Story File:** \`${ss.story.file}\` ← READ THIS FIRST\n`
|
|
614
|
+
header += `**Current Task:** ${ss.story.current_task || "all done"}\n`
|
|
615
|
+
header += `**Completed:** ${ss.story.completed_tasks.join(", ") || "none"}\n`
|
|
616
|
+
header += `**Pending:** ${ss.story.pending_tasks.join(", ") || "none"}\n`
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
header += `\n### Next Action (DO THIS NOW)\n\`\`\`\n${ss.next_action || "Check TODO list"}\n\`\`\`\n`
|
|
620
|
+
|
|
621
|
+
if (ss.key_decisions.length > 0) {
|
|
622
|
+
header += `\n### Key Technical Decisions\n`
|
|
623
|
+
header += ss.key_decisions.map(d => `- ${d}`).join("\n")
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
sections.push(header)
|
|
627
|
+
}
|
|
628
|
+
// FALLBACK: parse epic state file
|
|
629
|
+
else {
|
|
630
|
+
const epicState = await getActiveEpicState()
|
|
631
|
+
if (epicState) {
|
|
632
|
+
const progress = epicState.totalStories > 0
|
|
633
|
+
? ((epicState.completedCount / epicState.totalStories) * 100).toFixed(0)
|
|
634
|
+
: 0
|
|
504
635
|
|
|
505
|
-
|
|
636
|
+
sections.push(`## 🎯 Epic Workflow: ${epicState.epicTitle}
|
|
506
637
|
|
|
507
638
|
**Epic ID:** ${epicState.epicId}
|
|
508
639
|
**Epic State:** \`${epicState.statePath}\` ← READ THIS FIRST
|
|
@@ -525,7 +656,10 @@ ${epicState.nextStoryPath ? `**Next Story:** \`${epicState.nextStoryPath}\` ←
|
|
|
525
656
|
💡 **Note:** If this is part of /dev-sprint, after epic completes:
|
|
526
657
|
1. Update sprint-status.yaml (mark epic done)
|
|
527
658
|
2. Continue to next epic automatically`)
|
|
528
|
-
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (!ss && ctx.story) {
|
|
529
663
|
// Regular story mode
|
|
530
664
|
const s = ctx.story
|
|
531
665
|
const total = s.completedTasks.length + s.pendingTasks.length
|
|
@@ -777,6 +911,74 @@ This is /dev-epic autopilot mode. Execute stories sequentially until epic done.`
|
|
|
777
911
|
4. Update todo/story when task complete`
|
|
778
912
|
}
|
|
779
913
|
|
|
914
|
+
function buildBriefing(agent: string | null, ss: SessionState | null, ctx: SessionContext, readCommands: string): string {
|
|
915
|
+
const lines: string[] = []
|
|
916
|
+
|
|
917
|
+
// 1. WHO you are
|
|
918
|
+
if (agent) {
|
|
919
|
+
lines.push(`You are @${agent} (→ .opencode/agents/${agent}.md).`)
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// 2. WHAT you are doing
|
|
923
|
+
if (ss) {
|
|
924
|
+
const cmd = ss.command || "unknown command"
|
|
925
|
+
if (ss.epic) {
|
|
926
|
+
lines.push(`You are executing ${cmd} ${ss.epic.id}: ${ss.epic.title}.`)
|
|
927
|
+
} else if (ss.story) {
|
|
928
|
+
lines.push(`You are executing ${cmd} ${ss.story.id}: ${ss.story.title}.`)
|
|
929
|
+
} else {
|
|
930
|
+
lines.push(`You are executing ${cmd}.`)
|
|
931
|
+
}
|
|
932
|
+
} else if (ctx.activeCommand) {
|
|
933
|
+
lines.push(`You are executing ${ctx.activeCommand}.`)
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// 3. WHERE you stopped
|
|
937
|
+
if (ss?.story) {
|
|
938
|
+
const task = ss.story.current_task || "review"
|
|
939
|
+
lines.push(`You were on story ${ss.story.id}: ${ss.story.title}, task ${task}.`)
|
|
940
|
+
if (ss.story.completed_tasks.length > 0) {
|
|
941
|
+
lines.push(`Completed: ${ss.story.completed_tasks.join(", ")}.`)
|
|
942
|
+
}
|
|
943
|
+
if (ss.story.pending_tasks.length > 0) {
|
|
944
|
+
lines.push(`Remaining: ${ss.story.pending_tasks.join(", ")}.`)
|
|
945
|
+
}
|
|
946
|
+
} else if (ss?.epic) {
|
|
947
|
+
lines.push(`Epic progress: ${ss.epic.progress}.`)
|
|
948
|
+
} else if (ctx.story) {
|
|
949
|
+
lines.push(`You were on story: ${ctx.story.title}, task ${ctx.story.currentTask || "review"}.`)
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// 4. WHAT to do next
|
|
953
|
+
if (ss?.next_action) {
|
|
954
|
+
lines.push(`\nNext action: ${ss.next_action}`)
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// 5. READ these files
|
|
958
|
+
lines.push(`\n${readCommands}`)
|
|
959
|
+
|
|
960
|
+
// 6. KEY DECISIONS (if any)
|
|
961
|
+
if (ss?.key_decisions && ss.key_decisions.length > 0) {
|
|
962
|
+
lines.push(`\nKey decisions from your session:`)
|
|
963
|
+
for (const d of ss.key_decisions) {
|
|
964
|
+
lines.push(`- ${d}`)
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// 7. TODO status (brief)
|
|
969
|
+
if (ctx.todos.length > 0) {
|
|
970
|
+
const inProgress = ctx.todos.filter(t => t.status === "in_progress")
|
|
971
|
+
const pending = ctx.todos.filter(t => t.status === "pending")
|
|
972
|
+
const completed = ctx.todos.filter(t => t.status === "completed")
|
|
973
|
+
lines.push(`\nTODO: ${completed.length} done, ${inProgress.length} in progress, ${pending.length} pending.`)
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// 8. RULES
|
|
977
|
+
lines.push(`\nDO NOT ask user what to do. Read files above, then resume automatically.`)
|
|
978
|
+
|
|
979
|
+
return lines.join("\n")
|
|
980
|
+
}
|
|
981
|
+
|
|
780
982
|
return {
|
|
781
983
|
// Track active agent from chat messages
|
|
782
984
|
"chat.message": async (input, output) => {
|
|
@@ -831,26 +1033,12 @@ This is /dev-epic autopilot mode. Execute stories sequentially until epic done.`
|
|
|
831
1033
|
|
|
832
1034
|
const context = await formatContext(ctx)
|
|
833
1035
|
const instructions = await formatInstructions(ctx)
|
|
834
|
-
const readCommands = await generateReadCommands(agent, ctx.story, ctx.activeCommand)
|
|
835
|
-
|
|
836
|
-
// Agent identity reminder
|
|
837
|
-
const agentIdentity = agent
|
|
838
|
-
? `You are @${agent} (.opencode/agents/${agent}.md). Load your persona and continue.`
|
|
839
|
-
: "You are an AI assistant helping with this project."
|
|
840
|
-
|
|
841
|
-
output.context.push(`# Session Continuation
|
|
842
|
-
|
|
843
|
-
${agentIdentity}
|
|
844
|
-
|
|
845
|
-
${readCommands}
|
|
846
|
-
|
|
847
|
-
---
|
|
848
|
-
|
|
849
|
-
${context}
|
|
850
|
-
|
|
851
|
-
---
|
|
1036
|
+
const readCommands = await generateReadCommands(agent, ctx.story, ctx.activeCommand, ctx.sessionState)
|
|
852
1037
|
|
|
853
|
-
|
|
1038
|
+
// Build agentic briefing
|
|
1039
|
+
const ss = ctx.sessionState
|
|
1040
|
+
const briefing = buildBriefing(agent, ss, ctx, readCommands)
|
|
1041
|
+
output.context.push(briefing)
|
|
854
1042
|
|
|
855
1043
|
await log(directory, ` -> output.context pushed (${output.context.length} items)`)
|
|
856
1044
|
await log(directory, `=== COMPACTION DONE ===`)
|
|
@@ -9,64 +9,99 @@ metadata:
|
|
|
9
9
|
|
|
10
10
|
# Dev Epic Skill
|
|
11
11
|
|
|
12
|
+
## CRITICAL: Context Rules
|
|
13
|
+
|
|
14
|
+
**READ ONLY (max ~70KB):**
|
|
15
|
+
- `CLAUDE.md`
|
|
16
|
+
- `docs/coding-standards/README.md`
|
|
17
|
+
- `docs/coding-standards/patterns.md`
|
|
18
|
+
- Epic file (ONE)
|
|
19
|
+
- Current story file (ONE at a time)
|
|
20
|
+
|
|
21
|
+
**❌ DO NOT READ — WASTES CONTEXT:**
|
|
22
|
+
- ❌ `docs/prd.md` — epic/story already has context
|
|
23
|
+
- ❌ `docs/architecture.md` — too large, coding-standards has patterns
|
|
24
|
+
- ❌ All stories at once — read ONE, execute, then next
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
12
28
|
<workflow name="dev-epic">
|
|
13
29
|
|
|
14
30
|
<phase name="1-context" title="Load Minimal Context">
|
|
31
|
+
<critical>DO NOT read prd.md, architecture.md, or all stories!</critical>
|
|
15
32
|
<read>
|
|
16
33
|
<file>CLAUDE.md</file>
|
|
17
34
|
<file>docs/coding-standards/README.md</file>
|
|
18
35
|
<file>docs/coding-standards/patterns.md</file>
|
|
19
36
|
<file>{epic-file}</file>
|
|
20
|
-
<file>{current-story-file}
|
|
37
|
+
<file>{current-story-file} — ONE only!</file>
|
|
21
38
|
</read>
|
|
22
|
-
<
|
|
23
|
-
<file>docs/prd.md</file>
|
|
24
|
-
<file>docs/architecture.md</file>
|
|
25
|
-
<file>all stories at once</file>
|
|
26
|
-
</skip>
|
|
27
|
-
<goal>~70KB per story</goal>
|
|
39
|
+
<goal>~70KB per story, NOT 200KB+</goal>
|
|
28
40
|
</phase>
|
|
29
41
|
|
|
30
42
|
<phase name="2-init" title="Initialize Epic">
|
|
31
|
-
<step n="1">Parse epic file →
|
|
43
|
+
<step n="1">Parse epic file → "Story Tasks" section has all stories + tasks (no need to read story files yet)</step>
|
|
32
44
|
<step n="2">Create epic state: docs/sprint-artifacts/sprint-N/.sprint-state/epic-XX-state.yaml</step>
|
|
33
|
-
<step n="3">Create TODO list:
|
|
45
|
+
<step n="3">Create TODO list with IDs — stories, their tasks, and reviews:
|
|
34
46
|
```
|
|
35
|
-
[ ]
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
[ ] E{N}-S01: {story title}
|
|
48
|
+
[ ] E{N}-S01-T01: {task title}
|
|
49
|
+
[ ] E{N}-S01-T02: {task title}
|
|
50
|
+
[ ] E{N}-S01-Review: run tests, verify AC
|
|
51
|
+
[ ] E{N}-S02: {story title}
|
|
52
|
+
[ ] E{N}-S02-T01: {task title}
|
|
53
|
+
[ ] E{N}-S02-T02: {task title}
|
|
54
|
+
[ ] E{N}-S02-Review: run tests, verify AC
|
|
39
55
|
...
|
|
40
|
-
[ ]
|
|
41
|
-
[ ]
|
|
56
|
+
[ ] E{N}-Integration: run epic integration tests
|
|
57
|
+
[ ] E{N}-AC: verify epic acceptance criteria
|
|
42
58
|
```
|
|
43
59
|
</step>
|
|
60
|
+
<example>
|
|
61
|
+
```
|
|
62
|
+
[ ] E04-S01: Merge Domain Logic
|
|
63
|
+
[ ] E04-S01-T01: MergeResult value object
|
|
64
|
+
[ ] E04-S01-T02: Merge service — primary selection
|
|
65
|
+
[ ] E04-S01-T03: Unit tests
|
|
66
|
+
[ ] E04-S01-Review: run tests, verify AC
|
|
67
|
+
[ ] E04-S02: Auto Merge on Link
|
|
68
|
+
[ ] E04-S02-T01: Event handler for link
|
|
69
|
+
[ ] E04-S02-T02: Best-effort merge logic
|
|
70
|
+
[ ] E04-S02-T03: Integration tests
|
|
71
|
+
[ ] E04-S02-Review: run tests, verify AC
|
|
72
|
+
[ ] E04-Integration: run epic integration tests
|
|
73
|
+
[ ] E04-AC: verify epic acceptance criteria
|
|
74
|
+
```
|
|
75
|
+
</example>
|
|
44
76
|
<step n="4">Set state: status="in-progress", next_action="Execute [first-story.md]"</step>
|
|
45
77
|
<step n="5">Mark first story as in_progress in TODO</step>
|
|
46
78
|
</phase>
|
|
47
79
|
|
|
48
80
|
<phase name="3-loop" title="Story Execution Loop">
|
|
81
|
+
<critical>Status flow: in_progress → review → done. NEVER mark done before review!</critical>
|
|
49
82
|
<for-each item="story" in="pending_stories">
|
|
50
83
|
|
|
51
84
|
<action name="execute-story">
|
|
52
85
|
Follow /dev-story logic:
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
86
|
+
- Read ONE story file
|
|
87
|
+
- Execute tasks ONE BY ONE (or parallel if independent)
|
|
88
|
+
- NEVER delegate entire story to @coder in one prompt
|
|
89
|
+
- After each task: verify, mark done, next task
|
|
56
90
|
</action>
|
|
57
91
|
|
|
58
|
-
<action name="
|
|
59
|
-
|
|
92
|
+
<action name="story-to-review">
|
|
93
|
+
All tasks done → set story status: review
|
|
94
|
+
Mark story TODO as "review" (NOT "done" yet!)
|
|
60
95
|
</action>
|
|
61
96
|
|
|
62
|
-
<action name="review">
|
|
63
|
-
|
|
64
|
-
Invoke @reviewer
|
|
97
|
+
<action name="review-story">
|
|
98
|
+
Invoke @reviewer on story code
|
|
65
99
|
<if condition="CHANGES_REQUESTED">
|
|
66
100
|
Add fix tasks → re-execute → re-review (max 3 attempts)
|
|
67
101
|
</if>
|
|
68
102
|
<if condition="APPROVED">
|
|
69
|
-
|
|
103
|
+
Set story status: done
|
|
104
|
+
Mark story TODO as completed
|
|
70
105
|
</if>
|
|
71
106
|
</action>
|
|
72
107
|
|
|
@@ -79,32 +114,72 @@ metadata:
|
|
|
79
114
|
|
|
80
115
|
<action name="compact">
|
|
81
116
|
Mark next story as in_progress in TODO
|
|
82
|
-
Wait for auto-compaction
|
|
83
|
-
Plugin reads TODO + state → resume
|
|
117
|
+
Wait for auto-compaction → resume
|
|
84
118
|
</action>
|
|
85
119
|
|
|
86
120
|
</for-each>
|
|
87
121
|
</phase>
|
|
88
122
|
|
|
89
123
|
<phase name="4-finalize" title="Finalize Epic">
|
|
90
|
-
<
|
|
91
|
-
<step n="
|
|
92
|
-
<step n="
|
|
93
|
-
<step n="
|
|
94
|
-
<step n="
|
|
124
|
+
<critical>Epic also goes through review before done!</critical>
|
|
125
|
+
<step n="1">All stories done → set epic status: review</step>
|
|
126
|
+
<step n="2">Run epic integration tests (mark in TODO)</step>
|
|
127
|
+
<step n="3">Verify all AC from epic file (mark in TODO)</step>
|
|
128
|
+
<step n="4">If tests fail → fix → re-test</step>
|
|
129
|
+
<step n="5">All passed → set epic status: done</step>
|
|
130
|
+
<step n="6">Clear epic TODO list</step>
|
|
131
|
+
<step n="7">Update .opencode/session-state.yaml (next epic or done)</step>
|
|
132
|
+
<step n="8">Report completion with summary</step>
|
|
95
133
|
</phase>
|
|
96
134
|
|
|
97
135
|
</workflow>
|
|
98
136
|
|
|
137
|
+
## Session State (MANDATORY)
|
|
138
|
+
|
|
139
|
+
After each story/task completion, update `.opencode/session-state.yaml`:
|
|
140
|
+
|
|
141
|
+
```yaml
|
|
142
|
+
# .opencode/session-state.yaml — AI writes, compaction plugin reads
|
|
143
|
+
command: /dev-epic
|
|
144
|
+
agent: dev
|
|
145
|
+
|
|
146
|
+
epic:
|
|
147
|
+
id: PROJ-E01
|
|
148
|
+
title: Epic Title
|
|
149
|
+
file: docs/sprint-artifacts/sprint-1/epic-01-desc.md
|
|
150
|
+
progress: "3/5 stories"
|
|
151
|
+
|
|
152
|
+
story:
|
|
153
|
+
id: PROJ-S01-03
|
|
154
|
+
title: Current Story Title
|
|
155
|
+
file: docs/sprint-artifacts/sprint-1/stories/story-01-03-desc.md
|
|
156
|
+
current_task: T2
|
|
157
|
+
completed_tasks: [T1]
|
|
158
|
+
pending_tasks: [T2, T3]
|
|
159
|
+
|
|
160
|
+
next_action: "Continue T2 of story S01-03"
|
|
161
|
+
|
|
162
|
+
key_decisions:
|
|
163
|
+
- "Decision 1"
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
This file survives compaction and tells the agent where to resume.
|
|
167
|
+
|
|
99
168
|
<outputs>
|
|
100
169
|
- Implementation code for all stories
|
|
101
170
|
- Updated epic-XX-state.yaml
|
|
171
|
+
- Updated .opencode/session-state.yaml
|
|
102
172
|
- Clean TODO list (all completed)
|
|
103
173
|
</outputs>
|
|
104
174
|
|
|
105
175
|
<rules>
|
|
106
176
|
<do>Create clean TODO list for each epic</do>
|
|
107
177
|
<do>Update epic state file BEFORE compaction</do>
|
|
178
|
+
<do>Execute stories IN ORDER as planned in epic file</do>
|
|
179
|
+
<do>Execute tasks within story ONE BY ONE (or parallel if independent)</do>
|
|
108
180
|
<dont>Ask user for confirmation between stories — TODO is your guide</dont>
|
|
109
181
|
<dont>Proceed to next story if review fails — enter fix loop</dont>
|
|
182
|
+
<dont>Reorder, skip, merge, or "optimize" story execution order</dont>
|
|
183
|
+
<dont>Combine tasks from different stories into one batch</dont>
|
|
184
|
+
<dont>Delegate entire story to @coder in one prompt — task by task only</dont>
|
|
110
185
|
</rules>
|
|
@@ -9,46 +9,88 @@ metadata:
|
|
|
9
9
|
|
|
10
10
|
# Dev Sprint Skill
|
|
11
11
|
|
|
12
|
+
## CRITICAL: Context Rules
|
|
13
|
+
|
|
14
|
+
**READ ONLY (max ~70KB):**
|
|
15
|
+
- `CLAUDE.md`
|
|
16
|
+
- `docs/coding-standards/README.md`
|
|
17
|
+
- `docs/coding-standards/patterns.md`
|
|
18
|
+
- `sprint-status.yaml`
|
|
19
|
+
- Current epic file (ONE)
|
|
20
|
+
- Current story file (ONE at a time)
|
|
21
|
+
|
|
22
|
+
**❌ DO NOT READ — WASTES CONTEXT:**
|
|
23
|
+
- ❌ `docs/prd.md` — epic/story already has context
|
|
24
|
+
- ❌ `docs/architecture.md` — too large, coding-standards has patterns
|
|
25
|
+
- ❌ All epics at once — read ONE, execute, then next
|
|
26
|
+
- ❌ All stories at once — read ONE, execute, then next
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
12
30
|
<workflow name="dev-sprint">
|
|
13
31
|
|
|
14
32
|
<phase name="1-context" title="Load Minimal Context">
|
|
33
|
+
<critical>DO NOT read prd.md, architecture.md, or all epics/stories!</critical>
|
|
15
34
|
<read>
|
|
16
35
|
<file>CLAUDE.md</file>
|
|
17
36
|
<file>docs/coding-standards/README.md</file>
|
|
18
37
|
<file>docs/coding-standards/patterns.md</file>
|
|
19
38
|
<file>sprint-status.yaml</file>
|
|
20
|
-
<file>{current-epic-file}
|
|
21
|
-
<file>{current-story-file}
|
|
39
|
+
<file>{current-epic-file} — ONE only!</file>
|
|
40
|
+
<file>{current-story-file} — ONE only!</file>
|
|
22
41
|
</read>
|
|
23
|
-
<
|
|
24
|
-
<file>docs/prd.md</file>
|
|
25
|
-
<file>docs/architecture.md</file>
|
|
26
|
-
<file>all epics/stories at once</file>
|
|
27
|
-
</skip>
|
|
28
|
-
<goal>~70KB per story</goal>
|
|
42
|
+
<goal>~70KB per story, NOT 200KB+</goal>
|
|
29
43
|
</phase>
|
|
30
44
|
|
|
31
45
|
<phase name="2-init" title="Initialize Sprint">
|
|
32
46
|
<step n="1">Parse sprint-status.yaml → extract epic list for target sprint</step>
|
|
33
|
-
<step n="2">Create master TODO list:
|
|
47
|
+
<step n="2">Create master TODO list with IDs — epics, stories, tasks, and reviews:
|
|
34
48
|
```
|
|
35
|
-
[ ]
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
[ ]
|
|
49
|
+
[ ] E{N1}: {epic title}
|
|
50
|
+
[ ] E{N1}-S01: {story title}
|
|
51
|
+
[ ] E{N1}-S01-T01: {task title}
|
|
52
|
+
[ ] E{N1}-S01-T02: {task title}
|
|
53
|
+
[ ] E{N1}-S01-Review: run tests, verify AC
|
|
54
|
+
[ ] E{N1}-S02: {story title}
|
|
55
|
+
[ ] E{N1}-S02-T01: {task title}
|
|
56
|
+
[ ] E{N1}-S02-Review: run tests, verify AC
|
|
57
|
+
[ ] E{N1}-Integration: epic integration tests
|
|
58
|
+
[ ] E{N2}: {epic title}
|
|
59
|
+
[ ] E{N2}-S01: {story title}
|
|
60
|
+
[ ] E{N2}-S01-T01: {task title}
|
|
61
|
+
[ ] E{N2}-S01-Review: run tests, verify AC
|
|
62
|
+
[ ] E{N2}-Integration: epic integration tests
|
|
63
|
+
[ ] Sprint-Integration: run sprint integration tests
|
|
45
64
|
```
|
|
46
65
|
</step>
|
|
66
|
+
<example>
|
|
67
|
+
```
|
|
68
|
+
[ ] E04: Identity Merge
|
|
69
|
+
[ ] E04-S01: Merge Domain Logic
|
|
70
|
+
[ ] E04-S01-T01: MergeResult value object
|
|
71
|
+
[ ] E04-S01-T02: Merge service
|
|
72
|
+
[ ] E04-S01-T03: Unit tests
|
|
73
|
+
[ ] E04-S01-Review: run tests, verify AC
|
|
74
|
+
[ ] E04-S02: Auto Merge on Link
|
|
75
|
+
[ ] E04-S02-T01: Event handler
|
|
76
|
+
[ ] E04-S02-T02: Integration tests
|
|
77
|
+
[ ] E04-S02-Review: run tests, verify AC
|
|
78
|
+
[ ] E04-Integration: epic integration tests
|
|
79
|
+
[ ] E06: Team Management
|
|
80
|
+
[ ] E06-S01: Team CRUD
|
|
81
|
+
[ ] E06-S01-T01: Domain model
|
|
82
|
+
[ ] E06-S01-T02: Handler
|
|
83
|
+
[ ] E06-S01-Review: run tests, verify AC
|
|
84
|
+
[ ] E06-Integration: epic integration tests
|
|
85
|
+
[ ] Sprint-Integration: run sprint integration tests
|
|
86
|
+
```
|
|
87
|
+
</example>
|
|
47
88
|
<step n="3">Set sprint status="in-progress" in sprint-status.yaml</step>
|
|
48
89
|
<step n="4">Mark first epic as in_progress in TODO</step>
|
|
49
90
|
</phase>
|
|
50
91
|
|
|
51
92
|
<phase name="3-loop" title="Epic Execution Loop">
|
|
93
|
+
<critical>Status flow: in_progress → review → done. NEVER mark done before review!</critical>
|
|
52
94
|
<for-each item="epic" in="pending_epics">
|
|
53
95
|
|
|
54
96
|
<action name="execute-epic">
|
|
@@ -58,14 +100,20 @@ metadata:
|
|
|
58
100
|
- Clears epic TODO when done
|
|
59
101
|
</action>
|
|
60
102
|
|
|
61
|
-
<action name="
|
|
62
|
-
|
|
103
|
+
<action name="epic-to-review">
|
|
104
|
+
All stories done → set epic status: review
|
|
105
|
+
Mark epic TODO as "review" (NOT "done" yet!)
|
|
63
106
|
</action>
|
|
64
107
|
|
|
65
108
|
<action name="epic-review">
|
|
66
|
-
Mark "Review Epic" as in_progress
|
|
67
109
|
Run epic integration tests
|
|
68
|
-
|
|
110
|
+
<if condition="TESTS_FAIL">
|
|
111
|
+
Fix → re-test (max 3 attempts)
|
|
112
|
+
</if>
|
|
113
|
+
<if condition="TESTS_PASS">
|
|
114
|
+
Set epic status: done
|
|
115
|
+
Mark epic TODO as completed
|
|
116
|
+
</if>
|
|
69
117
|
</action>
|
|
70
118
|
|
|
71
119
|
<action name="update-state">
|
|
@@ -76,31 +124,76 @@ metadata:
|
|
|
76
124
|
|
|
77
125
|
<action name="compact">
|
|
78
126
|
Mark next epic as in_progress in TODO
|
|
79
|
-
Wait for auto-compaction
|
|
80
|
-
Plugin reads sprint TODO → resume
|
|
127
|
+
Wait for auto-compaction → resume
|
|
81
128
|
</action>
|
|
82
129
|
|
|
83
130
|
</for-each>
|
|
84
131
|
</phase>
|
|
85
132
|
|
|
86
133
|
<phase name="4-finalize" title="Finalize Sprint">
|
|
87
|
-
<
|
|
88
|
-
<step n="
|
|
89
|
-
<step n="
|
|
90
|
-
<step n="
|
|
134
|
+
<critical>Sprint also goes through review before done!</critical>
|
|
135
|
+
<step n="1">All epics done → set sprint status: review</step>
|
|
136
|
+
<step n="2">Run sprint integration tests (mark in TODO)</step>
|
|
137
|
+
<step n="3">If tests fail → fix → re-test</step>
|
|
138
|
+
<step n="4">All passed → set sprint status: done in sprint-status.yaml</step>
|
|
139
|
+
<step n="5">Clear sprint TODO list</step>
|
|
140
|
+
<step n="6">Update .opencode/session-state.yaml (done)</step>
|
|
141
|
+
<step n="7">Report completion with summary + metrics</step>
|
|
91
142
|
</phase>
|
|
92
143
|
|
|
93
144
|
</workflow>
|
|
94
145
|
|
|
146
|
+
## Session State (MANDATORY)
|
|
147
|
+
|
|
148
|
+
After each epic/story/task completion, update `.opencode/session-state.yaml`:
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
# .opencode/session-state.yaml — AI writes, compaction plugin reads
|
|
152
|
+
command: /dev-sprint
|
|
153
|
+
agent: dev
|
|
154
|
+
|
|
155
|
+
sprint:
|
|
156
|
+
number: 2
|
|
157
|
+
status: in-progress
|
|
158
|
+
|
|
159
|
+
epic:
|
|
160
|
+
id: PROJ-E04
|
|
161
|
+
title: Current Epic Title
|
|
162
|
+
file: docs/sprint-artifacts/sprint-2/epic-04-desc.md
|
|
163
|
+
progress: "3/5 stories"
|
|
164
|
+
|
|
165
|
+
story:
|
|
166
|
+
id: PROJ-S04-03
|
|
167
|
+
title: Current Story Title
|
|
168
|
+
file: docs/sprint-artifacts/sprint-2/stories/story-04-03-desc.md
|
|
169
|
+
current_task: T2
|
|
170
|
+
completed_tasks: [T1]
|
|
171
|
+
pending_tasks: [T2, T3]
|
|
172
|
+
|
|
173
|
+
next_action: "Continue T2 of story S04-03"
|
|
174
|
+
|
|
175
|
+
key_decisions:
|
|
176
|
+
- "Decision 1"
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
This file survives compaction and tells the agent where to resume.
|
|
180
|
+
|
|
95
181
|
<outputs>
|
|
96
182
|
- Implementation code for all stories in sprint
|
|
97
183
|
- Updated sprint-status.yaml
|
|
184
|
+
- Updated .opencode/session-state.yaml
|
|
98
185
|
- Clean TODO list (all completed)
|
|
99
186
|
</outputs>
|
|
100
187
|
|
|
101
188
|
<rules>
|
|
102
189
|
<do>Create ONE master TODO list for entire sprint at start</do>
|
|
103
190
|
<do>Let dev-epic skill manage its own nested TODO</do>
|
|
191
|
+
<do>Execute epics IN ORDER as planned in sprint-status.yaml</do>
|
|
192
|
+
<do>Within each epic, execute stories IN ORDER as planned</do>
|
|
193
|
+
<do>Within each story, execute tasks ONE BY ONE (or parallel if independent)</do>
|
|
104
194
|
<dont>Ask for confirmation between epics — sprint TODO is your guide</dont>
|
|
105
195
|
<dont>Proceed to next epic if epic review fails — HALT and report</dont>
|
|
196
|
+
<dont>Reorder, skip, merge, or "optimize" epic/story execution order</dont>
|
|
197
|
+
<dont>Work on multiple stories or epics in parallel</dont>
|
|
198
|
+
<dont>Delegate entire story to @coder in one prompt — task by task only</dont>
|
|
106
199
|
</rules>
|
|
@@ -11,20 +11,31 @@ metadata:
|
|
|
11
11
|
|
|
12
12
|
# Dev Story Skill
|
|
13
13
|
|
|
14
|
+
## CRITICAL: Context Rules
|
|
15
|
+
|
|
16
|
+
**READ ONLY (max ~70KB):**
|
|
17
|
+
- `CLAUDE.md`
|
|
18
|
+
- `docs/coding-standards/README.md`
|
|
19
|
+
- `docs/coding-standards/patterns.md`
|
|
20
|
+
- Story file
|
|
21
|
+
|
|
22
|
+
**❌ DO NOT READ — WASTES CONTEXT:**
|
|
23
|
+
- ❌ `docs/prd.md` — story already has context
|
|
24
|
+
- ❌ `docs/architecture.md` — too large, coding-standards has patterns
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
14
28
|
<workflow name="dev-story">
|
|
15
29
|
|
|
16
30
|
<phase name="1-context" title="Load Minimal Context">
|
|
31
|
+
<critical>DO NOT read prd.md or architecture.md!</critical>
|
|
17
32
|
<read>
|
|
18
33
|
<file>CLAUDE.md</file>
|
|
19
34
|
<file>docs/coding-standards/README.md</file>
|
|
20
35
|
<file>docs/coding-standards/patterns.md</file>
|
|
21
36
|
<file>{story-file}</file>
|
|
22
37
|
</read>
|
|
23
|
-
<
|
|
24
|
-
<file>docs/prd.md</file>
|
|
25
|
-
<file>docs/architecture.md</file>
|
|
26
|
-
</skip>
|
|
27
|
-
<goal>~70KB context, not 200KB+</goal>
|
|
38
|
+
<goal>~70KB context, NOT 200KB+</goal>
|
|
28
39
|
</phase>
|
|
29
40
|
|
|
30
41
|
<phase name="2-transform" title="Transform Story Task → Executable Instruction">
|
|
@@ -57,21 +68,90 @@ metadata:
|
|
|
57
68
|
</step>
|
|
58
69
|
</phase>
|
|
59
70
|
|
|
60
|
-
<phase name="
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
|
|
71
|
+
<phase name="2b-todo" title="Create TODO with IDs">
|
|
72
|
+
<critical>TODO MUST use task IDs from story file!</critical>
|
|
73
|
+
<template>
|
|
74
|
+
```
|
|
75
|
+
[ ] E{E}-S{N}-T01: {task title from story}
|
|
76
|
+
[ ] E{E}-S{N}-T02: {task title}
|
|
77
|
+
[ ] E{E}-S{N}-T03: {task title}
|
|
78
|
+
...
|
|
79
|
+
[ ] E{E}-S{N}-Review: run all tests, verify AC
|
|
80
|
+
```
|
|
81
|
+
</template>
|
|
82
|
+
<example>
|
|
83
|
+
```
|
|
84
|
+
[ ] E04-S01-T01: Domain Model — MergeResult value object
|
|
85
|
+
[ ] E04-S01-T02: Merge Service — primary selection logic
|
|
86
|
+
[ ] E04-S01-T03: External ID reassignment
|
|
87
|
+
[ ] E04-S01-T04: Unit tests for merge logic
|
|
88
|
+
[ ] E04-S01-Review: run all tests, verify AC
|
|
89
|
+
```
|
|
90
|
+
</example>
|
|
64
91
|
</phase>
|
|
65
92
|
|
|
66
|
-
<phase name="
|
|
67
|
-
<
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
93
|
+
<phase name="3-execute" title="Execute Tasks — One by One or Parallel">
|
|
94
|
+
<critical>DO NOT delegate entire story to @coder in one prompt!</critical>
|
|
95
|
+
<strategy>
|
|
96
|
+
For each task in TODO:
|
|
97
|
+
1. Check task dependencies (from story file)
|
|
98
|
+
2. If independent tasks exist → delegate in parallel (different files only)
|
|
99
|
+
3. If task depends on previous → delegate ONE task, wait for result
|
|
100
|
+
4. After @coder returns → verify, mark done, then next task
|
|
101
|
+
</strategy>
|
|
102
|
+
<loop>
|
|
103
|
+
<step n="1">Pick next task(s) from TODO</step>
|
|
104
|
+
<step n="2">Transform task → executable instruction (phase 2)</step>
|
|
105
|
+
<step n="3">Delegate to @coder using template below</step>
|
|
106
|
+
<step n="4">Verify result: tests pass, files compile</step>
|
|
107
|
+
<step n="5">Mark task ✅ in story file + TODO</step>
|
|
108
|
+
<step n="6">Update .opencode/session-state.yaml</step>
|
|
109
|
+
<step n="7">Next task or story complete</step>
|
|
110
|
+
</loop>
|
|
111
|
+
<parallel-rules>
|
|
112
|
+
OK to parallel: T01 (domain) + T02 (dto) — different files, no deps
|
|
113
|
+
NOT OK: T03 (service) depends on T01 (domain) — wait for T01 first
|
|
114
|
+
</parallel-rules>
|
|
115
|
+
</phase>
|
|
116
|
+
|
|
117
|
+
<phase name="4-review" title="Review BEFORE Done">
|
|
118
|
+
<critical>Status flow: in_progress → review → done. NEVER skip review!</critical>
|
|
119
|
+
<step n="1">All tasks done → set story status: review</step>
|
|
120
|
+
<step n="2">Run all tests, verify AC</step>
|
|
121
|
+
<step n="3">If called from /dev-epic: invoke @reviewer</step>
|
|
122
|
+
<step n="4">If fixes needed → fix → re-review (max 3 attempts)</step>
|
|
123
|
+
<step n="5">Review passed → set story status: done</step>
|
|
124
|
+
<step n="6">Update .opencode/session-state.yaml</step>
|
|
71
125
|
</phase>
|
|
72
126
|
|
|
73
127
|
</workflow>
|
|
74
128
|
|
|
129
|
+
## Session State (MANDATORY)
|
|
130
|
+
|
|
131
|
+
After each task completion, update `.opencode/session-state.yaml`:
|
|
132
|
+
|
|
133
|
+
```yaml
|
|
134
|
+
# .opencode/session-state.yaml — AI writes, compaction plugin reads
|
|
135
|
+
command: /dev-story
|
|
136
|
+
agent: dev
|
|
137
|
+
|
|
138
|
+
story:
|
|
139
|
+
id: PROJ-S01-01
|
|
140
|
+
title: Story Title
|
|
141
|
+
file: docs/sprint-artifacts/sprint-1/stories/story-01-01-desc.md
|
|
142
|
+
current_task: T3
|
|
143
|
+
completed_tasks: [T1, T2]
|
|
144
|
+
pending_tasks: [T3, T4]
|
|
145
|
+
|
|
146
|
+
next_action: "Continue T3: Implement handler"
|
|
147
|
+
|
|
148
|
+
key_decisions:
|
|
149
|
+
- "Decision 1"
|
|
150
|
+
- "Decision 2"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
This file survives compaction and tells the agent where to resume.
|
|
154
|
+
|
|
75
155
|
## Task Template for @coder
|
|
76
156
|
|
|
77
157
|
<template name="coder-task">
|
|
@@ -108,9 +188,16 @@ metadata:
|
|
|
108
188
|
</template>
|
|
109
189
|
|
|
110
190
|
<rules name="delegation">
|
|
191
|
+
<rule name="task-by-task" critical="true">
|
|
192
|
+
Delegate ONE task at a time (or parallel group if independent).
|
|
193
|
+
NEVER delegate entire story as one big prompt.
|
|
194
|
+
</rule>
|
|
111
195
|
<rule name="parallel">
|
|
112
196
|
Each task gets full context. No shared state. Different files only.
|
|
113
197
|
</rule>
|
|
198
|
+
<rule name="verify-between">
|
|
199
|
+
After each task: run tests, verify, mark done, THEN next task.
|
|
200
|
+
</rule>
|
|
114
201
|
<rule name="no-code">
|
|
115
202
|
Give direction, NOT solution. @coder writes implementation.
|
|
116
203
|
</rule>
|
|
@@ -79,10 +79,10 @@ Epic is complete when:
|
|
|
79
79
|
|
|
80
80
|
## Stories
|
|
81
81
|
|
|
82
|
-
| ID | Title | Size |
|
|
83
|
-
|
|
84
|
-
|
|
|
85
|
-
|
|
|
82
|
+
| ID | Title | Size | File | Status |
|
|
83
|
+
|----|-------|------|------|--------|
|
|
84
|
+
| E{{E}}-S01 | {{title}} | S/M/L | `story-{{E}}-01-{{slug}}.md` | ⬜ |
|
|
85
|
+
| E{{E}}-S02 | {{title}} | S/M/L | `story-{{E}}-02-{{slug}}.md` | ⬜ |
|
|
86
86
|
|
|
87
87
|
**Dependency Flow:**
|
|
88
88
|
```
|
|
@@ -91,8 +91,32 @@ S01 ──► S02 ──► S03
|
|
|
91
91
|
└──► S04
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Story Tasks
|
|
97
|
+
|
|
98
|
+
<!-- This section is populated by /stories command.
|
|
99
|
+
Used by /dev-epic to build TODO without reading all story files. -->
|
|
100
|
+
|
|
101
|
+
### E{{E}}-S01: {{story title}}
|
|
102
|
+
- E{{E}}-S01-T01: {{task title}}
|
|
103
|
+
- E{{E}}-S01-T02: {{task title}}
|
|
104
|
+
|
|
105
|
+
### E{{E}}-S02: {{story title}}
|
|
106
|
+
- E{{E}}-S02-T01: {{task title}}
|
|
107
|
+
- E{{E}}-S02-T02: {{task title}}
|
|
108
|
+
|
|
109
|
+
<!-- Example:
|
|
110
|
+
### E04-S01: Merge Domain Logic
|
|
111
|
+
- E04-S01-T01: MergeResult value object
|
|
112
|
+
- E04-S01-T02: Merge service — primary selection
|
|
113
|
+
- E04-S01-T03: Unit tests
|
|
114
|
+
|
|
115
|
+
### E04-S02: Auto Merge on Link
|
|
116
|
+
- E04-S02-T01: Event handler for link
|
|
117
|
+
- E04-S02-T02: Best-effort merge logic
|
|
118
|
+
- E04-S02-T03: Integration tests
|
|
119
|
+
-->
|
|
96
120
|
|
|
97
121
|
---
|
|
98
122
|
|
|
@@ -233,6 +233,26 @@ T1 ──► T2 ──► T3
|
|
|
233
233
|
|
|
234
234
|
Save to: `docs/sprint-artifacts/sprint-[N]/stories/story-[EPIC]-[NN]-[description].md`
|
|
235
235
|
|
|
236
|
+
## MANDATORY: Update Epic File
|
|
237
|
+
|
|
238
|
+
After creating stories, update the parent epic's **Story Tasks** section.
|
|
239
|
+
This lets `/dev-epic` build TODO from epic file alone — no need to read all stories.
|
|
240
|
+
|
|
241
|
+
```markdown
|
|
242
|
+
## Story Tasks
|
|
243
|
+
|
|
244
|
+
### E04-S01: Merge Domain Logic
|
|
245
|
+
- E04-S01-T01: MergeResult value object
|
|
246
|
+
- E04-S01-T02: Merge service — primary selection
|
|
247
|
+
- E04-S01-T03: Unit tests
|
|
248
|
+
|
|
249
|
+
### E04-S02: Auto Merge on Link
|
|
250
|
+
- E04-S02-T01: Event handler for link
|
|
251
|
+
- E04-S02-T02: Best-effort merge logic
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Format:** `E{NN}-S{NN}-T{NN}: {task title}` — one line per task.
|
|
255
|
+
|
|
236
256
|
## Related Skills
|
|
237
257
|
|
|
238
258
|
- `acceptance-criteria` - For detailed AC
|