@champpaba/claude-agent-kit 3.2.0 → 3.4.0

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.
@@ -43,162 +43,163 @@ Please create the change with OpenSpec first
43
43
 
44
44
  **WHY:** Cross-session context helps understand blockers and infrastructure state before starting work.
45
45
 
46
- ```typescript
47
- const projectStatusPath = 'PROJECT_STATUS.yml'
46
+ 1. Check if `PROJECT_STATUS.yml` exists in project root
48
47
 
49
- if (fileExists(projectStatusPath)) {
50
- const projectStatus = parseYaml(Read(projectStatusPath))
48
+ **If file exists:**
51
49
 
52
- output(`\nšŸ“Š Project Context (from PROJECT_STATUS.yml)`)
50
+ 2. Read the file `PROJECT_STATUS.yml`
53
51
 
54
- // Show current focus
55
- if (projectStatus.current_focus?.description) {
56
- output(` Focus: ${projectStatus.current_focus.description}`)
57
- }
58
-
59
- // Check for blockers that might affect this change
60
- if (projectStatus.blockers?.length > 0) {
61
- const relevantBlockers = projectStatus.blockers.filter(b =>
62
- b.blocks?.some(blocked =>
63
- blocked.toLowerCase().includes(changeId.toLowerCase()) ||
64
- changeId.toLowerCase().includes(blocked.toLowerCase())
65
- )
66
- )
67
-
68
- if (relevantBlockers.length > 0) {
69
- output(`\n āš ļø Potential blockers for this change:`)
70
- relevantBlockers.forEach(b => {
71
- output(` - ${b.id}: ${b.description}`)
72
- })
73
- output(`\n Consider resolving blockers before starting.`)
74
- }
75
- }
76
-
77
- // Show infrastructure status summary
78
- if (projectStatus.infrastructure) {
79
- const downServices = Object.entries(projectStatus.infrastructure)
80
- .filter(([_, info]) => info.status === 'down' || info.status === 'degraded')
81
-
82
- if (downServices.length > 0) {
83
- output(`\n āš ļø Infrastructure issues:`)
84
- downServices.forEach(([service, info]) => {
85
- output(` - ${service}: ${info.status}${info.notes ? ` (${info.notes})` : ''}`)
86
- })
87
- }
88
- }
89
-
90
- // Check pending follow-ups that might affect this change (v2.1.6)
91
- if (projectStatus.pending_followups?.length > 0) {
92
- const proposalPath = `openspec/changes/${changeId}/proposal.md`
93
- const proposal = fileExists(proposalPath) ? Read(proposalPath).toLowerCase() : ''
94
-
95
- const relatedPending = projectStatus.pending_followups.filter(p => {
96
- const affects = p.affects || []
97
- return affects.some(pattern => {
98
- const patternLower = pattern.toLowerCase()
99
- return changeId.toLowerCase().includes(patternLower) ||
100
- proposal.includes(patternLower) ||
101
- (patternLower.includes('db') && proposal.includes('table')) ||
102
- (patternLower.includes('schema') && proposal.includes('model')) ||
103
- (patternLower.includes('migration') && proposal.includes('database'))
104
- })
105
- })
106
-
107
- if (relatedPending.length > 0) {
108
- output(`\n āš ļø Found related pending follow-ups:`)
109
- relatedPending.forEach(p => {
110
- output(` - "${p.item}" (from ${p.from_change})`)
111
- output(` Reason: ${p.reason}`)
112
- if (p.affects) output(` Affects: ${p.affects.join(', ')}`)
113
- })
114
-
115
- output(`\n This change may be affected by unresolved follow-ups.`)
116
- output(` Options:`)
117
- output(` 1. Continue anyway (risk: issues like schema sync)`)
118
- output(` 2. Address follow-up first (create separate proposal)`)
119
- output(` 3. Include follow-up in this change's scope`)
120
-
121
- const choice = await askUser(`\n How to proceed? (1/2/3)`)
122
-
123
- if (choice === '2') {
124
- output(`\n āŒ Setup paused. Create proposal for pending follow-up first.`)
125
- return
126
- } else if (choice === '3') {
127
- output(`\n ā„¹ļø Remember to include follow-up items in tasks.md`)
128
- } else {
129
- output(`\n āš ļø Continuing with caution. Monitor for related issues.`)
130
- }
131
- }
132
- }
133
-
134
- // Check stale status
135
- const lastUpdated = new Date(projectStatus.last_updated)
136
- const daysSinceUpdate = Math.floor((Date.now() - lastUpdated) / (1000 * 60 * 60 * 24))
137
- const staleThreshold = projectStatus._config?.stale_warning_days || 7
138
-
139
- if (daysSinceUpdate > staleThreshold) {
140
- output(`\n ā„¹ļø PROJECT_STATUS.yml last updated ${daysSinceUpdate} days ago.`)
141
- output(` Consider running /pstatus to refresh.`)
142
- }
143
-
144
- // Update active change
145
- if (projectStatus.current_focus?.active_change !== changeId) {
146
- output(`\n šŸ“ Update current_focus.active_change to "${changeId}"? (yes/no)`)
147
- const updateFocus = await askUser()
148
- if (updateFocus) {
149
- projectStatus.current_focus = projectStatus.current_focus || {}
150
- projectStatus.current_focus.active_change = changeId
151
- projectStatus.last_updated = new Date().toISOString().split('T')[0]
152
- Write(projectStatusPath, toYaml(projectStatus))
153
- output(` āœ… Updated active_change to "${changeId}"`)
154
- }
155
- }
156
-
157
- output(``) // Blank line
158
- }
52
+ 3. Output header:
159
53
  ```
54
+ šŸ“Š Project Context (from PROJECT_STATUS.yml)
55
+ ```
56
+
57
+ 4. **If `current_focus.description` exists:**
58
+ - Output: ` Focus: {description}`
59
+
60
+ 5. **Check for blockers that might affect this change:**
61
+ - Look at `blockers` array
62
+ - For each blocker, check if `blocks` field contains:
63
+ - The change-id (case-insensitive)
64
+ - OR any keyword from change-id
65
+ - OR vice versa
66
+
67
+ **If relevant blockers found:**
68
+ ```
69
+ āš ļø Potential blockers for this change:
70
+ - {blocker.id}: {blocker.description}
71
+ ... (for each relevant blocker)
72
+
73
+ Consider resolving blockers before starting.
74
+ ```
75
+
76
+ 6. **Check infrastructure status:**
77
+ - Look at `infrastructure` section
78
+ - Find services where `status` is 'down' or 'degraded'
79
+
80
+ **If degraded services found:**
81
+ ```
82
+ āš ļø Infrastructure issues:
83
+ - {service}: {status} ({notes if present})
84
+ ... (for each down/degraded service)
85
+ ```
86
+
87
+ 7. **Check pending follow-ups (v2.1.6):**
88
+ - Read `openspec/changes/{changeId}/proposal.md` (if exists)
89
+ - Look at `pending_followups` array
90
+ - For each pending item, check if `affects` field matches:
91
+ - Change-id contains pattern
92
+ - OR proposal contains pattern
93
+ - OR special database patterns (db→table, schema→model, migration→database)
94
+
95
+ **If related pending items found:**
96
+ ```
97
+ āš ļø Found related pending follow-ups:
98
+ - "{item}" (from {from_change})
99
+ Reason: {reason}
100
+ Affects: {affects list}
101
+ ... (for each related pending)
102
+
103
+ This change may be affected by unresolved follow-ups.
104
+ Options:
105
+ 1. Continue anyway (risk: issues like schema sync)
106
+ 2. Address follow-up first (create separate proposal)
107
+ 3. Include follow-up in this change's scope
108
+
109
+ How to proceed? (1/2/3)
110
+ ```
111
+
112
+ → Wait for user input
113
+
114
+ **If user chose 2:**
115
+ ```
116
+ āŒ Setup paused. Create proposal for pending follow-up first.
117
+ ```
118
+ → STOP execution, exit command
119
+
120
+ **If user chose 3:**
121
+ ```
122
+ ā„¹ļø Remember to include follow-up items in tasks.md
123
+ ```
124
+ → Continue
125
+
126
+ **If user chose 1 or other:**
127
+ ```
128
+ āš ļø Continuing with caution. Monitor for related issues.
129
+ ```
130
+ → Continue
131
+
132
+ 8. **Check if status is stale:**
133
+ - Calculate days since `last_updated`
134
+ - Compare with `_config.stale_warning_days` (default: 7)
135
+
136
+ **If days > threshold:**
137
+ ```
138
+ ā„¹ļø PROJECT_STATUS.yml last updated {days} days ago.
139
+ Consider running /pstatus to refresh.
140
+ ```
141
+
142
+ 9. **Update active change if needed:**
143
+ - Check if `current_focus.active_change` equals current changeId
144
+
145
+ **If NOT equal:**
146
+ - Ask user:
147
+ ```
148
+ šŸ“ Update current_focus.active_change to "{changeId}"? (yes/no)
149
+ ```
150
+
151
+ **If user approves:**
152
+ - Set `current_focus.active_change = changeId`
153
+ - Set `last_updated = today's date (YYYY-MM-DD)`
154
+ - Write updated YAML back to `PROJECT_STATUS.yml`
155
+ - Output: ` āœ… Updated active_change to "{changeId}"`
156
+
157
+ 10. Output blank line
158
+
159
+ **If file does not exist:**
160
+ - Skip to next step (no output needed)
160
161
 
161
162
  ### Step 1.6: Memory Context Query (v2.2.0 - claude-mem Integration)
162
163
 
163
164
  **WHY:** Query past work to leverage decisions, avoid repeating mistakes, and maintain consistency.
164
165
 
165
- ```typescript
166
- // Extract keywords from change-id and proposal title
167
- const changeKeywords = changeId.split('-').join(' ')
168
- const proposalPath = `openspec/changes/${changeId}/proposal.md`
169
- const proposalContent = fileExists(proposalPath) ? Read(proposalPath) : ''
170
- const proposalTitle = proposalContent.match(/^#\s+(.+)/m)?.[1] || changeId
171
-
172
- output(`\n🧠 Querying claude-mem for related past work...`)
166
+ 1. Extract keywords from change-id:
167
+ - Replace hyphens with spaces
168
+ - Example: `add-auth-system` → `add auth system`
173
169
 
174
- // Use mem-search skill to find related observations
175
- // The skill auto-invokes when asking about past work
176
- const queries = [
177
- `decisions about ${changeKeywords}`,
178
- `bugs related to ${changeKeywords}`,
179
- `implementations of ${changeKeywords}`
180
- ]
170
+ 2. Read `openspec/changes/{changeId}/proposal.md` (if exists)
171
+ - Extract first heading line (proposal title)
172
+ - If no file or no heading, use changeId as title
181
173
 
182
- // Claude will auto-invoke mem-search for these queries
183
- // Results are stored for inclusion in research-checklist.md
174
+ 3. Output:
175
+ ```
176
+ 🧠 Querying claude-mem for related past work...
177
+ ```
184
178
 
185
- let pastLearnings = []
179
+ 4. Query claude-mem using natural language questions:
180
+ - "decisions about {keywords}"
181
+ - "bugs related to {keywords}"
182
+ - "implementations of {keywords}"
186
183
 
187
- // Note: In practice, Main Claude asks these questions naturally
188
- // and mem-search skill returns relevant observations
184
+ **Note:** The mem-search skill will auto-invoke when you ask these questions naturally.
189
185
 
190
- output(` Searched for: ${changeKeywords}`)
191
- output(` (Results will be included in research-checklist.md if relevant)`)
192
- output(``)
186
+ 5. Store results in memory for later use
193
187
 
194
- // Store for later use in research-checklist.md generation
195
- // pastLearnings will be populated by mem-search results
188
+ 6. Output:
189
+ ```
190
+ Searched for: {keywords}
191
+ (Results will be included in pre-work-context.md if relevant)
196
192
  ```
197
193
 
198
- **Integration with research-checklist.md:**
194
+ 7. Output blank line
199
195
 
200
- When generating `research-checklist.md` (Step 2.6), include a "Past Learnings" section:
196
+ ---
197
+
198
+ **Integration with pre-work-context.md:**
201
199
 
200
+ When generating `pre-work-context.md` (Step 2.6.7), include a "Past Learnings" section:
201
+
202
+ **If relevant observations found:**
202
203
  ```markdown
203
204
  ## Past Learnings (from claude-mem)
204
205
 
@@ -206,15 +207,15 @@ When generating `research-checklist.md` (Step 2.6), include a "Past Learnings" s
206
207
 
207
208
  | ID | Type | Summary | Relevance |
208
209
  |----|------|---------|-----------|
209
- | #12345 | decision | Chose Drizzle over Prisma | HIGH |
210
- | #12340 | bugfix | Fixed N+1 query in user list | MEDIUM |
210
+ | #{id} | {type} | {summary} | {HIGH/MEDIUM/LOW} |
211
+ ... (for each observation)
211
212
 
212
213
  ### Key Takeaways:
213
- - Use Drizzle patterns established in #12345
214
- - Watch for N+1 queries (see #12340 for solution)
214
+ - {takeaway 1}
215
+ - {takeaway 2}
215
216
  ```
216
217
 
217
- If no relevant observations found:
218
+ **If no relevant observations found:**
218
219
  ```markdown
219
220
  ## Past Learnings (from claude-mem)
220
221
 
@@ -236,86 +237,98 @@ Read in order:
236
237
 
237
238
  > **Updated v2.0.0:** Validate design files + read page-plan.md if exists
238
239
 
239
- ```typescript
240
- // Detect if change involves UI/frontend work
241
- const tasksContent = Read('openspec/changes/{change-id}/tasks.md')
242
- const hasFrontend = tasksContent.toLowerCase().match(/(ui|component|page|frontend|design|responsive)/i)
243
-
244
- let tokens = null
245
- let pagePlan = null
246
- let pageType = 'generic'
247
-
248
- if (hasFrontend) {
249
- output(`\nšŸŽØ UI work detected - validating design system...`)
250
-
251
- const tokensPath = 'design-system/data.yaml' // v2.0 tokens
252
- const readmePath = 'design-system/README.md'
253
- const pagePlanPath = `openspec/changes/${changeId}/page-plan.md`
254
-
255
- const hasTokens = fileExists(tokensPath)
256
- const hasReadme = fileExists(readmePath)
257
- const hasPagePlan = fileExists(pagePlanPath)
258
-
259
- // ========== LOAD data.yaml (v2.0 structure) ==========
260
- if (hasTokens) {
261
- tokens = parseYaml(Read(tokensPath))
262
- output(`āœ… data.yaml Loaded:`)
263
- output(` - Style: ${tokens.style.name}`)
264
- output(` - Theme: ${tokens.theme.name}`)
265
- output(` - Animations: ${tokens.animations.enabled ? 'Enabled' : 'Disabled'}`)
266
- }
240
+ 1. Read `openspec/changes/{change-id}/tasks.md`
267
241
 
268
- // ========== LOAD page-plan.md (if exists) ==========
269
- if (hasPagePlan) {
270
- pagePlan = Read(pagePlanPath)
271
- output(`āœ… page-plan.md Found`)
272
-
273
- // Extract page type from page-plan.md
274
- const pageTypeMatch = pagePlan.match(/Page Type:\*\*\s*(.*)/i)
275
- if (pageTypeMatch) {
276
- pageType = pageTypeMatch[1].trim().toLowerCase()
277
- output(` - Page Type: ${pageType}`)
278
- }
279
- } else {
280
- output(`ā„¹ļø page-plan.md not found (optional)`)
281
- output(` → Run /pageplan first for better component planning`)
282
- }
242
+ 2. **Check if change involves UI/frontend work:**
243
+ - Search tasks.md (case-insensitive) for keywords:
244
+ - ui, component, page, frontend, design, responsive
245
+ - Store result as `hasFrontend`
283
246
 
284
- if (!hasTokens || !hasReadme) {
285
- warn(`
286
- āš ļø WARNING: UI work detected but design system incomplete!
247
+ **If hasFrontend is TRUE:**
287
248
 
288
- Found:
289
- ${hasReadme ? 'āœ…' : 'āŒ'} README.md (human-readable)
290
- ${hasTokens ? 'āœ…' : 'āŒ'} data.yaml
291
- ${hasPagePlan ? 'āœ…' : 'āŒ'} page-plan.md
292
-
293
- This may result in:
294
- - Inconsistent colors (random hex codes)
295
- - Arbitrary spacing (p-5, gap-7)
296
- - Duplicate components
297
-
298
- Recommendation:
299
- 1. Run: /designsetup
300
- 2. Run: /pageplan @prd.md (optional but recommended)
301
- 3. Then: /csetup ${changeId}
302
-
303
- Continue anyway? (yes/no)
304
- `)
305
-
306
- const answer = await askUser()
307
- if (answer === 'no') {
308
- return error('Setup cancelled. Run /designsetup first.')
309
- }
310
- } else {
311
- output(`āœ… Design System Ready`)
312
- output(` - README.md āœ“ (human-readable)`)
313
- output(` - data.yaml āœ“`)
314
- if (hasPagePlan) output(` - page-plan.md āœ“`)
315
- }
316
- }
249
+ 3. Output:
250
+ ```
251
+ šŸŽØ UI work detected - validating design system...
317
252
  ```
318
253
 
254
+ 4. Check if these files exist:
255
+ - `design-system/data.yaml`
256
+ - `design-system/README.md`
257
+ - `openspec/changes/{changeId}/page-plan.md`
258
+
259
+ 5. **If `data.yaml` exists:**
260
+ - Read and parse `design-system/data.yaml`
261
+ - Output:
262
+ ```
263
+ āœ… data.yaml Loaded:
264
+ - Style: {tokens.style.name}
265
+ - Theme: {tokens.theme.name}
266
+ - Animations: {Enabled/Disabled based on tokens.animations.enabled}
267
+ ```
268
+ - Store parsed tokens for later use
269
+
270
+ 6. **If `page-plan.md` exists:**
271
+ - Read `openspec/changes/{changeId}/page-plan.md`
272
+ - Output:
273
+ ```
274
+ āœ… page-plan.md Found
275
+ ```
276
+ - Search for line matching pattern: `Page Type:**` or `**Page Type:**`
277
+ - Extract page type value (trim whitespace, convert to lowercase)
278
+ - Output: ` - Page Type: {pageType}`
279
+ - Store page type for later use
280
+
281
+ **If `page-plan.md` does NOT exist:**
282
+ ```
283
+ ā„¹ļø page-plan.md not found (optional)
284
+ → Run /pageplan first for better component planning
285
+ ```
286
+
287
+ 7. **Check completeness:**
288
+
289
+ **If `data.yaml` OR `README.md` is missing:**
290
+ - Output warning:
291
+ ```
292
+ āš ļø WARNING: UI work detected but design system incomplete!
293
+
294
+ Found:
295
+ {āœ…/āŒ} README.md (human-readable)
296
+ {āœ…/āŒ} data.yaml
297
+ {āœ…/āŒ} page-plan.md
298
+
299
+ This may result in:
300
+ - Inconsistent colors (random hex codes)
301
+ - Arbitrary spacing (p-5, gap-7)
302
+ - Duplicate components
303
+
304
+ Recommendation:
305
+ 1. Run: /designsetup
306
+ 2. Run: /pageplan @prd.md (optional but recommended)
307
+ 3. Then: /csetup {changeId}
308
+
309
+ Continue anyway? (yes/no)
310
+ ```
311
+
312
+ → Wait for user input
313
+
314
+ **If user answered 'no':**
315
+ - Output: `Setup cancelled. Run /designsetup first.`
316
+ - STOP execution, exit command
317
+
318
+ **If user answered 'yes' or other:**
319
+ - Continue to next step
320
+
321
+ **If both `data.yaml` AND `README.md` exist:**
322
+ ```
323
+ āœ… Design System Ready
324
+ - README.md āœ“ (human-readable)
325
+ - data.yaml āœ“
326
+ - page-plan.md āœ“ (only if exists)
327
+ ```
328
+
329
+ **If hasFrontend is FALSE:**
330
+ - Skip all steps above, continue to Step 2.6
331
+
319
332
  ---
320
333
 
321
334
  ### Step 2.6: Generate Pre-Work Context (v3.2.0 - Consolidated)
@@ -626,237 +639,199 @@ Do NOT treat this as pseudocode. EXECUTE these instructions.
626
639
  > **NEW:** Verify chosen libraries support ALL spec requirements before proceeding
627
640
  > **WHY:** Prevents spec drift - discovering during implementation that library doesn't support requirements
628
641
 
629
- ```typescript
630
- output(`\nšŸ” Validating Library Capabilities...`)
631
-
632
- // Initialize variables at function scope
633
- let detectedLibraries = []
634
- let specRequirements = []
635
- let capabilityGaps = []
636
- let customImplementationRequired = []
637
-
638
- // 1. Extract spec requirements from design.md
639
- const designPath = `openspec/changes/${changeId}/design.md`
640
- if (!fileExists(designPath)) {
641
- output(` āš ļø No design.md found - skipping library validation`)
642
- } else {
643
- const designContent = Read(designPath)
644
-
645
- // 2. Find library mentions in spec
646
- const libraryPatterns = {
647
- 'better-auth': {
648
- patterns: ['better-auth', 'betterauth'],
649
- context7Id: null, // No Context7 mapping yet
650
- knownLimitations: [
651
- { feature: 'refresh token rotation', supported: false },
652
- { feature: 'redis session storage', supported: false },
653
- { feature: 'jwt plugin', supported: true },
654
- { feature: 'bearer plugin', supported: true },
655
- { feature: 'session-based auth', supported: true }
656
- ]
657
- },
658
- 'nextauth': {
659
- patterns: ['next-auth', 'nextauth', 'authjs'],
660
- context7Id: '/nextauthjs/next-auth',
661
- knownLimitations: []
662
- },
663
- 'lucia': {
664
- patterns: ['lucia', 'lucia-auth'],
665
- context7Id: '/lucia-auth/lucia',
666
- knownLimitations: []
667
- },
668
- 'prisma': {
669
- patterns: ['prisma'],
670
- context7Id: '/prisma/prisma',
671
- knownLimitations: []
672
- },
673
- 'drizzle': {
674
- patterns: ['drizzle'],
675
- context7Id: '/drizzle-team/drizzle-orm',
676
- knownLimitations: []
677
- }
678
- }
642
+ 1. Output:
643
+ ```
644
+ šŸ” Validating Library Capabilities...
645
+ ```
679
646
 
680
- // 3. Detect which libraries are mentioned
681
- for (const [libName, config] of Object.entries(libraryPatterns)) {
682
- if (config.patterns.some(p => designContent.toLowerCase().includes(p))) {
683
- detectedLibraries.push({ name: libName, ...config })
684
- }
685
- }
647
+ 2. Check if `openspec/changes/{changeId}/design.md` exists
686
648
 
687
- if (detectedLibraries.length > 0) {
688
- output(`\nšŸ“š Libraries in Spec:`)
689
- detectedLibraries.forEach(lib => output(` - ${lib.name}`))
690
-
691
- // 4. Extract requirements from design.md
692
- // Look for patterns like: "JWT 15min", "refresh token", "rotation"
693
- const requirementPatterns = [
694
- { name: 'JWT access token', pattern: /jwt.*(?:access|token).*(\d+\s*min)/i },
695
- { name: 'Refresh token', pattern: /refresh\s*token/i },
696
- { name: 'Token rotation', pattern: /(?:token\s*)?rotation|rotate/i },
697
- { name: 'Redis session', pattern: /redis.*session|session.*redis/i },
698
- { name: 'Bearer token', pattern: /bearer\s*(?:token|auth)/i },
699
- { name: 'OAuth providers', pattern: /oauth|google|github|social\s*login/i },
700
- { name: 'Rate limiting', pattern: /rate\s*limit/i },
701
- { name: 'Account lockout', pattern: /lockout|lock\s*account/i }
702
- ]
703
-
704
- for (const rp of requirementPatterns) {
705
- if (rp.pattern.test(designContent)) {
706
- specRequirements.push(rp.name)
707
- }
708
- }
649
+ **If design.md does NOT exist:**
650
+ ```
651
+ āš ļø No design.md found - skipping library validation
652
+ ```
653
+ → Skip to Step 3
654
+
655
+ **If design.md exists:**
656
+
657
+ 3. Read `openspec/changes/{changeId}/design.md`
658
+
659
+ 4. **Detect libraries mentioned in design.md:**
660
+
661
+ Search for these library patterns (case-insensitive):
662
+
663
+ | Library | Search Patterns | Context7 ID | Known Limitations |
664
+ |---------|----------------|-------------|-------------------|
665
+ | better-auth | better-auth, betterauth | (none) | āŒ refresh token rotation, āŒ redis session, āœ… jwt plugin, āœ… bearer plugin, āœ… session-based auth |
666
+ | nextauth | next-auth, nextauth, authjs | /nextauthjs/next-auth | (none) |
667
+ | lucia | lucia, lucia-auth | /lucia-auth/lucia | (none) |
668
+ | prisma | prisma | /prisma/prisma | (none) |
669
+ | drizzle | drizzle | /drizzle-team/drizzle-orm | (none) |
670
+
671
+ **If libraries found:**
672
+ ```
673
+ šŸ“š Libraries in Spec:
674
+ - {library1}
675
+ - {library2}
676
+ ...
677
+ ```
678
+
679
+ 5. **Extract requirements from design.md:**
680
+
681
+ Search for these requirement patterns:
682
+
683
+ | Requirement | Search Pattern |
684
+ |-------------|----------------|
685
+ | JWT access token | jwt + (access OR token) + (number + min) |
686
+ | Refresh token | refresh token |
687
+ | Token rotation | rotation OR rotate |
688
+ | Redis session | redis + session (any order) |
689
+ | Bearer token | bearer + (token OR auth) |
690
+ | OAuth providers | oauth OR google OR github OR social login |
691
+ | Rate limiting | rate limit |
692
+ | Account lockout | lockout OR lock account |
693
+
694
+ **If requirements found:**
695
+ ```
696
+ šŸ“‹ Spec Requirements Found:
697
+ - {requirement1}
698
+ - {requirement2}
699
+ ...
700
+ ```
701
+
702
+ 6. **Check each library's capabilities:**
703
+
704
+ For each detected library:
705
+ ```
706
+ šŸ” Checking {library} capabilities...
707
+ ```
708
+
709
+ For each requirement:
710
+ - Check known limitations database
711
+ - Match requirement with limitation feature (partial match OK)
712
+
713
+ **If known limitation says NOT supported:**
714
+ ```
715
+ āŒ {requirement} - NOT SUPPORTED
716
+ ```
717
+ → Add to capability gaps list
718
+
719
+ **If known limitation says supported:**
720
+ ```
721
+ āœ… {requirement} - Supported
722
+ ```
723
+
724
+ **If unknown (not in limitations database):**
725
+ - **If library has Context7 ID:**
726
+ ```
727
+ šŸ” {requirement} - Checking Context7...
728
+ āš ļø {requirement} - Verify manually
729
+ ```
730
+ - **If NO Context7 ID:**
731
+ ```
732
+ āš ļø {requirement} - Verify manually (no Context7 mapping)
733
+ ```
734
+
735
+ 7. **Report capability gaps (if any):**
736
+
737
+ **If gaps found:**
738
+ ```
739
+ āš ļø Library Capability Gaps Detected!
740
+
741
+ The following spec requirements are NOT supported by chosen libraries:
742
+
743
+ {library1}:
744
+ - {requirement1}
745
+ - {requirement2}
746
+ {library2}:
747
+ - {requirement3}
748
+
749
+ This will cause spec drift during implementation!
750
+
751
+ Options:
752
+ A) Change library - Use a library that supports these features
753
+ B) Downgrade spec - Remove unsupported requirements (must document trade-off)
754
+ C) Custom implementation - Build missing features on top of library
755
+ D) Continue anyway - Proceed and let agent handle at implementation time
756
+ ```
757
+
758
+ → Ask user: "How would you like to handle the capability gaps?"
759
+
760
+ **If user chose A (Change library):**
761
+ ```
762
+ šŸ“ Suggested alternative libraries:
763
+ ```
764
+ - If better-auth has gaps:
765
+ ```
766
+ Instead of better-auth, consider:
767
+ - lucia-auth (supports custom session storage)
768
+ - NextAuth.js (supports refresh token rotation with JWT strategy)
769
+ - Custom implementation with jose + Redis
770
+
771
+ Please update design.md with new library choice and re-run /csetup.
772
+ ```
773
+ → STOP execution, exit command
774
+
775
+ **If user chose B (Downgrade spec):**
776
+ ```
777
+ šŸ“ Update design.md to remove unsupported requirements:
778
+
779
+ ```markdown
780
+ ### D{n}: Library Capability Alignment
781
+
782
+ **Changed requirements to match {library} capabilities:**
783
+
784
+ - ~~{requirement1}~~ → Use {library}'s default approach instead
785
+ - ~~{requirement2}~~ → Use {library}'s default approach instead
786
+
787
+ **Reason:** Library limitation
788
+ **Trade-off:** {requirement1}, {requirement2} not available
789
+ **Date:** {today's date YYYY-MM-DD}
790
+ ```
791
+
792
+ Please update design.md and re-run /csetup.
793
+ ```
794
+ → STOP execution, exit command
795
+
796
+ **If user chose C (Custom implementation):**
797
+ ```
798
+ šŸ“ Custom implementation notes for agents:
799
+
800
+ Add to context.md:
801
+ ```markdown
802
+ ## Custom Implementation Required
803
+
804
+ The following features need custom implementation:
805
+ - {requirement1} (not supported by {library1})
806
+ - {requirement2} (not supported by {library2})
807
+
808
+ Agents should implement these on top of the base library.
809
+ ```
810
+ ```
811
+ → Store gaps for context.md generation (Step 7)
812
+ → Continue to Step 3
813
+
814
+ **If user chose D (Continue anyway):**
815
+ → Store gaps for agent awareness
816
+ → Continue to Step 3
817
+
818
+ **If NO gaps found:**
819
+ ```
820
+ āœ… All spec requirements supported by chosen libraries
821
+ ```
822
+
823
+ **If no libraries detected:**
824
+ ```
825
+ ā„¹ļø No specific libraries detected in spec
826
+ ```
709
827
 
710
- if (specRequirements.length > 0) {
711
- output(`\nšŸ“‹ Spec Requirements Found:`)
712
- specRequirements.forEach(r => output(` - ${r}`))
713
-
714
- // 5. Check each library's capability
715
- for (const lib of detectedLibraries) {
716
- output(`\nšŸ” Checking ${lib.name} capabilities...`)
717
-
718
- for (const req of specRequirements) {
719
- // Check known limitations first
720
- const known = lib.knownLimitations.find(l =>
721
- req.toLowerCase().includes(l.feature.toLowerCase()) ||
722
- l.feature.toLowerCase().includes(req.toLowerCase())
723
- )
724
-
725
- if (known && !known.supported) {
726
- output(` āŒ ${req} - NOT SUPPORTED`)
727
- capabilityGaps.push({
728
- library: lib.name,
729
- requirement: req,
730
- supported: false,
731
- note: `${lib.name} does not have built-in support for ${req}`
732
- })
733
- } else if (known && known.supported) {
734
- output(` āœ… ${req} - Supported`)
735
- } else {
736
- // Unknown - query Context7 if available
737
- if (lib.context7Id) {
738
- output(` šŸ” ${req} - Checking Context7...`)
739
- // Note: In actual implementation, this would call Context7
740
- // For now, mark as unknown
741
- output(` āš ļø ${req} - Verify manually`)
742
- } else {
743
- output(` āš ļø ${req} - Verify manually (no Context7 mapping)`)
744
- }
745
- }
746
- }
747
- }
748
-
749
- // 6. Report gaps if any
750
- if (capabilityGaps.length > 0) {
751
- output(`\nāš ļø Library Capability Gaps Detected!`)
752
- output(``)
753
- output(`The following spec requirements are NOT supported by chosen libraries:`)
754
- output(``)
755
-
756
- // Group by library
757
- const byLibrary = {}
758
- capabilityGaps.forEach(g => {
759
- if (!byLibrary[g.library]) byLibrary[g.library] = []
760
- byLibrary[g.library].push(g.requirement)
761
- })
762
-
763
- for (const [library, reqs] of Object.entries(byLibrary)) {
764
- output(` ${library}:`)
765
- reqs.forEach(r => output(` - ${r}`))
766
- }
767
-
768
- output(``)
769
- output(`This will cause spec drift during implementation!`)
770
- output(``)
771
- output(`Options:`)
772
- output(` A) Change library - Use a library that supports these features`)
773
- output(` B) Downgrade spec - Remove unsupported requirements (must document trade-off)`)
774
- output(` C) Custom implementation - Build missing features on top of library`)
775
- output(` D) Continue anyway - Proceed and let agent handle at implementation time`)
776
- output(``)
777
-
778
- const decision = await askUserQuestion({
779
- questions: [{
780
- question: 'How would you like to handle the capability gaps?',
781
- header: 'Lib Gaps',
782
- options: [
783
- { label: 'A) Change library', description: 'Switch to a library that supports requirements' },
784
- { label: 'B) Downgrade spec', description: 'Update design.md to use what library supports' },
785
- { label: 'C) Custom implementation', description: 'Build on top of library (more work)' },
786
- { label: 'D) Continue anyway', description: 'Let agent handle during implementation' }
787
- ],
788
- multiSelect: false
789
- }]
790
- })
791
-
792
- if (decision.includes('A')) {
793
- output(`\nšŸ“ Suggested alternative libraries:`)
794
- for (const [library, reqs] of Object.entries(byLibrary)) {
795
- if (library === 'better-auth') {
796
- output(` Instead of ${library}, consider:`)
797
- output(` - lucia-auth (supports custom session storage)`)
798
- output(` - NextAuth.js (supports refresh token rotation with JWT strategy)`)
799
- output(` - Custom implementation with jose + Redis`)
800
- }
801
- }
802
- output(``)
803
- output(`Please update design.md with new library choice and re-run /csetup.`)
804
- return
805
- } else if (decision.includes('B')) {
806
- output(`\nšŸ“ Update design.md to remove unsupported requirements:`)
807
- output(``)
808
- output(`\`\`\`markdown`)
809
- output(`### D{n}: Library Capability Alignment`)
810
- output(``)
811
- output(`**Changed requirements to match ${Object.keys(byLibrary).join(', ')} capabilities:**`)
812
- output(``)
813
- for (const gap of capabilityGaps) {
814
- output(`- ~~${gap.requirement}~~ → Use ${gap.library}'s default approach instead`)
815
- }
816
- output(``)
817
- output(`**Reason:** Library limitation`)
818
- output(`**Trade-off:** ${capabilityGaps.map(g => g.requirement).join(', ')} not available`)
819
- output(`**Date:** ${new Date().toISOString().split('T')[0]}`)
820
- output(`\`\`\``)
821
- output(``)
822
- output(`Please update design.md and re-run /csetup.`)
823
- return
824
- } else if (decision.includes('C')) {
825
- output(`\nšŸ“ Custom implementation notes for agents:`)
826
- output(``)
827
- output(`Add to context.md:`)
828
- output(`\`\`\`markdown`)
829
- output(`## Custom Implementation Required`)
830
- output(``)
831
- output(`The following features need custom implementation:`)
832
- for (const gap of capabilityGaps) {
833
- output(`- ${gap.requirement} (not supported by ${gap.library})`)
834
- }
835
- output(``)
836
- output(`Agents should implement these on top of the base library.`)
837
- output(`\`\`\``)
838
-
839
- // Store for context.md generation
840
- customImplementationRequired = capabilityGaps
841
- }
842
- // If D, continue with gaps logged for agent awareness
843
- } else {
844
- output(`\nāœ… All spec requirements supported by chosen libraries`)
845
- }
846
- }
847
- } else {
848
- output(` ā„¹ļø No specific libraries detected in spec`)
849
- }
850
- }
828
+ 8. Store capability analysis for later use:
829
+ - Detected libraries
830
+ - Spec requirements
831
+ - Capability gaps
832
+ - Custom implementation needs
851
833
 
852
- // Store capability analysis (variables declared at function scope above)
853
- const capabilityAnalysis = {
854
- libraries: detectedLibraries,
855
- requirements: specRequirements,
856
- gaps: capabilityGaps,
857
- customRequired: customImplementationRequired
858
- }
859
- ```
834
+ ---
860
835
 
861
836
 
862
837
  ---
@@ -866,161 +841,138 @@ const capabilityAnalysis = {
866
841
  > **NEW in v2.0:** No templates, no keyword matching. AI analyzes tasks and makes decisions.
867
842
  > **See:** `.claude/lib/task-analyzer.md` for complete analysis logic
868
843
 
869
- ```typescript
870
- const tasksContent = Read(`openspec/changes/${changeId}/tasks.md`)
871
-
872
- output(`\nšŸ“Š Task Analyzer v2.0 (Template-Free)...`)
873
-
874
- // ========== 3.1 Parse ALL Tasks ==========
875
- // Extract EVERY task from tasks.md - nothing is filtered out
876
- const allTasks = parseAllTasks(tasksContent)
877
- output(` Found: ${allTasks.length} tasks from tasks.md`)
878
-
879
- // ========== 3.2 AI-Driven Analysis ==========
880
- // Claude analyzes each task and decides:
881
- // - complexity (1-10)
882
- // - risk (LOW/MEDIUM/HIGH)
883
- // - agent (based on context, NOT keywords)
884
- // - dependencies (blocked_by, blocks)
885
- // - needsIncremental (boolean)
886
-
887
- const analyzedTasks = []
888
-
889
- output(`\nšŸ” Analyzing each task...`)
844
+ 1. Read `openspec/changes/${changeId}/tasks.md`
890
845
 
891
- for (const task of allTasks) {
892
- // AI reads the task description and context, then decides:
893
-
894
- const analysis = {
895
- // Complexity: How many operations? Multiple systems? Business logic?
896
- complexity: /* AI determines 1-10 based on task scope */,
897
-
898
- // Risk: What if this fails? Security/money/data involved?
899
- risk: /* AI determines LOW/MEDIUM/HIGH */,
900
-
901
- // Agent: Read the full context and decide which agent
902
- // DO NOT use keyword matching - understand the task
903
- agent: /* AI decides: uxui-frontend, backend, database, frontend, test-debug, integration */,
904
- agentReason: /* Brief explanation why this agent */,
905
-
906
- // Dependencies: What must complete first? What's waiting for this?
907
- dependencies: {
908
- blockedBy: /* AI identifies blocking tasks */,
909
- blocks: /* AI identifies tasks this blocks */,
910
- canParallelize: /* Tasks with no shared dependencies */
911
- },
912
-
913
- // Incremental: Does this need milestone-based execution?
914
- // YES if: batch processing, external API, data transformation,
915
- // multiple methods, complex form, HIGH risk, complexity >= 7
916
- needsIncremental: /* AI determines based on task nature */
917
- }
918
-
919
- analyzedTasks.push({ ...task, ...analysis })
920
- }
921
-
922
- // Report analysis
923
- output(`\nšŸ“Š Analysis Results:`)
924
- output(` Complexity: avg ${avgComplexity}/10`)
925
- output(` Risk: ${highRiskCount} HIGH, ${mediumRiskCount} MEDIUM, ${lowRiskCount} LOW`)
926
- output(` Agents: ${agentBreakdown}`)
927
-
928
- // ========== 3.3 Auto-Add Best Practices ==========
929
- // No warnings - just add what's needed automatically
930
-
931
- const additions = []
932
-
933
- for (const task of analyzedTasks) {
934
- // Rule 1: HIGH Risk → Add checkpoint
935
- if (task.risk === 'HIGH') {
936
- additions.push({
937
- id: `${task.id}.verify`,
938
- description: `Checkpoint: Verify ${task.description} before proceeding`,
939
- type: 'verification',
940
- autoAdded: true,
941
- reason: 'HIGH risk task requires verification checkpoint',
942
- phase: task.phase
943
- })
944
- }
945
-
946
- // Rule 2: External API → Add error handling
947
- if (task.hasExternalAPI) {
948
- if (!hasRelatedTask(allTasks, 'error handling')) {
949
- additions.push({
950
- id: `${task.id}.errors`,
951
- description: `Add error handling for external API`,
952
- type: 'implementation',
953
- autoAdded: true,
954
- reason: 'External APIs require error handling',
955
- phase: task.phase
956
- })
957
- }
958
- }
959
-
960
- // Rule 3: Security-Critical → Add security review
961
- if (task.isSecurityCritical) {
962
- additions.push({
963
- id: `${task.id}.security`,
964
- description: `Security review: ${task.description}`,
965
- type: 'verification',
966
- autoAdded: true,
967
- reason: 'Security-critical tasks require review',
968
- phase: task.phase
969
- })
970
- }
971
-
972
- // Rule 4: Database Changes → Add migration safety
973
- if (task.involvesDatabaseChange) {
974
- additions.push({
975
- id: `${task.id}.backup`,
976
- description: `Backup affected tables before ${task.description}`,
977
- type: 'safety',
978
- autoAdded: true,
979
- reason: 'Database changes require backup',
980
- phase: task.phase
981
- })
982
- }
983
- }
984
-
985
- output(` Auto-added: ${additions.length} best practice tasks`)
986
-
987
- // ========== 3.4 Generate Incremental Milestones ==========
988
- // For tasks that need milestone-based execution
989
-
990
- for (const task of analyzedTasks) {
991
- if (task.needsIncremental) {
992
- // AI generates appropriate milestones based on task type:
993
- // - Repository/Service: method-by-method
994
- // - External API: mock → single → errors → scale
995
- // - Batch Processing: 1 → 5 → 20 → 100
996
- // - Complex Form: architecture → e2e → all fields
997
-
998
- task.milestones = generateMilestones(task)
999
- }
1000
- }
1001
-
1002
- const incrementalCount = analyzedTasks.filter(t => t.milestones).length
1003
- const totalMilestones = analyzedTasks.reduce((sum, t) => sum + (t.milestones?.length || 0), 0)
1004
- output(` Incremental: ${incrementalCount} tasks with ${totalMilestones} milestones`)
1005
-
1006
- // ========== 3.5 Sort by Priority ==========
1007
- // Respect original phase order, then sort within phases
846
+ 2. Output:
847
+ ```
848
+ šŸ“Š Task Analyzer v2.0 (Template-Free)...
849
+ ```
1008
850
 
1009
- const sortedTasks = sortTasks([...analyzedTasks, ...additions])
851
+ 3. **Parse ALL tasks from tasks.md:**
852
+ - Extract EVERY checkbox item
853
+ - Pattern: `- [ ] {id} {description}`
854
+ - Nothing is filtered out
855
+ - Output: ` Found: {count} tasks from tasks.md`
1010
856
 
1011
- // Sorting rules:
1012
- // 1. Preserve original phase order from tasks.md
1013
- // 2. Within each phase:
1014
- // a. Dependencies first (no blockers)
1015
- // b. HIGH risk early (fail fast)
1016
- // c. Foundation before features
1017
- // d. Lower complexity first (quick wins)
857
+ 4. Output:
858
+ ```
859
+ šŸ” Analyzing each task...
860
+ ```
1018
861
 
1019
- output(`\nāœ… Task Analysis Complete`)
1020
- output(` Total: ${allTasks.length} original + ${additions.length} auto-added = ${sortedTasks.length} tasks`)
862
+ 5. **AI-Driven Analysis - For EACH task, determine:**
863
+
864
+ **a) Complexity (1-10):**
865
+ - Consider: number of operations, systems involved, business logic
866
+ - 1-3: Simple CRUD
867
+ - 4-6: Multiple operations, some logic
868
+ - 7-8: Complex logic, multiple systems
869
+ - 9-10: High complexity, many dependencies
870
+
871
+ **b) Risk (LOW/MEDIUM/HIGH):**
872
+ - Consider: What happens if this fails?
873
+ - HIGH: Security, money, data loss
874
+ - MEDIUM: User experience, performance
875
+ - LOW: UI tweaks, minor features
876
+
877
+ **c) Agent Assignment:**
878
+ - **DO NOT use keyword matching**
879
+ - Read full task description and context
880
+ - Decide which agent: uxui-frontend, backend, database, frontend, test-debug, integration
881
+ - Write brief reason why this agent
882
+
883
+ **d) Dependencies:**
884
+ - Identify which tasks must complete first (blockedBy)
885
+ - Identify which tasks depend on this (blocks)
886
+ - Identify tasks that can run in parallel (no shared dependencies)
887
+
888
+ **e) Incremental Testing Needed:**
889
+ - YES if ANY of these:
890
+ - Batch processing
891
+ - External API integration
892
+ - Data transformation
893
+ - Multiple methods to implement
894
+ - Complex form
895
+ - Risk = HIGH
896
+ - Complexity >= 7
897
+
898
+ Store all analysis results with each task.
899
+
900
+ 6. **Calculate and output analysis summary:**
901
+ - Calculate average complexity
902
+ - Count tasks by risk level
903
+ - Count tasks by agent
904
+
905
+ Output:
906
+ ```
907
+ šŸ“Š Analysis Results:
908
+ Complexity: avg {average}/10
909
+ Risk: {HIGH count} HIGH, {MEDIUM count} MEDIUM, {LOW count} LOW
910
+ Agents: {agent1} ({count1}), {agent2} ({count2}), ...
911
+ ```
912
+
913
+ 7. **Auto-Add Best Practice Tasks:**
914
+
915
+ For each analyzed task, apply these rules:
916
+
917
+ **Rule 1: HIGH Risk → Add checkpoint**
918
+ - If risk = HIGH
919
+ - Add new task: `{task.id}.verify - Checkpoint: Verify {description} before proceeding`
920
+ - Mark as autoAdded, type: verification
921
+
922
+ **Rule 2: External API → Add error handling**
923
+ - If task involves external API
924
+ - Check if error handling task already exists
925
+ - If not, add: `{task.id}.errors - Add error handling for external API`
926
+ - Mark as autoAdded, type: implementation
927
+
928
+ **Rule 3: Security-Critical → Add security review**
929
+ - If task is security-critical (auth, payment, data access)
930
+ - Add: `{task.id}.security - Security review: {description}`
931
+ - Mark as autoAdded, type: verification
932
+
933
+ **Rule 4: Database Changes → Add migration safety**
934
+ - If task involves database schema changes
935
+ - Add: `{task.id}.backup - Backup affected tables before {description}`
936
+ - Mark as autoAdded, type: safety
937
+
938
+ Output: ` Auto-added: {count} best practice tasks`
939
+
940
+ 8. **Generate Incremental Milestones:**
941
+
942
+ For each task where needsIncremental = true:
943
+ - Determine task type (Repository/API/Batch/Form)
944
+ - Generate appropriate milestones:
945
+ - **Repository/Service:** method-by-method implementation
946
+ - **External API:** mock → single → errors → scale
947
+ - **Batch Processing:** 1 record → 5 → 20 → 100
948
+ - **Complex Form:** architecture → e2e → all fields
949
+
950
+ Count totals and output:
951
+ ```
952
+ Incremental: {count} tasks with {total milestones} milestones
953
+ ```
954
+
955
+ 9. **Sort Tasks by Priority:**
956
+
957
+ Combine analyzed tasks + auto-added tasks
958
+
959
+ Sort using these rules:
960
+ 1. Preserve original phase order from tasks.md
961
+ 2. Within each phase:
962
+ - Dependencies first (tasks with no blockers)
963
+ - HIGH risk early (fail fast principle)
964
+ - Foundation before features
965
+ - Lower complexity first (quick wins)
966
+
967
+ 10. Output:
968
+ ```
969
+ āœ… Task Analysis Complete
970
+ Total: {original count} original + {auto-added count} auto-added = {total count} tasks
1021
971
  ```
1022
972
 
1023
- **Output:**
973
+ ---
974
+
975
+ **Expected Output Example:**
1024
976
  ```
1025
977
  šŸ“Š Task Analyzer v2.0 (Template-Free)...
1026
978
  Found: 47 tasks from tasks.md
@@ -1037,79 +989,82 @@ output(` Total: ${allTasks.length} original + ${additions.length} auto-added =
1037
989
 
1038
990
  āœ… Task Analysis Complete
1039
991
  Total: 47 original + 12 auto-added = 59 tasks
1040
-
1041
- 🧪 UX Testing Injection...
1042
- Injected Phase 1.5 (ux-tester) after Phase 1
1043
- āœ… 1 UX approval gate(s) added
1044
992
  ```
1045
993
 
1046
994
  ---
1047
995
 
1048
996
  ### Step 4: Create .claude Directory
1049
997
 
1050
- **Create output directory before generating files:**
1051
- ```typescript
1052
- // Create .claude directory for change-specific files
1053
- const claudeDir = `openspec/changes/${changeId}/.claude`
998
+ **WHY:** `/cdev` expects files at `openspec/changes/{id}/.claude/` - creating the directory first ensures consistent file paths.
1054
999
 
1055
- if (!fileExists(claudeDir)) {
1056
- mkdir(claudeDir)
1057
- output(`šŸ“ Created: ${claudeDir}`)
1058
- }
1059
- ```
1000
+ 1. Check if directory exists: `openspec/changes/{changeId}/.claude`
1001
+
1002
+ **If directory does NOT exist:**
1003
+ - Create the directory
1004
+ - Output: `šŸ“ Created: openspec/changes/{changeId}/.claude`
1060
1005
 
1061
- WHY: `/cdev` expects files at `openspec/changes/{id}/.claude/` - creating the directory first ensures consistent file paths.
1006
+ **If directory exists:**
1007
+ - Skip (no output needed)
1008
+
1009
+ ---
1062
1010
 
1063
1011
  ### Step 4.5: Inject UX Testing Phases (v2.7.0)
1064
1012
 
1065
1013
  > **CRITICAL:** Auto-inject Phase X.5 (ux-tester) after EVERY uxui-frontend phase
1066
1014
  > **Purpose:** User approval gate before proceeding to backend development
1067
1015
 
1068
- ```typescript
1069
- // Group tasks by phase first
1070
- let phases = groupTasksByPhase(sortedTasks)
1071
-
1072
- // Check if any phase has uxui-frontend agent
1073
- const hasUIWork = phases.some(p => {
1074
- const phaseTasks = sortedTasks.filter(t => t.phase?.number === p.number)
1075
- return getMostCommonAgent(phaseTasks) === 'uxui-frontend'
1076
- })
1077
-
1078
- if (hasUIWork) {
1079
- output(`\n🧪 UX Testing Injection...`)
1080
-
1081
- // Find all uxui-frontend phases
1082
- const uiFrontendPhases = phases.filter(p => {
1083
- const phaseTasks = sortedTasks.filter(t => t.phase?.number === p.number)
1084
- return getMostCommonAgent(phaseTasks) === 'uxui-frontend'
1085
- })
1086
-
1087
- // Inject .5 phase after each uxui-frontend phase
1088
- uiFrontendPhases.forEach(uiPhase => {
1089
- const uxTestingPhase = {
1090
- number: `${uiPhase.number}.5`,
1091
- name: 'UX Testing (Approval Gate)',
1092
- agent: 'ux-tester',
1093
- isApprovalGate: true,
1094
- strategy: 'approval-required',
1095
- tasks: [
1096
- { id: `${uiPhase.number}.5.1`, description: 'Generate personas from product context', autoAdded: true },
1097
- { id: `${uiPhase.number}.5.2`, description: 'Test UI from each persona perspective', autoAdded: true },
1098
- { id: `${uiPhase.number}.5.3`, description: 'Generate UX test report with conversion prediction', autoAdded: true },
1099
- { id: `${uiPhase.number}.5.4`, description: 'āøļø PAUSE: Wait for user approval', autoAdded: true }
1100
- ]
1101
- }
1016
+ 1. **Group tasks by phase:**
1017
+ - Use sorted tasks from Step 3
1018
+ - Group by phase number
1019
+ - Create phase objects with number, name, tasks list
1102
1020
 
1103
- // Insert after the UI phase
1104
- const insertIndex = phases.findIndex(p => p.number === uiPhase.number) + 1
1105
- phases.splice(insertIndex, 0, uxTestingPhase)
1021
+ 2. **Check if ANY phase has uxui-frontend work:**
1022
+ - For each phase, count tasks by agent
1023
+ - Find most common agent for that phase
1024
+ - Check if most common agent = 'uxui-frontend'
1025
+ - Store result as `hasUIWork`
1106
1026
 
1107
- output(` Injected Phase ${uiPhase.number}.5 (ux-tester) after Phase ${uiPhase.number}`)
1108
- })
1027
+ **If hasUIWork is TRUE:**
1109
1028
 
1110
- output(` āœ… ${uiFrontendPhases.length} UX approval gate(s) added`)
1111
- }
1029
+ 3. Output:
1112
1030
  ```
1031
+ 🧪 UX Testing Injection...
1032
+ ```
1033
+
1034
+ 4. **Find all uxui-frontend phases:**
1035
+ - Filter phases where dominant agent = 'uxui-frontend'
1036
+ - Store as `uiFrontendPhases`
1037
+
1038
+ 5. **For EACH uxui-frontend phase:**
1039
+
1040
+ a) Create new UX Testing phase object:
1041
+ - Phase number: `{uiPhase.number}.5` (e.g., if UI phase is 1, UX phase is 1.5)
1042
+ - Phase name: `UX Testing (Approval Gate)`
1043
+ - Agent: `ux-tester`
1044
+ - Mark as approval gate: `isApprovalGate: true`
1045
+ - Strategy: `approval-required`
1046
+
1047
+ b) Add these tasks to UX Testing phase:
1048
+ - `{phaseNum}.5.1 - Generate personas from product context` (autoAdded)
1049
+ - `{phaseNum}.5.2 - Test UI from each persona perspective` (autoAdded)
1050
+ - `{phaseNum}.5.3 - Generate UX test report with conversion prediction` (autoAdded)
1051
+ - `{phaseNum}.5.4 - āøļø PAUSE: Wait for user approval` (autoAdded)
1052
+
1053
+ c) Insert UX Testing phase into phases array:
1054
+ - Find position of UI phase
1055
+ - Insert UX Testing phase RIGHT AFTER (position + 1)
1056
+
1057
+ d) Output: ` Injected Phase {uiPhase.number}.5 (ux-tester) after Phase {uiPhase.number}`
1058
+
1059
+ 6. Output:
1060
+ ```
1061
+ āœ… {count} UX approval gate(s) added
1062
+ ```
1063
+
1064
+ **If hasUIWork is FALSE:**
1065
+ - Skip all steps above
1066
+
1067
+ ---
1113
1068
 
1114
1069
  **Workflow with UX Testing:**
1115
1070
  ```
@@ -1132,486 +1087,454 @@ Phase 1.5: ux-tester (APPROVAL GATE)
1132
1087
  > **v2.0:** No templates loaded. Phases generated directly from analyzed tasks.
1133
1088
  > **v2.7.0:** UX Testing phases already injected in Step 4.5
1134
1089
 
1135
- ```typescript
1136
- // Generate phases.md from phases (already includes UX Testing phases)
1090
+ **Prepare data:**
1091
+
1092
+ 1. Extract proposal title from `openspec/changes/{changeId}/proposal.md`
1093
+ - Get first heading line
1094
+ - If not found, use changeId
1095
+
1096
+ 2. Get current timestamp (ISO format)
1097
+
1098
+ 3. Calculate totals from sorted tasks:
1099
+ - Original tasks count (where autoAdded is false)
1100
+ - Auto-added tasks count (where autoAdded is true)
1101
+ - Incremental tasks count (where milestones exist)
1102
+ - Total milestones (sum of all milestone arrays)
1137
1103
 
1138
- const phasesContent = generatePhasesMarkdown(phases, sortedTasks, changeId, proposal)
1104
+ **Generate overview table:**
1139
1105
 
1140
- function generatePhasesMarkdown(phases, tasks, changeId, proposal) {
1141
- const title = extractTitle(proposal)
1142
- const timestamp = new Date().toISOString()
1106
+ 4. For each phase:
1107
+ - Get tasks for this phase
1108
+ - Find dominant agent (most common agent in phase tasks)
1109
+ - Check if phase has incremental tasks
1110
+ - Get max risk level in phase
1111
+ - Create table row: `| {number} | {name} | {task count} | {agent} | {strategy} | {risk} |`
1143
1112
 
1144
- // Calculate totals
1145
- const originalCount = tasks.filter(t => !t.autoAdded).length
1146
- const autoAddedCount = tasks.filter(t => t.autoAdded).length
1147
- const incrementalCount = tasks.filter(t => t.milestones).length
1148
- const totalMilestones = tasks.reduce((sum, t) => sum + (t.milestones?.length || 0), 0)
1113
+ **Generate phase sections:**
1149
1114
 
1150
- // Generate overview table
1151
- const overviewRows = phases.map(phase => {
1152
- const phaseTasks = tasks.filter(t => t.phase?.number === phase.number)
1153
- const dominantAgent = getMostCommonAgent(phaseTasks)
1154
- const hasIncremental = phaseTasks.some(t => t.milestones)
1155
- const maxRisk = getMaxRisk(phaseTasks)
1115
+ 5. For each phase, generate phase section:
1156
1116
 
1157
- return `| ${phase.number} | ${phase.name} | ${phaseTasks.length} | ${dominantAgent} | ${hasIncremental ? 'incremental' : 'standard'} | ${maxRisk} |`
1158
- }).join('\n')
1117
+ a) Header:
1118
+ ```markdown
1119
+ ## Phase {number}: {name}
1159
1120
 
1160
- // Generate phase sections
1161
- const phaseSections = phases.map(phase => {
1162
- return generatePhaseSection(phase, tasks.filter(t => t.phase?.number === phase.number))
1163
- }).join('\n\n---\n\n')
1121
+ **Agent:** {dominantAgent}
1122
+ **Strategy:** {šŸ”„ INCREMENTAL or Standard}
1123
+ **Risk:** {maxRisk}
1124
+ ```
1164
1125
 
1165
- // Generate auto-added summary
1166
- const autoAddedTasks = tasks.filter(t => t.autoAdded)
1167
- const autoAddedSummary = autoAddedTasks.length > 0 ? `
1168
- ## Auto-Added Tasks (Best Practices)
1126
+ b) **If phase has TDD tasks (v3.1.0):**
1127
+ - Filter tasks where tdd.tdd_required = true
1128
+ - Collect unique TDD reasons
1129
+ - Add:
1130
+ ```markdown
1131
+ **TDD Required:** āœ… YES
1132
+ **TDD Reason:** {reasons, max 2}
1133
+ **TDD Workflow:** red-green-refactor
1169
1134
 
1170
- | Task | Reason | Phase |
1171
- |------|--------|-------|
1172
- ${autoAddedTasks.map(t => `| ${t.description} | ${t.reason} | ${t.phase?.number || '-'} |`).join('\n')}
1173
- ` : ''
1135
+ āš ļø **TDD WORKFLOW REQUIRED:**
1136
+ 1. šŸ”“ RED: Write tests FIRST (they should fail)
1137
+ 2. āœ… GREEN: Write minimal implementation to pass tests
1138
+ 3. šŸ”§ REFACTOR: Improve code quality while keeping tests green
1139
+ ```
1174
1140
 
1175
- return `# Phases: ${title}
1141
+ c) **Standard tasks list:**
1142
+ - Filter tasks without milestones
1143
+ - For each task:
1144
+ ```markdown
1145
+ - [ ] {✨ if autoAdded}{task.id} {task.description}
1146
+ ```
1176
1147
 
1177
- > **Generated by:** Task Analyzer v2.0 (Template-Free)
1178
- > **Source:** tasks.md (Single Source of Truth)
1179
- > **Strategy:** Incremental development (small → large)
1180
- > **Generated:** ${timestamp}
1148
+ d) **Incremental tasks with milestones:**
1149
+ - Filter tasks with milestones
1150
+ - For each incremental task:
1151
+ ```markdown
1152
+ ### Task {id}: {description}
1153
+ **Complexity:** {complexity}/10 | **Why Agent:** {agentReason}
1154
+ ```
1181
1155
 
1182
- ---
1156
+ For each milestone:
1157
+ ```markdown
1158
+ #### Milestone {milestone.id}/{total milestones}: {milestone.name}
1159
+ **Goal:** {milestone.goal}
1183
1160
 
1184
- ## Overview
1161
+ {milestone tasks as checkboxes}
1185
1162
 
1186
- | Phase | Name | Tasks | Agent | Strategy | Risk |
1187
- |-------|------|-------|-------|----------|------|
1188
- ${overviewRows}
1163
+ **Exit Criteria:**
1164
+ {exit criteria as checkboxes}
1189
1165
 
1190
- **Total Tasks:** ${originalCount} original + ${autoAddedCount} auto-added = ${tasks.length}
1191
- **Incremental Tasks:** ${incrementalCount} tasks with ${totalMilestones} milestones
1166
+ **CHECKPOINT:** Report results before {next milestone or next phase}
1192
1167
 
1193
- ---
1168
+ ---
1169
+ ```
1194
1170
 
1195
- ${phaseSections}
1171
+ e) **Phase exit criteria:**
1172
+ ```markdown
1173
+ ### Phase {number} Exit Criteria
1174
+ - [ ] All tasks completed
1175
+ - [ ] All tests pass
1176
+ - [ ] No regression in existing functionality
1177
+ ```
1196
1178
 
1197
- ${autoAddedSummary}
1179
+ **Generate auto-added summary:**
1198
1180
 
1199
- ---
1181
+ 6. **If auto-added tasks exist:**
1182
+ ```markdown
1183
+ ## Auto-Added Tasks (Best Practices)
1200
1184
 
1201
- **End of phases.md**
1202
- `
1203
- }
1185
+ | Task | Reason | Phase |
1186
+ |------|--------|-------|
1187
+ | {description} | {reason} | {phase number} |
1188
+ ... (for each auto-added task)
1189
+ ```
1204
1190
 
1205
- function generatePhaseSection(phase, phaseTasks) {
1206
- const dominantAgent = getMostCommonAgent(phaseTasks)
1207
- const hasIncremental = phaseTasks.some(t => t.milestones)
1208
- const maxRisk = getMaxRisk(phaseTasks)
1209
-
1210
- // v3.1.0: Use TDD classification from task-analyzer.md (Step 2.6)
1211
- // Each task now has task.tdd = { tdd_required, workflow, reason, confidence }
1212
- const tddTasks = phaseTasks.filter(t => t.tdd?.tdd_required === true)
1213
- const needsTDD = tddTasks.length > 0
1214
- const tddReasons = [...new Set(tddTasks.map(t => t.tdd?.reason).filter(Boolean))]
1215
-
1216
- let section = `## Phase ${phase.number}: ${phase.name}
1217
-
1218
- **Agent:** ${dominantAgent}
1219
- **Strategy:** ${hasIncremental ? 'šŸ”„ INCREMENTAL' : 'Standard'}
1220
- **Risk:** ${maxRisk}
1221
- ${needsTDD ? `**TDD Required:** āœ… YES
1222
- **TDD Reason:** ${tddReasons.slice(0, 2).join('; ')}
1223
- **TDD Workflow:** red-green-refactor
1224
-
1225
- āš ļø **TDD WORKFLOW REQUIRED:**
1226
- 1. šŸ”“ RED: Write tests FIRST (they should fail)
1227
- 2. āœ… GREEN: Write minimal implementation to pass tests
1228
- 3. šŸ”§ REFACTOR: Improve code quality while keeping tests green
1229
- ` : ''}
1230
- `
1231
-
1232
- // Group tasks: incremental tasks get milestone sections, others get simple list
1233
- const incrementalTasks = phaseTasks.filter(t => t.milestones)
1234
- const standardTasks = phaseTasks.filter(t => !t.milestones)
1235
-
1236
- // Standard tasks section
1237
- if (standardTasks.length > 0) {
1238
- section += `### Tasks\n\n`
1239
- standardTasks.forEach(task => {
1240
- const prefix = task.autoAdded ? '✨ ' : ''
1241
- section += `- [ ] ${prefix}${task.id} ${task.description}\n`
1242
- })
1243
- section += '\n'
1244
- }
1191
+ **Assemble final content:**
1245
1192
 
1246
- // Incremental tasks with milestones
1247
- incrementalTasks.forEach(task => {
1248
- section += `### Task ${task.id}: ${task.description}
1249
- **Complexity:** ${task.complexity}/10 | **Why Agent:** ${task.agentReason}
1193
+ 7. Combine all sections:
1194
+ ```markdown
1195
+ # Phases: {title}
1250
1196
 
1251
- `
1252
- task.milestones.forEach((milestone, idx) => {
1253
- section += `#### Milestone ${milestone.id}/${task.milestones.length}: ${milestone.name}
1254
- **Goal:** ${milestone.goal}
1197
+ > **Generated by:** Task Analyzer v2.0 (Template-Free)
1198
+ > **Source:** tasks.md (Single Source of Truth)
1199
+ > **Strategy:** Incremental development (small → large)
1200
+ > **Generated:** {timestamp}
1255
1201
 
1256
- ${milestone.tasks.map(t => `- [ ] ${t}`).join('\n')}
1202
+ ---
1257
1203
 
1258
- **Exit Criteria:**
1259
- ${milestone.exitCriteria.map(c => `- [ ] ${c}`).join('\n')}
1204
+ ## Overview
1260
1205
 
1261
- **CHECKPOINT:** Report results before ${idx < task.milestones.length - 1 ? `Milestone ${milestone.id + 1}` : 'next phase'}
1206
+ | Phase | Name | Tasks | Agent | Strategy | Risk |
1207
+ |-------|------|-------|-------|----------|------|
1208
+ {overview rows}
1262
1209
 
1263
- ---
1210
+ **Total Tasks:** {original} original + {auto-added} auto-added = {total}
1211
+ **Incremental Tasks:** {incremental count} tasks with {total milestones} milestones
1264
1212
 
1265
- `
1266
- })
1267
- })
1213
+ ---
1268
1214
 
1269
- // Exit criteria for the phase
1270
- section += `### Phase ${phase.number} Exit Criteria
1271
- - [ ] All tasks completed
1272
- - [ ] All tests pass
1273
- - [ ] No regression in existing functionality
1274
- `
1215
+ {all phase sections separated by ---}
1275
1216
 
1276
- return section
1277
- }
1278
- ```
1217
+ {auto-added summary if exists}
1218
+
1219
+ ---
1220
+
1221
+ **End of phases.md**
1222
+ ```
1279
1223
 
1280
- Write to: `openspec/changes/{change-id}/.claude/phases.md`
1224
+ 8. Write to: `openspec/changes/{changeId}/.claude/phases.md`
1225
+
1226
+ ---
1281
1227
 
1282
1228
  ### Step 6: Generate flags.json (Template-Free)
1283
1229
 
1284
1230
  > **v2.0:** Flags generated from analyzed tasks, not templates.
1285
1231
 
1286
- ```typescript
1287
- // Generate flags.json from sortedTasks (from Step 3)
1288
-
1289
- const phases = groupTasksByPhase(sortedTasks)
1290
-
1291
- const flags = {
1292
- version: '2.0.0',
1293
- change_id: changeId,
1294
- change_type: detectChangeType(sortedTasks), // AI determines from task analysis
1295
- created_at: new Date().toISOString(),
1296
- updated_at: new Date().toISOString(),
1297
- current_phase: phases[0]?.number || 1,
1298
- meta: {
1299
- total_phases: phases.length,
1300
- pending_phases: phases.length,
1301
- completed_phases: 0,
1302
- total_tasks: sortedTasks.length,
1303
- original_tasks: sortedTasks.filter(t => !t.autoAdded).length,
1304
- auto_added_tasks: sortedTasks.filter(t => t.autoAdded).length,
1305
- incremental_tasks: sortedTasks.filter(t => t.milestones).length,
1306
- total_milestones: sortedTasks.reduce((sum, t) => sum + (t.milestones?.length || 0), 0)
1232
+ 1. **Group tasks by phase** (use phases from Step 4.5 which includes UX Testing phases)
1233
+
1234
+ 2. **Detect change type from tasks:**
1235
+ - Check agents assigned to tasks
1236
+ - Rules:
1237
+ - If has uxui-frontend + backend + database → `full-stack`
1238
+ - If has uxui-frontend but NO backend → `frontend-only`
1239
+ - If has backend but NO uxui-frontend → `backend-only`
1240
+ - If has test-debug and total tasks <= 5 → `bug-fix`
1241
+ - Otherwise → `feature`
1242
+
1243
+ 3. **Create flags.json structure:**
1244
+
1245
+ ```json
1246
+ {
1247
+ "version": "2.0.0",
1248
+ "change_id": "{changeId}",
1249
+ "change_type": "{detected type}",
1250
+ "created_at": "{current timestamp ISO}",
1251
+ "updated_at": "{current timestamp ISO}",
1252
+ "current_phase": "{first phase number or 1}",
1253
+ "meta": {
1254
+ "total_phases": {total phase count},
1255
+ "pending_phases": {total phase count},
1256
+ "completed_phases": 0,
1257
+ "total_tasks": {total sorted tasks count},
1258
+ "original_tasks": {count where autoAdded = false},
1259
+ "auto_added_tasks": {count where autoAdded = true},
1260
+ "incremental_tasks": {count where milestones exist},
1261
+ "total_milestones": {sum of all milestone counts}
1307
1262
  },
1308
- phases: {}
1309
- }
1310
-
1311
- // Initialize all phases from analyzed tasks
1312
- phases.forEach((phase, index) => {
1313
- const phaseTasks = sortedTasks.filter(t => t.phase?.number === phase.number)
1314
- const dominantAgent = getMostCommonAgent(phaseTasks)
1315
- const hasIncremental = phaseTasks.some(t => t.milestones)
1316
-
1317
- flags.phases[phase.number] = {
1318
- phase_number: index + 1,
1319
- name: phase.name,
1320
- status: 'pending',
1321
- agent: dominantAgent,
1322
- task_count: phaseTasks.length,
1323
- strategy: hasIncremental ? 'incremental' : 'standard',
1324
- milestones: hasIncremental ? phaseTasks.filter(t => t.milestones).reduce((sum, t) => sum + t.milestones.length, 0) : 0
1263
+ "phases": {
1264
+ "{phase.number}": {
1265
+ "phase_number": {sequential index starting from 1},
1266
+ "name": "{phase.name}",
1267
+ "status": "pending",
1268
+ "agent": "{dominant agent in phase}",
1269
+ "task_count": {tasks in this phase},
1270
+ "strategy": "{incremental or standard}",
1271
+ "milestones": {milestone count or 0}
1272
+ }
1273
+ ... (for each phase)
1325
1274
  }
1326
- })
1275
+ }
1327
1276
  ```
1328
1277
 
1329
- Write to: `openspec/changes/{change-id}/.claude/flags.json`
1278
+ 4. **For each phase, add to phases object:**
1279
+ - Get tasks for this phase
1280
+ - Find dominant agent (most common agent)
1281
+ - Check if any task has milestones
1282
+ - Count total milestones in phase
1283
+ - Create phase entry with structure above
1284
+
1285
+ 5. Write JSON to: `openspec/changes/{changeId}/.claude/flags.json`
1286
+ - Format with proper indentation (2 spaces)
1287
+
1288
+ ---
1330
1289
 
1331
1290
  ### Step 7: Generate context.md
1332
1291
 
1333
1292
  **Load template and populate:**
1334
- ```typescript
1335
- // Load template
1336
- let contextTemplate = Read('.claude/templates/context-template.md')
1337
-
1338
- // Load project tech stack
1339
- const projectTech = Read('.claude/contexts/domain/project/tech-stack.md')
1340
-
1341
- // Detect additional tech from proposal/tasks
1342
- const additionalTech = detectAdditionalTech(proposalContent, tasksContent)
1343
-
1344
- // šŸ†• Load design info (if UI work) - v2.0.0
1345
- let designInfo = ''
1346
- if (hasFrontend && tokens) {
1347
- designInfo = `
1348
- ## šŸŽØ Design System (v2.0.0)
1349
-
1350
- **Design Files:**
1351
- - data.yaml: \`design-system/data.yaml\` (~800 tokens)
1352
- - patterns/: \`design-system/patterns/*.md\` (selective loading)
1353
- - README.md: \`design-system/README.md\` (human-readable, ~100 lines)
1354
- ${pagePlan ? `- page-plan.md: \`openspec/changes/${changeId}/page-plan.md\` āœ…` : ''}
1355
-
1356
- **Style Direction:**
1357
- - Style: ${tokens.style.name}
1358
- - Theme: ${tokens.theme.name}
1359
- - Feel: ${tokens.style.feel}
1360
-
1361
- **Design Tokens:**
1362
- - Primary Color: ${tokens.colors.primary.DEFAULT}
1363
- - Component Library: ${tokens.component_library.name}
1364
- - Spacing Scale: ${tokens.spacing.scale.join(', ')}px
1365
- - Animations: ${tokens.animations.enabled ? 'Enabled' : 'Disabled'}
1366
-
1367
- **Theme & Decorations:**
1368
- ${pageType.includes('landing') || pageType.includes('marketing') ? `
1369
- - Decorations: āœ… Enabled
1370
- - USE: ${tokens.theme.decorative_elements.use.slice(0, 3).join(', ')}
1371
- - AVOID: ${tokens.theme.decorative_elements.avoid.slice(0, 2).join(', ') || '(none)'}
1372
- - Scroll Animations: āœ… Enabled
1373
- ` : `
1374
- - Decorations: āŒ Disabled (${pageType} page)
1375
- - Scroll Animations: āŒ Disabled
1376
- `}
1377
-
1378
- **Pattern Files to Load:**
1379
- ${pageType.includes('landing') || pageType.includes('marketing') ?
1380
- `- patterns/buttons.md āœ…
1381
- - patterns/cards.md āœ…
1382
- - patterns/scroll-animations.md āœ…
1383
- - patterns/decorations.md āœ…` :
1384
- pageType.includes('auth') ?
1385
- `- patterns/buttons.md āœ…
1386
- - patterns/forms.md āœ…` :
1387
- `- patterns/buttons.md āœ…
1388
- - patterns/cards.md āœ…
1389
- - patterns/forms.md āœ…`}
1390
-
1391
- **Agent Loading (STEP 0.5 for uxui-frontend):**
1392
- 1. Read: data.yaml (~800 tokens)
1393
- 2. Read: page-plan.md (if exists)
1394
- 3. Load patterns selectively based on page type
1395
- 4. Report: Design tokens + page type extracted
1396
-
1397
- **Style Guidelines:**
1398
-
1399
- | Instead of | Use | WHY |
1400
- |------------|-----|-----|
1401
- | text-gray-500 | text-foreground/70 | Theme-aware |
1402
- | p-5 | p-4 or p-6 | Spacing scale |
1403
- | ${pageType.includes('landing') ? 'āœ… Apply decorations from theme' : 'āŒ Skip decorations for this page type'} | | |
1404
- `
1405
- }
1406
-
1407
- // Replace placeholders (v2.0: use phases from Task Analyzer, not templates)
1408
- const phases = groupTasksByPhase(sortedTasks)
1409
- const totalPhases = phases.length
1410
-
1411
- contextTemplate = contextTemplate
1412
- .replace('{CHANGE_ID}', changeId)
1413
- .replace('{CHANGE_TITLE}', extractTitle(proposalContent))
1414
- .replace('{CHANGE_TYPE}', detectChangeType(sortedTasks))
1415
- .replace('{CURRENT_PHASE_NUMBER}', '1')
1416
- .replace('{TOTAL_PHASES}', totalPhases.toString())
1417
- .replace('{CREATED_DATE}', new Date().toISOString())
1418
- .replace('{CORE_TECH_LIST}', generateCoreTechList(projectTech))
1419
- .replace('{ADDITIONAL_TECH_LIST}', generateAdditionalTechList(additionalTech))
1420
- .replace('{CURRENT_PHASE}', phases[0]?.name || 'Phase 1')
1421
- .replace('{STATUS}', 'pending')
1422
- .replace('{DESIGN_SYSTEM}', designInfo) // šŸ†• Add design section
1423
- ```
1424
1293
 
1425
- Write to: `openspec/changes/{change-id}/.claude/context.md`
1294
+ 1. **Read the context template:**
1295
+ - Load: `.claude/templates/context-template.md`
1296
+
1297
+ 2. **Load project tech stack:**
1298
+ - Read: `.claude/contexts/domain/project/tech-stack.md`
1299
+ - This contains the core technologies used in the project
1300
+
1301
+ 3. **Detect additional technologies:**
1302
+ - Use Step 2.7's library detection results from pre-work-context.md
1303
+ - Extract libraries mentioned in proposal.md and tasks.md
1304
+
1305
+ 4. **Load design information (if UI work detected):**
1306
+ - Check if any task has `agent: uxui-frontend`
1307
+ - If yes and `design-system/data.yaml` exists:
1308
+ - Read design tokens from `design-system/data.yaml`
1309
+ - Check for page-plan.md at `openspec/changes/{changeId}/page-plan.md`
1310
+ - Determine page type from tasks (landing/marketing/auth/dashboard)
1311
+ - Build design system section with:
1312
+ - Design file paths (data.yaml, patterns/, README.md, page-plan.md)
1313
+ - Style direction (style name, theme name, feel)
1314
+ - Design tokens (primary color, component library, spacing scale, animations status)
1315
+ - Theme & decorations (enabled for landing/marketing, disabled for others)
1316
+ - Pattern files to load (selective based on page type)
1317
+ - Agent loading instructions (STEP 0.5 checklist)
1318
+ - Style guidelines table
1319
+
1320
+ 5. **Group tasks by phase:**
1321
+ - Use the groupTasksByPhase helper (see Helper Functions section below)
1322
+ - Count total phases
1323
+
1324
+ 6. **Replace template placeholders:**
1325
+ - `{CHANGE_ID}` → changeId parameter
1326
+ - `{CHANGE_TITLE}` → Extract title from proposal.md (first heading)
1327
+ - `{CHANGE_TYPE}` → Use detectChangeType helper (full-stack/frontend-only/backend-only/bug-fix/feature)
1328
+ - `{CURRENT_PHASE_NUMBER}` → "1"
1329
+ - `{TOTAL_PHASES}` → Total phase count
1330
+ - `{CREATED_DATE}` → Current timestamp in ISO format
1331
+ - `{CORE_TECH_LIST}` → Markdown list from tech-stack.md
1332
+ - `{ADDITIONAL_TECH_LIST}` → Markdown list from detected libraries
1333
+ - `{CURRENT_PHASE}` → First phase name or "Phase 1"
1334
+ - `{STATUS}` → "pending"
1335
+ - `{DESIGN_SYSTEM}` → Design info section (from step 4)
1336
+
1337
+ 7. **Write to file:**
1338
+ - Write final content to: `openspec/changes/{changeId}/.claude/context.md`
1426
1339
 
1427
1340
  ### Step 8: Output Summary (v2.0.0 - Template-Free)
1428
1341
 
1429
- ```typescript
1430
- // Calculate from analyzed tasks (not templates)
1431
- const phases = groupTasksByPhase(sortedTasks)
1432
- const totalPhases = phases.length
1433
- const incrementalCount = sortedTasks.filter(t => t.milestones).length
1434
- const totalMilestones = sortedTasks.reduce((sum, t) => sum + (t.milestones?.length || 0), 0)
1435
- const autoAddedCount = sortedTasks.filter(t => t.autoAdded).length
1436
-
1437
- // Check if UI work was detected
1438
- const hasUIWork = sortedTasks.some(t => t.agent === 'uxui-frontend')
1439
-
1440
- // Check for existing page-plan.md
1441
- const pagePlanPath = `openspec/changes/${changeId}/page-plan.md`
1442
- const hasPagePlan = fileExists(pagePlanPath)
1443
-
1444
- // Agent breakdown
1445
- const agentCounts = {}
1446
- sortedTasks.forEach(t => {
1447
- agentCounts[t.agent] = (agentCounts[t.agent] || 0) + 1
1448
- })
1449
- const agentSummary = Object.entries(agentCounts)
1450
- .map(([agent, count]) => `${agent} (${count})`)
1451
- .join(', ')
1452
-
1453
- // Build output
1454
- let output = `
1455
- āœ… Change setup complete!
1342
+ **Calculate statistics from analyzed tasks:**
1343
+
1344
+ 1. **Group tasks and count:**
1345
+ - Use groupTasksByPhase helper to organize tasks
1346
+ - Count total phases
1347
+ - Count tasks with incremental milestones
1348
+ - Sum all milestones across tasks
1349
+ - Count auto-added tasks
1350
+
1351
+ 2. **Check for UI work:**
1352
+ - Check if any task has `agent: uxui-frontend`
1353
+ - If yes, check if `openspec/changes/{changeId}/page-plan.md` exists
1354
+
1355
+ 3. **Calculate agent breakdown:**
1356
+ - Count tasks per agent
1357
+ - Format as: "agent1 (count1), agent2 (count2), ..."
1358
+
1359
+ 4. **Build output message with these sections:**
1360
+
1361
+ **Header:**
1362
+ ```
1363
+ āœ… Change setup complete!
1364
+
1365
+ šŸ“¦ Change: {changeId}
1366
+ šŸ“Š Architecture: Task Analyzer v2.0 (Template-Free)
1367
+ šŸ› ļø Agents: {agent summary}
1368
+ ```
1369
+
1370
+ **Files created:**
1371
+ ```
1372
+ šŸ“ Files created:
1373
+ āœ“ openspec/changes/{changeId}/.claude/phases.md
1374
+ āœ“ openspec/changes/{changeId}/.claude/flags.json
1375
+ āœ“ openspec/changes/{changeId}/.claude/context.md
1376
+ ```
1377
+
1378
+ **Task analysis:**
1379
+ ```
1380
+ šŸ“Š Task Analysis:
1381
+ Total: X tasks (Y original + Z auto-added)
1382
+ Incremental: X tasks with Y milestones
1383
+ Phases: X
1384
+ UX Approval Gates: X
1385
+ ```
1386
+
1387
+ **Phase overview:**
1388
+ ```
1389
+ šŸ“‹ Phase Overview:
1390
+ Phase 1: {name} ({agent}, {count} tasks)
1391
+ Phase 2: {name} ({agent}, {count} tasks)
1392
+ ...
1393
+ ```
1394
+
1395
+ 5. **Add UI work recommendation (if applicable):**
1396
+ - If UI work detected AND page-plan.md exists:
1397
+ - Show: "āœ… page-plan.md found: {path}"
1398
+ - Note: "uxui-frontend will use this for component planning"
1399
+
1400
+ - If UI work detected AND page-plan.md missing:
1401
+ - Show banner: "šŸŽØ UI Work Detected!"
1402
+ - List phases with UI work
1403
+ - Explain benefits: Content variants, component index, asset checklist, approval process
1404
+ - Show recommended steps (4 steps with /pageplan workflow)
1405
+
1406
+ 6. **Add next steps:**
1407
+ - If UI work without page-plan:
1408
+ ```
1409
+ šŸš€ Ready to start development!
1410
+
1411
+ Next steps:
1412
+ 1. (Recommended) Run: /pageplan @prd.md
1413
+ 2. Edit page-plan.md (content, assets, approval)
1414
+ 3. Review workflow: openspec/changes/{changeId}/.claude/phases.md
1415
+ 4. Start development: /cdev {changeId}
1416
+ 5. View progress: /cview {changeId}
1417
+ ```
1418
+
1419
+ - Otherwise:
1420
+ ```
1421
+ šŸš€ Ready to start development!
1422
+
1423
+ Next steps:
1424
+ 1. Review workflow: openspec/changes/{changeId}/.claude/phases.md
1425
+ 2. Start development: /cdev {changeId}
1426
+ 3. View progress: /cview {changeId}
1427
+ ```
1428
+
1429
+ 7. **Display the complete output message**
1456
1430
 
1457
- šŸ“¦ Change: ${changeId}
1458
- šŸ“Š Architecture: Task Analyzer v2.0 (Template-Free)
1459
- šŸ› ļø Agents: ${agentSummary}
1431
+ ---
1460
1432
 
1461
- šŸ“ Files created:
1462
- āœ“ openspec/changes/${changeId}/.claude/phases.md
1463
- āœ“ openspec/changes/${changeId}/.claude/flags.json
1464
- āœ“ openspec/changes/${changeId}/.claude/context.md
1433
+ ## Helper Functions
1465
1434
 
1466
- šŸ“Š Task Analysis:
1467
- Total: ${sortedTasks.length} tasks (${sortedTasks.filter(t => !t.autoAdded).length} original + ${autoAddedCount} auto-added)
1468
- Incremental: ${incrementalCount} tasks with ${totalMilestones} milestones
1469
- Phases: ${totalPhases}
1470
- UX Approval Gates: ${phases.filter(p => p.agent === 'ux-tester').length}
1435
+ ### extractTaskIds()
1471
1436
 
1472
- šŸ“‹ Phase Overview:
1473
- ${phases.map((p, i) => {
1474
- const phaseTasks = sortedTasks.filter(t => t.phase?.number === p.number)
1475
- const agent = getMostCommonAgent(phaseTasks)
1476
- return ` Phase ${p.number}: ${p.name} (${agent}, ${phaseTasks.length} tasks)`
1477
- }).join('\n')}
1478
- `
1479
-
1480
- // šŸ†• v2.6.0: Recommend /pageplan if UI work detected
1481
- if (hasUIWork) {
1482
- if (hasPagePlan) {
1483
- output += `
1484
- āœ… page-plan.md found: ${pagePlanPath}
1485
- → uxui-frontend will use this for component planning
1486
- `
1487
- } else {
1488
- output += `
1489
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1490
- šŸŽØ UI Work Detected!
1491
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1492
-
1493
- Phases with UI work:
1494
- ${uiPhases.map(p => \` • Phase \${p.number}: \${p.name} (\${p.agent})\`).join('\\n')}
1495
-
1496
- šŸ’” RECOMMENDED: Run /pageplan before /cdev
1497
-
1498
- Why?
1499
- ā”œā”€ā”€ Content variants (3 options per element - user picks A/B/C)
1500
- ā”œā”€ā”€ Component index (auto-generated, prevents duplicates)
1501
- ā”œā”€ā”€ Asset checklist (images, icons with specs)
1502
- └── Approval process (user reviews before implementation)
1503
-
1504
- šŸ“ Recommended Steps:
1505
- 1. /pageplan @prd.md ← Generate page plan
1506
- 2. Edit page-plan.md ← Pick A/B/C content, prepare assets
1507
- 3. Mark APPROVED in Section 6 ← Sign-off before implementation
1508
- 4. /cdev ${changeId} ← Implement with real content
1509
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1510
- `
1511
- }
1512
- }
1437
+ To extract task IDs from tasks.md content:
1513
1438
 
1514
- output += `
1515
- šŸš€ Ready to start development!
1439
+ 1. Search for patterns matching: `- [ ] X.X` (checkbox followed by number.number)
1440
+ - Pattern: `-\s*\[\s*\]\s*(\d+\.\d+)`
1441
+ - Matches: `- [ ] 1.1`, `- [ ] 2.3`, etc.
1516
1442
 
1517
- Next steps:`
1518
-
1519
- if (hasUIWork && !hasPagePlan) {
1520
- output += `
1521
- 1. (Recommended) Run: /pageplan @prd.md
1522
- 2. Edit page-plan.md (content, assets, approval)
1523
- 3. Review workflow: openspec/changes/${changeId}/.claude/phases.md
1524
- 4. Start development: /cdev ${changeId}
1525
- 5. View progress: /cview ${changeId}`
1526
- } else {
1527
- output += `
1528
- 1. Review workflow: openspec/changes/${changeId}/.claude/phases.md
1529
- 2. Start development: /cdev ${changeId}
1530
- 3. View progress: /cview ${changeId}`
1531
- }
1443
+ 2. Extract the number portion (e.g., "1.1", "1.2", "2.1")
1532
1444
 
1533
- console.log(output)
1534
- ```
1445
+ 3. Return all found task IDs as a list
1446
+
1447
+ **Example:**
1448
+ - Input: `- [ ] 1.1 Setup database\n- [ ] 1.2 Create schema`
1449
+ - Output: `["1.1", "1.2"]`
1535
1450
 
1536
1451
  ---
1537
1452
 
1538
- ## Helper Functions
1453
+ ### getMostCommonAgent() (v2.0 - Template-Free)
1539
1454
 
1540
- ### extractTaskIds()
1541
- ```typescript
1542
- // Extract task IDs like "1.1", "1.2", "2.1" from tasks.md
1543
- function extractTaskIds(content: string): string[] {
1544
- const regex = /-\s*\[\s*\]\s*(\d+\.\d+)/g
1545
- const matches = [...content.matchAll(regex)]
1546
- return matches.map(m => m[1])
1547
- }
1548
- ```
1455
+ > **v2.0:** Agent determined by AI analysis of tasks, not phase templates
1549
1456
 
1550
- ### getMostCommonAgent() (v2.0 - Template-Free)
1551
- ```typescript
1552
- // v2.0: Agent determined by AI analysis of tasks, not phase templates
1553
- function getMostCommonAgent(tasks: AnalyzedTask[]): string {
1554
- if (tasks.length === 0) return 'integration'
1555
-
1556
- const counts = {}
1557
- tasks.forEach(t => {
1558
- counts[t.agent] = (counts[t.agent] || 0) + 1
1559
- })
1560
-
1561
- return Object.entries(counts)
1562
- .sort((a, b) => b[1] - a[1])[0][0]
1563
- }
1564
- ```
1457
+ To find the most common agent in a list of tasks:
1458
+
1459
+ 1. **Handle empty list:**
1460
+ - If tasks list is empty, return `'integration'` as default
1461
+
1462
+ 2. **Count agents:**
1463
+ - Create a count map for each agent
1464
+ - Loop through tasks and increment count for each task's agent
1465
+
1466
+ 3. **Find the most common:**
1467
+ - Sort agents by count (descending)
1468
+ - Return the agent with highest count
1469
+
1470
+ **Example:**
1471
+ - Input: `[{agent: 'backend'}, {agent: 'backend'}, {agent: 'test-debug'}]`
1472
+ - Output: `'backend'` (appears 2 times)
1473
+
1474
+ ---
1565
1475
 
1566
1476
  ### groupTasksByPhase()
1567
- ```typescript
1568
- function groupTasksByPhase(tasks: AnalyzedTask[]): Phase[] {
1569
- const phaseMap = new Map()
1570
-
1571
- tasks.forEach(task => {
1572
- const phaseNum = task.phase?.number || 1
1573
- const phaseName = task.phase?.name || `Phase ${phaseNum}`
1574
-
1575
- if (!phaseMap.has(phaseNum)) {
1576
- phaseMap.set(phaseNum, {
1577
- number: phaseNum,
1578
- name: phaseName,
1579
- tasks: []
1580
- })
1581
- }
1582
- phaseMap.get(phaseNum).tasks.push(task)
1583
- })
1584
1477
 
1585
- return Array.from(phaseMap.values()).sort((a, b) => a.number - b.number)
1586
- }
1587
- ```
1478
+ To group tasks into phases:
1479
+
1480
+ 1. **Create a phase map** (keyed by phase number)
1481
+
1482
+ 2. **For each task:**
1483
+ - Get phase number from `task.phase.number` (default to 1 if not set)
1484
+ - Get phase name from `task.phase.name` (default to `"Phase {number}"` if not set)
1485
+
1486
+ 3. **Add to map:**
1487
+ - If phase number not in map yet:
1488
+ - Create new phase entry with: number, name, empty tasks array
1489
+ - Add task to that phase's tasks array
1490
+
1491
+ 4. **Convert map to sorted array:**
1492
+ - Convert map values to array
1493
+ - Sort by phase number (ascending)
1494
+ - Return sorted phases
1495
+
1496
+ **Example:**
1497
+ - Input: `[{phase: {number: 2, name: "Backend"}}, {phase: {number: 1, name: "UI"}}]`
1498
+ - Output: `[{number: 1, name: "UI", tasks: [...]}, {number: 2, name: "Backend", tasks: [...]}]`
1499
+
1500
+ ---
1588
1501
 
1589
1502
  ### getMaxRisk()
1590
- ```typescript
1591
- function getMaxRisk(tasks: AnalyzedTask[]): string {
1592
- if (tasks.some(t => t.risk === 'HIGH')) return 'HIGH'
1593
- if (tasks.some(t => t.risk === 'MEDIUM')) return 'MEDIUM'
1594
- return 'LOW'
1595
- }
1596
- ```
1503
+
1504
+ To find the highest risk level across tasks:
1505
+
1506
+ 1. Check if any task has `risk: 'HIGH'` → return `'HIGH'`
1507
+ 2. Otherwise, check if any task has `risk: 'MEDIUM'` → return `'MEDIUM'`
1508
+ 3. Otherwise, return `'LOW'`
1509
+
1510
+ **Example:**
1511
+ - Input: `[{risk: 'LOW'}, {risk: 'MEDIUM'}, {risk: 'LOW'}]`
1512
+ - Output: `'MEDIUM'`
1513
+
1514
+ ---
1597
1515
 
1598
1516
  ### detectChangeType() (v2.0 - AI-Driven)
1599
- ```typescript
1600
- function detectChangeType(tasks: AnalyzedTask[]): string {
1601
- // Detect from analyzed tasks, not keywords
1602
- const hasUI = tasks.some(t => t.agent === 'uxui-frontend')
1603
- const hasBackend = tasks.some(t => t.agent === 'backend')
1604
- const hasDatabase = tasks.some(t => t.agent === 'database')
1605
- const hasTests = tasks.some(t => t.agent === 'test-debug')
1606
-
1607
- if (hasUI && hasBackend && hasDatabase) return 'full-stack'
1608
- if (hasUI && !hasBackend) return 'frontend-only'
1609
- if (hasBackend && !hasUI) return 'backend-only'
1610
- if (hasTests && tasks.length <= 5) return 'bug-fix'
1611
-
1612
- return 'feature'
1613
- }
1614
- ```
1517
+
1518
+ > **v2.0:** Detect from analyzed tasks, not keywords
1519
+
1520
+ To detect change type from task agents:
1521
+
1522
+ 1. **Check which agents are present:**
1523
+ - `hasUI`: Any task with `agent: 'uxui-frontend'`
1524
+ - `hasBackend`: Any task with `agent: 'backend'`
1525
+ - `hasDatabase`: Any task with `agent: 'database'`
1526
+ - `hasTests`: Any task with `agent: 'test-debug'`
1527
+
1528
+ 2. **Determine type based on combination:**
1529
+ - If `hasUI` AND `hasBackend` AND `hasDatabase` → `'full-stack'`
1530
+ - If `hasUI` AND NOT `hasBackend` → `'frontend-only'`
1531
+ - If `hasBackend` AND NOT `hasUI` → `'backend-only'`
1532
+ - If `hasTests` AND total tasks <= 5 → `'bug-fix'`
1533
+ - Otherwise → `'feature'`
1534
+
1535
+ **Example:**
1536
+ - Input: `[{agent: 'uxui-frontend'}, {agent: 'backend'}, {agent: 'database'}]`
1537
+ - Output: `'full-stack'`
1615
1538
 
1616
1539
  ### detectAdditionalTech() - REMOVED (v3.1.0)
1617
1540