@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comfanion/workflow",
3
- "version": "4.38.1-dev.1",
3
+ "version": "4.38.1-dev.11",
4
4
  "description": "Initialize OpenCode Workflow system for AI-assisted development with semantic code search",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
- "version": "4.38.1-dev.1",
3
- "buildDate": "2026-01-27T01:03:19.105Z",
2
+ "version": "4.38.1-dev.11",
3
+ "buildDate": "2026-01-27T11:48:48.852Z",
4
4
  "files": [
5
5
  "config.yaml",
6
6
  "FLOW.yaml",
@@ -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 epic state file if in epic workflow
193
+ // For dev/coder: add session-state.yaml as priority read
183
194
  if ((agentKey === "dev" || agentKey === "coder")) {
184
- const epicState = await getActiveEpicState()
185
- if (epicState) {
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 if active
191
- if (story) {
192
- filesToRead.unshift(story.path) // Story first!
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
- // First, try to find epic state file
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
- // Parse epic state to get current story
313
- const storyPathMatch = epicState.content.match(/next_action:\s*["']?Execute\s+(.+?)["']?$/m)
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
- return {
355
- path: storyPath,
356
- title: titleMatch?.[1] || "Unknown Story",
357
- status: statusMatch?.[1] || "unknown",
358
- currentTask,
359
- completedTasks,
360
- pendingTasks,
361
- acceptanceCriteria,
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
- const storyPath = inProgressMatch[1]
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
- getActiveStory()
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
- const activeCommand = await detectActiveCommand(todos, epicState)
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
- // Check if we're in epic workflow
497
- const epicState = await getActiveEpicState()
498
-
499
- if (epicState) {
500
- // Epic/Sprint workflow mode - show epic progress
501
- const progress = epicState.totalStories > 0
502
- ? ((epicState.completedCount / epicState.totalStories) * 100).toFixed(0)
503
- : 0
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
- sections.push(`## 🎯 Epic Workflow: ${epicState.epicTitle}
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
- } else if (ctx.story) {
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
- ${instructions}`)
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}</file>
37
+ <file>{current-story-file} — ONE only!</file>
21
38
  </read>
22
- <skip reason="too large, epic/story has context">
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 → extract story list</step>
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
- [ ] Story 1: [Title]
36
- [ ] Review Story 1
37
- [ ] Story 2: [Title]
38
- [ ] Review Story 2
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
- [ ] Epic integration tests
41
- [ ] Verify epic AC
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
- - Create nested TODO for tasks
54
- - Implement all tasks (RED/GREEN/REFACTOR)
55
- - Clear task TODO when done
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="mark-done">
59
- Mark story as completed in epic TODO
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
- Mark "Review Story" as in_progress
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
- Mark "Review Story" as completed
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
- <step n="1">Run epic integration tests (mark in TODO)</step>
91
- <step n="2">Verify all AC from epic file (mark in TODO)</step>
92
- <step n="3">Set state: status="done"</step>
93
- <step n="4">Clear epic TODO list</step>
94
- <step n="5">Report completion with summary</step>
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}</file>
21
- <file>{current-story-file}</file>
39
+ <file>{current-epic-file} — ONE only!</file>
40
+ <file>{current-story-file} — ONE only!</file>
22
41
  </read>
23
- <skip reason="too large, epic/story has context">
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
- [ ] Epic 1: [Title]
36
- [ ] Story 1-1: [Title]
37
- [ ] Review Story 1-1
38
- [ ] Story 1-2: [Title]
39
- [ ] Review Story 1-2
40
- [ ] Review Epic 1
41
- [ ] Epic 2: [Title]
42
- [ ] Story 2-1: [Title]
43
- ...
44
- [ ] Sprint integration tests
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="mark-done">
62
- Mark epic as completed in sprint TODO
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
- Mark "Review Epic" as completed
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
- <step n="1">Run sprint integration tests (mark in TODO)</step>
88
- <step n="2">Set sprint status="done" in sprint-status.yaml</step>
89
- <step n="3">Clear sprint TODO list</step>
90
- <step n="4">Report completion with summary + metrics</step>
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
- <skip reason="too large, story has context">
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="3-delegate" title="Delegate to @coder">
61
- <action>Formulate task using template below</action>
62
- <action>Call @coder with full context</action>
63
- <rule>@coder writes code. Give direction, NOT solution.</rule>
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="4-verify" title="Verify & Mark Done">
67
- <action>Run tests</action>
68
- <action>Check "Done when" criteria</action>
69
- <action>Mark task in story file</action>
70
- <next>Next task or story complete</next>
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 | Focus | Status |
83
- |----|-------|------|-------|--------|
84
- | S{{E}}-01 | {{title}} | S/M/L | → Unit: `{{unit}}` | ⬜ |
85
- | S{{E}}-02 | {{title}} | S/M/L | → Unit: `{{unit}}` | ⬜ |
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
- **Notes:**
95
- - {{implementation_note}}
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