@comfanion/workflow 4.37.2 → 4.38.1-dev.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.
- package/package.json +1 -1
- package/src/build-info.json +2 -2
- package/src/opencode/FLOW.yaml +77 -0
- package/src/opencode/commands/dev-epic.md +19 -0
- package/src/opencode/commands/dev-sprint.md +19 -0
- package/src/opencode/commands/dev-story.md +11 -2
- package/src/opencode/commands/quick.md +5 -8
- package/src/opencode/config.yaml +47 -1
- package/src/opencode/opencode.json +1 -3
- package/src/opencode/plugins/README.md +32 -1
- package/src/opencode/plugins/custom-compaction.ts +302 -27
- package/src/opencode/skills/coding-standards/SKILL.md +79 -23
- package/src/opencode/skills/dev-epic/SKILL.md +110 -0
- package/src/opencode/skills/dev-sprint/SKILL.md +106 -0
- package/src/opencode/skills/dev-story/SKILL.md +115 -119
- package/src/opencode/skills/epic-writing/SKILL.md +2 -0
- package/src/opencode/skills/story-writing/SKILL.md +1 -1
|
@@ -44,6 +44,7 @@ interface SessionContext {
|
|
|
44
44
|
story: StoryContext | null
|
|
45
45
|
relevantFiles: string[]
|
|
46
46
|
activeAgent: string | null
|
|
47
|
+
activeCommand: string | null // /dev-story, /dev-epic, /dev-sprint
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
// Base files ALL agents need after compaction (to remember who they are)
|
|
@@ -53,20 +54,18 @@ const BASE_FILES = [
|
|
|
53
54
|
]
|
|
54
55
|
|
|
55
56
|
// Agent-specific file priorities (added to BASE_FILES)
|
|
57
|
+
// These are OPTIONAL files for context, not mandatory
|
|
56
58
|
const AGENT_FILES: Record<string, string[]> = {
|
|
57
59
|
dev: [
|
|
58
60
|
...BASE_FILES,
|
|
59
61
|
"docs/coding-standards/README.md",
|
|
60
62
|
"docs/coding-standards/patterns.md",
|
|
61
|
-
|
|
62
|
-
"docs/prd.md",
|
|
63
|
-
"docs/architecture.md",
|
|
63
|
+
// NO prd.md, NO architecture.md - too large, story has context
|
|
64
64
|
// story path added dynamically
|
|
65
65
|
],
|
|
66
66
|
coder: [
|
|
67
67
|
...BASE_FILES,
|
|
68
68
|
"docs/coding-standards/patterns.md",
|
|
69
|
-
"docs/coding-standards/testing.md",
|
|
70
69
|
],
|
|
71
70
|
architect: [
|
|
72
71
|
...BASE_FILES,
|
|
@@ -110,19 +109,19 @@ const DEFAULT_FILES = [
|
|
|
110
109
|
]
|
|
111
110
|
|
|
112
111
|
// Files agent MUST Read after compaction (commands generated)
|
|
112
|
+
// MINIMAL CONTEXT: ~70KB for dev, not 200KB+
|
|
113
|
+
// Note: coding-standards/README.md is standard path created by /coding-standards command
|
|
113
114
|
const MUST_READ_FILES: Record<string, string[]> = {
|
|
114
115
|
dev: [
|
|
115
116
|
"AGENTS.md",
|
|
116
117
|
"CLAUDE.md",
|
|
117
|
-
"docs/
|
|
118
|
-
|
|
119
|
-
// story path added dynamically
|
|
118
|
+
"docs/coding-standards/README.md", // if exists
|
|
119
|
+
// story/epic state path added dynamically
|
|
120
120
|
],
|
|
121
121
|
coder: [
|
|
122
122
|
"AGENTS.md",
|
|
123
123
|
"CLAUDE.md",
|
|
124
|
-
"docs/
|
|
125
|
-
"docs/architecture.md",
|
|
124
|
+
"docs/coding-standards/README.md",
|
|
126
125
|
],
|
|
127
126
|
architect: [
|
|
128
127
|
"AGENTS.md",
|
|
@@ -134,7 +133,6 @@ const MUST_READ_FILES: Record<string, string[]> = {
|
|
|
134
133
|
"AGENTS.md",
|
|
135
134
|
"CLAUDE.md",
|
|
136
135
|
"docs/prd.md",
|
|
137
|
-
"docs/architecture.md",
|
|
138
136
|
],
|
|
139
137
|
analyst: [
|
|
140
138
|
"AGENTS.md",
|
|
@@ -148,8 +146,6 @@ const MUST_READ_FILES: Record<string, string[]> = {
|
|
|
148
146
|
default: [
|
|
149
147
|
"AGENTS.md",
|
|
150
148
|
"CLAUDE.md",
|
|
151
|
-
"docs/prd.md",
|
|
152
|
-
"docs/architecture.md",
|
|
153
149
|
],
|
|
154
150
|
}
|
|
155
151
|
|
|
@@ -173,13 +169,28 @@ export const CustomCompactionPlugin: Plugin = async (ctx) => {
|
|
|
173
169
|
/**
|
|
174
170
|
* Generate Read commands that agent MUST execute after compaction
|
|
175
171
|
*/
|
|
176
|
-
function generateReadCommands(agent: string | null, story: StoryContext | null): string {
|
|
172
|
+
async function generateReadCommands(agent: string | null, story: StoryContext | null, activeCommand: string | null): Promise<string> {
|
|
177
173
|
const agentKey = (typeof agent === 'string' ? agent.toLowerCase() : null) || "default"
|
|
178
174
|
const filesToRead = [...(MUST_READ_FILES[agentKey] || MUST_READ_FILES.default)]
|
|
179
175
|
|
|
180
|
-
// For dev/coder: add
|
|
181
|
-
if ((agentKey === "dev" || agentKey === "coder") &&
|
|
182
|
-
|
|
176
|
+
// For dev/coder: add command file first
|
|
177
|
+
if ((agentKey === "dev" || agentKey === "coder") && activeCommand) {
|
|
178
|
+
const commandFile = activeCommand.replace("/", "") + ".md"
|
|
179
|
+
filesToRead.unshift(`.opencode/commands/${commandFile}`)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// For dev/coder: add epic state file if in epic workflow
|
|
183
|
+
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
|
+
}
|
|
189
|
+
|
|
190
|
+
// Then story file if active
|
|
191
|
+
if (story) {
|
|
192
|
+
filesToRead.unshift(story.path) // Story first!
|
|
193
|
+
}
|
|
183
194
|
}
|
|
184
195
|
|
|
185
196
|
const commands = filesToRead.map((f, i) => `${i + 1}. Read("${f}")`).join("\n")
|
|
@@ -203,8 +214,158 @@ DO NOT skip this step. DO NOT ask user what to do. Just read these files first.`
|
|
|
203
214
|
}
|
|
204
215
|
}
|
|
205
216
|
|
|
217
|
+
interface EpicState {
|
|
218
|
+
statePath: string
|
|
219
|
+
epicId: string
|
|
220
|
+
epicTitle: string
|
|
221
|
+
status: string
|
|
222
|
+
currentStoryIndex: number
|
|
223
|
+
totalStories: number
|
|
224
|
+
nextAction: string | null
|
|
225
|
+
nextStoryPath: string | null
|
|
226
|
+
completedCount: number
|
|
227
|
+
pendingCount: number
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function getActiveEpicState(): Promise<EpicState | null> {
|
|
231
|
+
try {
|
|
232
|
+
// Search for epic state files in all sprint folders
|
|
233
|
+
const sprintArtifactsPath = join(directory, "docs", "sprint-artifacts")
|
|
234
|
+
const entries = await readdir(sprintArtifactsPath)
|
|
235
|
+
|
|
236
|
+
for (const entry of entries) {
|
|
237
|
+
if (entry.startsWith("sprint-")) {
|
|
238
|
+
const statePath = join(sprintArtifactsPath, entry, ".sprint-state")
|
|
239
|
+
try {
|
|
240
|
+
const stateFiles = await readdir(statePath)
|
|
241
|
+
for (const stateFile of stateFiles) {
|
|
242
|
+
if (stateFile.endsWith("-state.yaml")) {
|
|
243
|
+
const fullPath = join(statePath, stateFile)
|
|
244
|
+
const content = await readFile(fullPath, "utf-8")
|
|
245
|
+
|
|
246
|
+
// Check if this epic is in-progress
|
|
247
|
+
if (content.includes("status: \"in-progress\"") || content.includes("status: in-progress")) {
|
|
248
|
+
// Parse epic state
|
|
249
|
+
const epicIdMatch = content.match(/epic_id:\s*["']?([^"'\n]+)["']?/i)
|
|
250
|
+
const epicTitleMatch = content.match(/epic_title:\s*["']?([^"'\n]+)["']?/i)
|
|
251
|
+
const statusMatch = content.match(/status:\s*["']?([^"'\n]+)["']?/i)
|
|
252
|
+
const currentIndexMatch = content.match(/current_story_index:\s*(\d+)/i)
|
|
253
|
+
const totalStoriesMatch = content.match(/total_stories:\s*(\d+)/i)
|
|
254
|
+
const nextActionMatch = content.match(/next_action:\s*["']?([^"'\n]+)["']?/i)
|
|
255
|
+
|
|
256
|
+
// Count completed/pending stories
|
|
257
|
+
const completedSection = content.match(/completed_stories:([\s\S]*?)(?=pending_stories:|$)/i)
|
|
258
|
+
const pendingSection = content.match(/pending_stories:([\s\S]*?)(?=\n\w+:|$)/i)
|
|
259
|
+
|
|
260
|
+
const completedCount = completedSection
|
|
261
|
+
? (completedSection[1].match(/- path:/g) || []).length
|
|
262
|
+
: 0
|
|
263
|
+
const pendingCount = pendingSection
|
|
264
|
+
? (pendingSection[1].match(/- path:/g) || []).length
|
|
265
|
+
: 0
|
|
266
|
+
|
|
267
|
+
// Extract next story path from next_action
|
|
268
|
+
let nextStoryPath: string | null = null
|
|
269
|
+
if (nextActionMatch) {
|
|
270
|
+
const actionText = nextActionMatch[1]
|
|
271
|
+
const storyFileMatch = actionText.match(/story-[\w-]+\.md/i)
|
|
272
|
+
if (storyFileMatch) {
|
|
273
|
+
// Find full path in pending_stories
|
|
274
|
+
const pathMatch = content.match(new RegExp(`path:\\s*["']?([^"'\\n]*${storyFileMatch[0]}[^"'\\n]*)["']?`, 'i'))
|
|
275
|
+
if (pathMatch) {
|
|
276
|
+
nextStoryPath = pathMatch[1]
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
statePath: fullPath.replace(directory + "/", ""),
|
|
283
|
+
epicId: epicIdMatch?.[1] || "unknown",
|
|
284
|
+
epicTitle: epicTitleMatch?.[1] || "Unknown Epic",
|
|
285
|
+
status: statusMatch?.[1] || "in-progress",
|
|
286
|
+
currentStoryIndex: currentIndexMatch ? parseInt(currentIndexMatch[1]) : 0,
|
|
287
|
+
totalStories: totalStoriesMatch ? parseInt(totalStoriesMatch[1]) : 0,
|
|
288
|
+
nextAction: nextActionMatch?.[1] || null,
|
|
289
|
+
nextStoryPath,
|
|
290
|
+
completedCount,
|
|
291
|
+
pendingCount
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
} catch {
|
|
297
|
+
// No .sprint-state folder in this sprint
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return null
|
|
302
|
+
} catch {
|
|
303
|
+
return null
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
206
307
|
async function getActiveStory(): Promise<StoryContext | null> {
|
|
207
308
|
try {
|
|
309
|
+
// First, try to find epic state file
|
|
310
|
+
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
|
+
}
|
|
353
|
+
|
|
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
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Fallback: try old sprint-status.yaml format
|
|
208
369
|
const sprintStatusPath = join(directory, "docs", "sprint-artifacts", "sprint-status.yaml")
|
|
209
370
|
const content = await readFile(sprintStatusPath, "utf-8")
|
|
210
371
|
|
|
@@ -300,21 +461,72 @@ DO NOT skip this step. DO NOT ask user what to do. Just read these files first.`
|
|
|
300
461
|
return relevantPaths
|
|
301
462
|
}
|
|
302
463
|
|
|
464
|
+
async function detectActiveCommand(todos: TaskStatus[], epicState: EpicState | null): Promise<string | null> {
|
|
465
|
+
// Detect command from TODO structure
|
|
466
|
+
if (todos.length === 0) return null
|
|
467
|
+
|
|
468
|
+
// Check if TODOs are epics (sprint mode)
|
|
469
|
+
const hasEpicTodos = todos.some(t => t.content.toLowerCase().includes("epic"))
|
|
470
|
+
if (hasEpicTodos) return "/dev-sprint"
|
|
471
|
+
|
|
472
|
+
// Check if TODOs are stories (epic mode)
|
|
473
|
+
const hasStoryTodos = todos.some(t => t.content.toLowerCase().includes("story"))
|
|
474
|
+
if (hasStoryTodos || epicState) return "/dev-epic"
|
|
475
|
+
|
|
476
|
+
// Regular story mode
|
|
477
|
+
return "/dev-story"
|
|
478
|
+
}
|
|
479
|
+
|
|
303
480
|
async function buildContext(agent: string | null): Promise<SessionContext> {
|
|
304
481
|
const [todos, story] = await Promise.all([
|
|
305
482
|
getTodoList(),
|
|
306
483
|
getActiveStory()
|
|
307
484
|
])
|
|
308
485
|
|
|
486
|
+
const epicState = await getActiveEpicState()
|
|
309
487
|
const relevantFiles = await getRelevantFiles(agent, story)
|
|
488
|
+
const activeCommand = await detectActiveCommand(todos, epicState)
|
|
310
489
|
|
|
311
|
-
return { todos, story, relevantFiles, activeAgent: agent }
|
|
490
|
+
return { todos, story, relevantFiles, activeAgent: agent, activeCommand }
|
|
312
491
|
}
|
|
313
492
|
|
|
314
|
-
function formatDevContext(ctx: SessionContext): string {
|
|
493
|
+
async function formatDevContext(ctx: SessionContext): Promise<string> {
|
|
315
494
|
const sections: string[] = []
|
|
316
495
|
|
|
317
|
-
if
|
|
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
|
|
504
|
+
|
|
505
|
+
sections.push(`## 🎯 Epic Workflow: ${epicState.epicTitle}
|
|
506
|
+
|
|
507
|
+
**Epic ID:** ${epicState.epicId}
|
|
508
|
+
**Epic State:** \`${epicState.statePath}\` ← READ THIS FIRST
|
|
509
|
+
**Progress:** ${progress}% (${epicState.completedCount}/${epicState.totalStories} stories)
|
|
510
|
+
|
|
511
|
+
### Next Action (DO THIS NOW)
|
|
512
|
+
\`\`\`
|
|
513
|
+
${epicState.nextAction || "All stories complete - run epic integration tests"}
|
|
514
|
+
\`\`\`
|
|
515
|
+
|
|
516
|
+
${epicState.nextStoryPath ? `**Next Story:** \`${epicState.nextStoryPath}\` ← READ THIS SECOND` : ""}
|
|
517
|
+
|
|
518
|
+
### Epic Progress
|
|
519
|
+
**Completed Stories:** ${epicState.completedCount}
|
|
520
|
+
**Pending Stories:** ${epicState.pendingCount}
|
|
521
|
+
**Current Index:** ${epicState.currentStoryIndex}
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
💡 **Note:** If this is part of /dev-sprint, after epic completes:
|
|
526
|
+
1. Update sprint-status.yaml (mark epic done)
|
|
527
|
+
2. Continue to next epic automatically`)
|
|
528
|
+
} else if (ctx.story) {
|
|
529
|
+
// Regular story mode
|
|
318
530
|
const s = ctx.story
|
|
319
531
|
const total = s.completedTasks.length + s.pendingTasks.length
|
|
320
532
|
const progress = total > 0 ? (s.completedTasks.length / total * 100).toFixed(0) : 0
|
|
@@ -433,13 +645,13 @@ ${ctx.relevantFiles.map(f => `- \`${f}\``).join("\n")}`)
|
|
|
433
645
|
return sections.join("\n\n---\n\n")
|
|
434
646
|
}
|
|
435
647
|
|
|
436
|
-
function formatContext(ctx: SessionContext): string {
|
|
648
|
+
async function formatContext(ctx: SessionContext): Promise<string> {
|
|
437
649
|
const agent = ctx.activeAgent?.toLowerCase()
|
|
438
650
|
|
|
439
651
|
switch (agent) {
|
|
440
652
|
case "dev":
|
|
441
653
|
case "coder":
|
|
442
|
-
return formatDevContext(ctx)
|
|
654
|
+
return await formatDevContext(ctx)
|
|
443
655
|
case "architect":
|
|
444
656
|
return formatArchitectContext(ctx)
|
|
445
657
|
case "pm":
|
|
@@ -453,12 +665,15 @@ ${ctx.relevantFiles.map(f => `- \`${f}\``).join("\n")}`)
|
|
|
453
665
|
}
|
|
454
666
|
}
|
|
455
667
|
|
|
456
|
-
function formatInstructions(ctx: SessionContext): string {
|
|
668
|
+
async function formatInstructions(ctx: SessionContext): Promise<string> {
|
|
457
669
|
const agent = ctx.activeAgent?.toLowerCase()
|
|
458
670
|
const hasInProgressTasks = ctx.todos.some(t => t.status === "in_progress")
|
|
459
671
|
const hasInProgressStory = ctx.story?.status === "in-progress"
|
|
672
|
+
|
|
673
|
+
// Check if we're in epic workflow
|
|
674
|
+
const epicState = await getActiveEpicState()
|
|
460
675
|
|
|
461
|
-
if (!hasInProgressTasks && !hasInProgressStory) {
|
|
676
|
+
if (!hasInProgressTasks && !hasInProgressStory && !epicState) {
|
|
462
677
|
return `## Status: COMPLETED ✅
|
|
463
678
|
|
|
464
679
|
Previous task was completed successfully.
|
|
@@ -469,7 +684,67 @@ Previous task was completed successfully.
|
|
|
469
684
|
3. Ask user for next task`
|
|
470
685
|
}
|
|
471
686
|
|
|
472
|
-
//
|
|
687
|
+
// Sprint workflow instructions
|
|
688
|
+
if (ctx.activeCommand === "/dev-sprint" && (agent === "dev" || agent === "coder")) {
|
|
689
|
+
const nextEpicTodo = ctx.todos.find(t => t.status === "in_progress" && t.content.toLowerCase().includes("epic"))
|
|
690
|
+
return `## Status: SPRINT IN PROGRESS 🔄
|
|
691
|
+
|
|
692
|
+
**Active Command:** ${ctx.activeCommand}
|
|
693
|
+
**Active Agent:** @${agent}
|
|
694
|
+
**Next Epic:** ${nextEpicTodo?.content || "check TODO"}
|
|
695
|
+
|
|
696
|
+
### Resume Protocol (AUTOMATIC - DO NOT ASK USER)
|
|
697
|
+
1. **Read command:** \`.opencode/commands/dev-sprint.md\`
|
|
698
|
+
2. **Read sprint-status.yaml**
|
|
699
|
+
3. **Find next epic** from TODO or sprint-status.yaml
|
|
700
|
+
4. **Execute epic** via /dev-epic workflow
|
|
701
|
+
5. **After epic done:**
|
|
702
|
+
- Update sprint-status.yaml (mark epic done)
|
|
703
|
+
- Update TODO (mark epic completed, next epic in_progress)
|
|
704
|
+
- Continue next epic automatically
|
|
705
|
+
|
|
706
|
+
### DO NOT
|
|
707
|
+
- Ask user what to do (TODO + sprint-status.yaml tell you)
|
|
708
|
+
- Re-read completed epics
|
|
709
|
+
- Wait for confirmation between epics (auto-continue)
|
|
710
|
+
|
|
711
|
+
### IMPORTANT
|
|
712
|
+
This is /dev-sprint autopilot mode. Execute epics sequentially until sprint done.`
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Epic workflow instructions
|
|
716
|
+
if ((ctx.activeCommand === "/dev-epic" || epicState) && (agent === "dev" || agent === "coder")) {
|
|
717
|
+
return `## Status: EPIC IN PROGRESS 🔄
|
|
718
|
+
|
|
719
|
+
**Active Command:** ${ctx.activeCommand || "/dev-epic"}
|
|
720
|
+
**Active Agent:** @${agent}
|
|
721
|
+
**Epic:** ${epicState?.epicTitle || "check epic state"}
|
|
722
|
+
**Next Action:** ${epicState?.nextAction || "check TODO"}
|
|
723
|
+
|
|
724
|
+
### Resume Protocol (AUTOMATIC - DO NOT ASK USER)
|
|
725
|
+
1. **Read command:** \`.opencode/commands/dev-epic.md\`
|
|
726
|
+
2. **Read epic state:** \`${epicState?.statePath || "find in .sprint-state/"}\`
|
|
727
|
+
3. **Read next story:** \`${epicState?.nextStoryPath || "check epic state"}\`
|
|
728
|
+
4. **Load skill:** \`.opencode/skills/dev-story/SKILL.md\`
|
|
729
|
+
5. **Execute story** following /dev-story workflow
|
|
730
|
+
6. **After story done:**
|
|
731
|
+
- Update epic state file (move story to completed)
|
|
732
|
+
- Update TODO (mark story completed, next story in_progress)
|
|
733
|
+
- Increment current_story_index
|
|
734
|
+
- Set next_action to next story
|
|
735
|
+
- Continue next story automatically
|
|
736
|
+
|
|
737
|
+
### DO NOT
|
|
738
|
+
- Ask user what to do (epic state + TODO tell you)
|
|
739
|
+
- Re-read completed stories
|
|
740
|
+
- Re-read epic file (info in state)
|
|
741
|
+
- Wait for confirmation between stories (auto-continue)
|
|
742
|
+
|
|
743
|
+
### IMPORTANT
|
|
744
|
+
This is /dev-epic autopilot mode. Execute stories sequentially until epic done.`
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Dev-specific instructions (regular story)
|
|
473
748
|
if ((agent === "dev" || agent === "coder") && ctx.story) {
|
|
474
749
|
return `## Status: IN PROGRESS 🔄
|
|
475
750
|
|
|
@@ -554,9 +829,9 @@ Previous task was completed successfully.
|
|
|
554
829
|
await log(directory, ` todos: ${ctx.todos.length}`)
|
|
555
830
|
await log(directory, ` relevantFiles: ${ctx.relevantFiles.length}`)
|
|
556
831
|
|
|
557
|
-
const context = formatContext(ctx)
|
|
558
|
-
const instructions = formatInstructions(ctx)
|
|
559
|
-
const readCommands = generateReadCommands(agent, ctx.story)
|
|
832
|
+
const context = await formatContext(ctx)
|
|
833
|
+
const instructions = await formatInstructions(ctx)
|
|
834
|
+
const readCommands = await generateReadCommands(agent, ctx.story, ctx.activeCommand)
|
|
560
835
|
|
|
561
836
|
// Agent identity reminder
|
|
562
837
|
const agentIdentity = agent
|
|
@@ -13,9 +13,26 @@ metadata:
|
|
|
13
13
|
|
|
14
14
|
You are a Senior Staff Engineer specializing in establishing coding standards. You create **modular documentation** - multiple focused files (1-10 files), each under 2000 lines.
|
|
15
15
|
|
|
16
|
-
## Core Principle:
|
|
16
|
+
## Core Principle: README.md is the Main File
|
|
17
17
|
|
|
18
|
-
**CRITICAL
|
|
18
|
+
**CRITICAL:** During development, agents read ONLY `README.md` from coding-standards (to save context ~65KB).
|
|
19
|
+
|
|
20
|
+
Therefore `README.md` MUST contain:
|
|
21
|
+
- **ALL critical rules** (not just links!)
|
|
22
|
+
- **Common patterns with examples**
|
|
23
|
+
- **Naming conventions**
|
|
24
|
+
- **Project structure overview**
|
|
25
|
+
- Links to other files for deep dive
|
|
26
|
+
|
|
27
|
+
Other files (testing.md, api.md, etc.) are for **deep dive only** - agents read them when story's "Required Reading" points to them.
|
|
28
|
+
|
|
29
|
+
## Size Guidelines
|
|
30
|
+
|
|
31
|
+
| File | Max Size | Purpose |
|
|
32
|
+
|------|----------|---------|
|
|
33
|
+
| README.md | 20-30KB | Main file, all critical rules |
|
|
34
|
+
| Other files | 10-20KB each | Deep dive on specific topics |
|
|
35
|
+
| Total | <2000 lines/file | Keep files readable |
|
|
19
36
|
|
|
20
37
|
## Documentation Structure
|
|
21
38
|
|
|
@@ -35,7 +52,7 @@ docs/coding-standards/
|
|
|
35
52
|
|
|
36
53
|
## File Templates
|
|
37
54
|
|
|
38
|
-
### README.md (
|
|
55
|
+
### README.md (Main File - Agents Read This!)
|
|
39
56
|
|
|
40
57
|
```markdown
|
|
41
58
|
# Coding Standards
|
|
@@ -44,33 +61,72 @@ docs/coding-standards/
|
|
|
44
61
|
**Tech Stack:** [languages, frameworks]
|
|
45
62
|
**Last Updated:** YYYY-MM-DD
|
|
46
63
|
|
|
47
|
-
##
|
|
64
|
+
## Project Structure
|
|
65
|
+
|
|
66
|
+
\`\`\`
|
|
67
|
+
project/
|
|
68
|
+
├── src/ # Source code
|
|
69
|
+
│ ├── modules/ # Business modules (domain, service, handler)
|
|
70
|
+
│ └── internal/ # Shared infrastructure
|
|
71
|
+
├── docs/ # Documentation
|
|
72
|
+
└── tests/ # Test files
|
|
73
|
+
\`\`\`
|
|
74
|
+
|
|
75
|
+
## Naming Conventions
|
|
76
|
+
|
|
77
|
+
### Files
|
|
78
|
+
- `snake_case` for files: `user_service.go`, `auth_handler.ts`
|
|
79
|
+
- Test files: `*_test.go`, `*.test.ts`
|
|
80
|
+
|
|
81
|
+
### Code
|
|
82
|
+
- Types/Classes: `PascalCase` - `UserService`, `AuthHandler`
|
|
83
|
+
- Functions: `camelCase` (TS) or `PascalCase` (Go exported)
|
|
84
|
+
- Variables: `camelCase` - `userId`, `isValid`
|
|
85
|
+
- Constants: `UPPER_SNAKE_CASE` - `MAX_RETRIES`
|
|
48
86
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
| Security | [security-standards.md](./security-standards.md) | Input validation |
|
|
57
|
-
| Libraries | [libraries.md](./libraries.md) | Approved list |
|
|
58
|
-
| Git | [git-workflow.md](./git-workflow.md) | Conventional commits |
|
|
87
|
+
## Common Patterns
|
|
88
|
+
|
|
89
|
+
### Service Pattern
|
|
90
|
+
[Example of service implementation in your language]
|
|
91
|
+
|
|
92
|
+
### Repository Pattern
|
|
93
|
+
[Example of repository implementation]
|
|
59
94
|
|
|
60
|
-
|
|
95
|
+
### Error Handling
|
|
96
|
+
[Example of error handling pattern]
|
|
61
97
|
|
|
62
|
-
|
|
63
|
-
2. [Second most important]
|
|
64
|
-
3. [Third most important]
|
|
98
|
+
## Critical Rules
|
|
65
99
|
|
|
66
|
-
|
|
100
|
+
1. **No business logic in handlers** - handlers only validate input and call services
|
|
101
|
+
2. **All errors must be wrapped** with context
|
|
102
|
+
3. **No hardcoded values** - use config or constants
|
|
103
|
+
4. **Tests required** for all business logic
|
|
67
104
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
105
|
+
## API Response Format
|
|
106
|
+
|
|
107
|
+
### Success
|
|
108
|
+
\`\`\`json
|
|
109
|
+
{ "data": { ... } }
|
|
110
|
+
\`\`\`
|
|
111
|
+
|
|
112
|
+
### Error
|
|
113
|
+
\`\`\`json
|
|
114
|
+
{ "error": { "code": "VALIDATION_ERROR", "message": "..." } }
|
|
115
|
+
\`\`\`
|
|
116
|
+
|
|
117
|
+
## Deep Dive Documents
|
|
118
|
+
|
|
119
|
+
| Topic | File | When to Read |
|
|
120
|
+
|-------|------|--------------|
|
|
121
|
+
| Testing | [testing.md](./testing.md) | Writing tests |
|
|
122
|
+
| API Design | [api.md](./api.md) | Creating endpoints |
|
|
123
|
+
| Database | [database.md](./database.md) | Schema changes |
|
|
124
|
+
| Security | [security.md](./security.md) | Auth, validation |
|
|
125
|
+
| Git | [git.md](./git.md) | Commits, PRs |
|
|
72
126
|
```
|
|
73
127
|
|
|
128
|
+
**Note:** This README should be 20-30KB with real examples, not placeholders.
|
|
129
|
+
|
|
74
130
|
### project-structure.md
|
|
75
131
|
|
|
76
132
|
```markdown
|