@kosuke-ai/cli 0.0.38 โ†’ 0.0.39

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.
@@ -2,10 +2,21 @@
2
2
  * Tickets command - Generate tickets from requirements document
3
3
  *
4
4
  * This command analyzes a requirements document (default: docs.md) and generates
5
- * structured tickets in three phases:
6
- * 1. Schema tickets (database design)
7
- * 2. Backend tickets (API, services, business logic)
8
- * 3. Frontend tickets (pages, components, UI)
5
+ * structured tickets with test coverage:
6
+ *
7
+ * SCAFFOLD BATCH:
8
+ * 1. Schema scaffold (database infrastructure)
9
+ * 2. DB test (validate scaffold schema)
10
+ * 3. Backend scaffold (API infrastructure)
11
+ * 4. Frontend scaffold (UI infrastructure)
12
+ * 5. Web tests (validate scaffold E2E)
13
+ *
14
+ * LOGIC BATCHES (1..N):
15
+ * 1. Schema logic (business entities)
16
+ * 2. DB test (validate logic schema)
17
+ * 3. Backend logic (business API)
18
+ * 4. Frontend logic (business UI)
19
+ * 5. Web tests (validate logic E2E)
9
20
  *
10
21
  * Claude Code Agent explores the specified directory (default: current directory)
11
22
  * to understand the existing codebase and generate contextual tickets.
@@ -23,10 +34,244 @@ import { existsSync, readFileSync, statSync, writeFileSync } from 'fs';
23
34
  import { join, resolve } from 'path';
24
35
  import { formatCostBreakdown, runAgent } from '../utils/claude-agent.js';
25
36
  import { logger, setupCancellationHandler } from '../utils/logger.js';
37
+ /**
38
+ * Analyze requirements to determine which layers are needed
39
+ */
40
+ async function analyzeRequiredLayers(requirementsContent, projectPath) {
41
+ console.log('๐Ÿ” Analyzing requirements to determine needed layers...\n');
42
+ const systemPrompt = `You are an expert software architect analyzing requirements to determine which layers need changes.
43
+
44
+ **Your Task:**
45
+ Analyze the requirements and determine which layers (schema/backend/frontend) need changes.
46
+
47
+ **Requirements:**
48
+ ${requirementsContent}
49
+
50
+ **Context:**
51
+ You have access to the project directory at: ${projectPath}
52
+ Explore the codebase to understand the existing architecture and tech stack.
53
+
54
+ **Analysis Criteria:**
55
+
56
+ **Schema (Database):**
57
+ - New tables, columns, or relationships
58
+ - Changes to existing database structure
59
+ - Data model modifications
60
+ - Examples: "Add comments to posts", "Track user preferences", "Store session data"
61
+
62
+ **Backend (API):**
63
+ - New API endpoints or business logic
64
+ - Changes to existing endpoints
65
+ - Server-side processing or validation
66
+ - Integration with external services
67
+ - Examples: "Export data to CSV", "Send email notifications", "Process payments"
68
+
69
+ **Frontend (UI):**
70
+ - New pages, components, or user interactions
71
+ - Changes to existing UI
72
+ - User-facing features
73
+ - Examples: "Add dark mode toggle", "Create dashboard", "Build user profile page"
74
+
75
+ **Important:**
76
+ - Simple UI changes (styling, layout) typically DON'T need backend or schema changes
77
+ - Features involving data persistence ALWAYS need schema + backend + frontend
78
+ - API-only features (webhooks, cron jobs) may not need frontend changes
79
+ - Be precise - only include layers that are actually needed
80
+
81
+ **Output Format:**
82
+ Return ONLY a valid JSON object with this structure:
83
+ {
84
+ "needsSchema": boolean,
85
+ "needsBackend": boolean,
86
+ "needsFrontend": boolean,
87
+ "reasoning": "Brief explanation of why each layer is or isn't needed"
88
+ }
89
+
90
+ No markdown, no code blocks, just raw JSON.`;
91
+ const agentResult = await runAgent('Analyze requirements and determine needed layers', {
92
+ systemPrompt,
93
+ cwd: projectPath,
94
+ maxTurns: 15,
95
+ verbosity: 'minimal',
96
+ });
97
+ // Parse response
98
+ try {
99
+ const jsonMatch = agentResult.response.match(/\{[\s\S]*\}/);
100
+ if (!jsonMatch) {
101
+ throw new Error('No JSON found in analysis response');
102
+ }
103
+ const analysis = JSON.parse(jsonMatch[0]);
104
+ // Validate structure
105
+ if (typeof analysis.needsSchema !== 'boolean' ||
106
+ typeof analysis.needsBackend !== 'boolean' ||
107
+ typeof analysis.needsFrontend !== 'boolean' ||
108
+ typeof analysis.reasoning !== 'string') {
109
+ throw new Error('Invalid analysis structure');
110
+ }
111
+ return analysis;
112
+ }
113
+ catch (error) {
114
+ console.error('โŒ Failed to parse layer analysis:', error);
115
+ console.error('Raw response:', agentResult.response.substring(0, 500));
116
+ throw new Error(`Failed to analyze required layers: ${error instanceof Error ? error.message : String(error)}`);
117
+ }
118
+ }
119
+ /**
120
+ * Build system prompt for DB test ticket generation
121
+ */
122
+ function buildDBTestPrompt(batchType, requirementsContent, projectPath, previousSchemaTickets) {
123
+ const ticketId = batchType === 'scaffold' ? 'DB-TEST-1' : 'DB-TEST-2';
124
+ const schemaTicketsContext = previousSchemaTickets
125
+ .map((t) => `**${t.id}: ${t.title}**\n${t.description}`)
126
+ .join('\n\n');
127
+ return `You are an expert QA engineer generating database validation test tickets.
128
+
129
+ **Your Task:**
130
+ Generate ONE database test ticket to validate the schema implementation from the tickets below.
131
+
132
+ **Schema Tickets to Validate:**
133
+ ${schemaTicketsContext}
134
+
135
+ **Database Test Ticket Goal:**
136
+ Based on the schema tickets above, create a test that validates those tables were correctly created:
137
+ 1. Extract all table names mentioned in the schema tickets
138
+ 2. List all tables that need to be validated
139
+ 3. Create a test ticket that checks those tables exist
140
+
141
+ **IMPORTANT:**
142
+ - Do NOT explore the codebase
143
+ - Do NOT look at existing schema files
144
+ - ONLY use the schema tickets above to determine what tables to validate
145
+ - The test should verify that the tables described in those tickets exist
146
+
147
+ **Ticket Structure:**
148
+ - id: "${ticketId}"
149
+ - title: Clear description of what schema is being validated
150
+ - description: Detailed test plan with:
151
+ * List of tables to validate (extracted from schema tickets above)
152
+ * What to check: verify all tables exist
153
+ * Success criteria: all tables from schema tickets exist in database
154
+ - type: "db-test"
155
+ - estimatedEffort: 1-3 (these are simple validation tests)
156
+ - status: "Todo"
157
+ - category: "database-validation"
158
+
159
+ **Output Format:**
160
+ Return ONLY a valid JSON array with ONE ticket. No markdown, no code blocks, just raw JSON.
161
+
162
+ Example:
163
+ [
164
+ {
165
+ "id": "DB-TEST-1",
166
+ "title": "Validate scaffold database schema",
167
+ "description": "Verify that the scaffold schema has been correctly implemented based on SCHEMA-SCAFFOLD-1:\\n\\nTables to validate:\\n- users\\n- user_subscriptions\\n- notifications\\n\\nValidation checks:\\n- Verify all tables exist\\n- Check table names are correct\\n\\nAcceptance Criteria:\\n- All tables from SCHEMA-SCAFFOLD-1 exist in database\\n- No schema errors",
168
+ "type": "db-test",
169
+ "estimatedEffort": 2,
170
+ "status": "Todo",
171
+ "category": "database-validation"
172
+ }
173
+ ]
174
+
175
+ **Critical Instructions:**
176
+ 1. Analyze the schema tickets above to extract table names
177
+ 2. Generate a focused test ticket that validates those specific tables exist
178
+ 3. Reference the schema ticket IDs in the description
179
+ 4. Keep descriptions clear and actionable
180
+ 5. Return ONLY valid JSON - no explanations, no markdown formatting`;
181
+ }
182
+ /**
183
+ * Build system prompt for Web test ticket generation
184
+ */
185
+ function buildWebTestPrompt(batchType, requirementsContent, projectPath, previousImplementationTickets, startingNumber) {
186
+ const backendTicketsContext = previousImplementationTickets.backend
187
+ .map((t) => `**${t.id}: ${t.title}**\n${t.description}`)
188
+ .join('\n\n');
189
+ const frontendTicketsContext = previousImplementationTickets.frontend
190
+ .map((t) => `**${t.id}: ${t.title}**\n${t.description}`)
191
+ .join('\n\n');
192
+ return `You are an expert QA engineer generating end-to-end web test tickets.
193
+
194
+ **Your Task:**
195
+ Generate web test tickets to validate the implementation from the backend and frontend tickets below.
196
+
197
+ **Backend Tickets to Validate:**
198
+ ${backendTicketsContext || 'No backend tickets for this batch'}
199
+
200
+ **Frontend Tickets to Validate:**
201
+ ${frontendTicketsContext || 'No frontend tickets for this batch'}
202
+
203
+ **Web Test Ticket Goals:**
204
+ Based on the implementation tickets above, create tests that validate those features work end-to-end:
205
+ 1. Analyze the backend and frontend tickets to understand what features were implemented
206
+ 2. Create test tickets that verify those features work correctly in the browser
207
+ 3. Focus on user-facing functionality and complete user flows
208
+
209
+ **IMPORTANT:**
210
+ - Do NOT explore the codebase
211
+ - Do NOT look at existing frontend implementation
212
+ - ONLY use the implementation tickets above to determine what to test
213
+ - The tests should verify that the features described in those tickets work end-to-end
214
+
215
+ Let Claude decide granularity based on complexity - could be:
216
+ - One test per major user flow
217
+ - One test covering multiple related features
218
+ - Multiple tests for complex features
219
+
220
+ **Ticket Structure:**
221
+ Each ticket must have:
222
+ - id: "WEB-TEST-${startingNumber}", "WEB-TEST-${startingNumber + 1}", etc. (sequential)
223
+ - title: Clear description of what is being tested
224
+ - description: Detailed test plan with:
225
+ * Reference to implementation tickets being tested
226
+ * User flow to test
227
+ * Steps to execute
228
+ * Expected outcomes (based on implementation tickets)
229
+ * Success criteria
230
+ - type: "web-test"
231
+ - estimatedEffort: number (1-10 based on test complexity)
232
+ - status: "Todo"
233
+ - category: feature name being tested
234
+
235
+ **Output Format:**
236
+ Return ONLY a valid JSON array of tickets. No markdown, no code blocks, just raw JSON.
237
+
238
+ Example:
239
+ [
240
+ {
241
+ "id": "WEB-TEST-1",
242
+ "title": "Test authentication flow (validates BACKEND-SCAFFOLD-1, FRONTEND-SCAFFOLD-1)",
243
+ "description": "Validate that the authentication implementation from BACKEND-SCAFFOLD-1 and FRONTEND-SCAFFOLD-1 works end-to-end:\\n\\nTest Flow:\\n1. Navigate to sign-in page\\n2. Enter credentials\\n3. Submit form\\n4. Verify redirect to dashboard\\n5. Check user session is active\\n\\nExpected Results (from implementation tickets):\\n- Sign-in successful\\n- User redirected to dashboard\\n- Protected content visible\\n\\nAcceptance Criteria:\\n- Authentication works as described in BACKEND-SCAFFOLD-1\\n- UI matches FRONTEND-SCAFFOLD-1 requirements\\n- No console errors\\n- Session persists correctly",
244
+ "type": "web-test",
245
+ "estimatedEffort": 5,
246
+ "status": "Todo",
247
+ "category": "authentication"
248
+ }
249
+ ]
250
+
251
+ **Critical Instructions:**
252
+ 1. Analyze the implementation tickets above to extract features to test
253
+ 2. Generate test tickets that validate those specific features
254
+ 3. Reference the implementation ticket IDs in test descriptions
255
+ 4. Focus on end-to-end user flows that span backend + frontend
256
+ 5. Make descriptions detailed with clear steps
257
+ 6. Return ONLY valid JSON - no explanations, no markdown formatting
258
+ 7. Ensure ticket IDs are sequential starting from WEB-TEST-${startingNumber}`;
259
+ }
26
260
  /**
27
261
  * Build system prompt for ticket generation with integrated analysis
28
262
  */
29
- function buildTicketGenerationPrompt(phase, ticketType, requirementsContent, projectPath) {
263
+ function buildTicketGenerationPrompt(phase, ticketType, requirementsContent, projectPath, isScaffoldMode, previousTickets) {
264
+ // Handle test tickets differently
265
+ if (phase === 'db-test') {
266
+ return buildDBTestPrompt(ticketType, requirementsContent, projectPath, previousTickets?.schema || []);
267
+ }
268
+ if (phase === 'web-test') {
269
+ const startingNumber = previousTickets?.webTestStartNumber || 1;
270
+ return buildWebTestPrompt(ticketType, requirementsContent, projectPath, {
271
+ backend: previousTickets?.backend || [],
272
+ frontend: previousTickets?.frontend || [],
273
+ }, startingNumber);
274
+ }
30
275
  const phaseTypeKey = `${phase}_${ticketType}`;
31
276
  const phaseInstructions = {
32
277
  schema_scaffold: `
@@ -147,7 +392,8 @@ Assign appropriate category based on business domain:
147
392
  - Or use feature name (e.g., "user-management", "notifications", "analytics")
148
393
  - Keep categories consistent across related tickets
149
394
  `;
150
- const templateBaseline = `
395
+ const contextualGuidance = isScaffoldMode
396
+ ? `
151
397
  **Kosuke Template Baseline (what the template already includes):**
152
398
  - **Authentication**: Better Auth with Email OTP
153
399
  - **User Model**: Individual users (no organizations/multi-tenancy by default)
@@ -174,8 +420,28 @@ For LOGIC tickets:
174
420
  - Focus on core business functionality
175
421
  - Implement application-specific features
176
422
  - Build domain models and workflows
423
+ `
424
+ : `
425
+ **Project Context:**
426
+ Explore the codebase to understand:
427
+ - Tech stack and framework versions (Next.js, React, etc.)
428
+ - Existing architecture patterns (App Router, API routes, etc.)
429
+ - Database schema structure and ORM (Drizzle, Prisma, etc.)
430
+ - API route patterns and conventions
431
+ - UI component library and styling approach
432
+ - Testing framework and patterns
433
+ - Authentication system (if any)
434
+ - State management approach
435
+
436
+ **Critical Instructions:**
437
+ - Generate tickets that follow existing patterns in the codebase
438
+ - Use the same naming conventions, file structure, and code style
439
+ - Leverage existing utilities and components where possible
440
+ - Match the existing tech stack (don't introduce new frameworks)
441
+ - Follow the project's architectural decisions
442
+ - Maintain consistency with existing code quality standards
177
443
  `;
178
- return `You are an expert software architect generating implementation tickets for a Kosuke Template project.
444
+ return `You are an expert software architect generating implementation tickets for ${isScaffoldMode ? 'a Kosuke Template project' : 'an existing project'}.
179
445
 
180
446
  **Your Task:**
181
447
  ${phaseInstructions[phaseTypeKey]}
@@ -183,11 +449,11 @@ ${phaseInstructions[phaseTypeKey]}
183
449
  **Requirements Document:**
184
450
  ${requirementsContent}
185
451
 
186
- ${templateBaseline}
452
+ ${contextualGuidance}
187
453
 
188
454
  **Context:**
189
455
  You have access to the project directory at: ${projectPath}
190
- The template baseline is documented in CLAUDE.md.
456
+ ${isScaffoldMode ? 'The template baseline is documented in CLAUDE.md.' : ''}
191
457
  Explore the codebase to understand the tech stack, architecture patterns, and coding conventions.
192
458
 
193
459
  **Ticket Structure:**
@@ -246,11 +512,12 @@ function parseTicketsFromResponse(response, phase, ticketType) {
246
512
  if (!Array.isArray(tickets)) {
247
513
  throw new Error(`Expected array of tickets, got ${typeof tickets}`);
248
514
  }
515
+ const validTypes = ['scaffold', 'logic', 'db-test', 'web-test'];
249
516
  for (const ticket of tickets) {
250
517
  if (!ticket.id || !ticket.title || !ticket.description) {
251
518
  throw new Error(`Invalid ticket structure: ${JSON.stringify(ticket)}`);
252
519
  }
253
- if (!ticket.type || (ticket.type !== 'scaffold' && ticket.type !== 'logic')) {
520
+ if (!ticket.type || !validTypes.includes(ticket.type)) {
254
521
  throw new Error(`Invalid or missing type for ticket ${ticket.id}: ${ticket.type}`);
255
522
  }
256
523
  if (typeof ticket.estimatedEffort !== 'number' ||
@@ -284,29 +551,37 @@ function writeTicketsToFile(outputPath, tickets) {
284
551
  /**
285
552
  * Generate tickets for a specific phase and type
286
553
  */
287
- async function generatePhaseTickets(phase, ticketType, requirementsContent, projectPath, outputPath, existingTickets) {
554
+ async function generatePhaseTickets(phase, ticketType, requirementsContent, projectPath, outputPath, existingTickets, isScaffoldMode, previousTickets) {
288
555
  const phaseEmoji = {
289
556
  schema: '๐Ÿ—„๏ธ',
290
557
  backend: 'โš™๏ธ',
291
558
  frontend: '๐ŸŽจ',
559
+ 'db-test': '๐Ÿงช',
560
+ 'web-test': '๐ŸŒ',
292
561
  };
293
562
  const typeEmoji = {
294
563
  scaffold: '๐Ÿ—๏ธ',
295
564
  logic: '๐Ÿ’ก',
565
+ 'db-test': '๐Ÿงช',
566
+ 'web-test': '๐ŸŒ',
296
567
  };
297
568
  const phaseName = {
298
569
  schema: 'Schema',
299
570
  backend: 'Backend',
300
571
  frontend: 'Frontend',
572
+ 'db-test': 'DB Test',
573
+ 'web-test': 'Web Test',
301
574
  };
302
575
  const typeName = {
303
576
  scaffold: 'Scaffold',
304
577
  logic: 'Logic',
578
+ 'db-test': 'DB Test',
579
+ 'web-test': 'Web Test',
305
580
  };
306
581
  console.log(`\n${'='.repeat(60)}`);
307
582
  console.log(`${phaseEmoji[phase]} ${typeEmoji[ticketType]} ${phaseName[phase]} ${typeName[ticketType]} Tickets`);
308
583
  console.log(`${'='.repeat(60)}\n`);
309
- const systemPrompt = buildTicketGenerationPrompt(phase, ticketType, requirementsContent, projectPath);
584
+ const systemPrompt = buildTicketGenerationPrompt(phase, ticketType, requirementsContent, projectPath, isScaffoldMode, previousTickets);
310
585
  const agentResult = await runAgent(`Generate ${phaseName[phase]} ${typeName[ticketType]} tickets from the requirements.`, {
311
586
  systemPrompt,
312
587
  cwd: projectPath,
@@ -335,7 +610,8 @@ async function generatePhaseTickets(phase, ticketType, requirementsContent, proj
335
610
  * Core tickets logic
336
611
  */
337
612
  export async function ticketsCore(options) {
338
- const { path = 'docs.md', directory } = options;
613
+ const { directory, scaffold = false } = options;
614
+ const isScaffoldMode = scaffold;
339
615
  // 1. Validate and resolve project directory
340
616
  const projectPath = directory ? resolve(directory) : process.cwd();
341
617
  if (!existsSync(projectPath)) {
@@ -347,21 +623,56 @@ export async function ticketsCore(options) {
347
623
  if (!stats.isDirectory()) {
348
624
  throw new Error(`Path is not a directory: ${projectPath}\n` + `Please provide a valid directory path.`);
349
625
  }
350
- console.log(`๐Ÿ“ Using project directory: ${projectPath}\n`);
351
- // 2. Read requirements document (relative to project directory)
352
- console.log('๐Ÿ“„ Reading requirements document...');
353
- const requirementsPath = join(projectPath, path);
354
- if (!existsSync(requirementsPath)) {
355
- throw new Error(`Requirements document not found: ${path}\n` +
356
- `Please provide a valid path using --path=<file>\n` +
357
- `Example: kosuke tickets --path=requirements.md`);
626
+ console.log(`๐Ÿ“ Using project directory: ${projectPath}`);
627
+ console.log(`๐Ÿ—๏ธ Mode: ${isScaffoldMode ? 'Scaffold (infrastructure + logic)' : 'Logic-only (smart layer detection)'}\n`);
628
+ // 2. Get requirements content (from prompt or file)
629
+ let requirementsContent;
630
+ if (options.prompt && options.path) {
631
+ throw new Error('Cannot use both --prompt and --path. Please provide only one:\n' +
632
+ ' kosuke tickets --prompt="Add dark mode"\n' +
633
+ ' kosuke tickets --path=docs.md');
634
+ }
635
+ if (options.prompt) {
636
+ requirementsContent = options.prompt;
637
+ console.log(`๐Ÿ“ Using inline prompt (${requirementsContent.length} characters)\n`);
638
+ }
639
+ else if (options.path) {
640
+ const requirementsPath = join(projectPath, options.path);
641
+ if (!existsSync(requirementsPath)) {
642
+ throw new Error(`Requirements document not found: ${options.path}\n` +
643
+ `Please provide a valid path using --path=<file>\n` +
644
+ `Example: kosuke tickets --path=requirements.md`);
645
+ }
646
+ requirementsContent = readFileSync(requirementsPath, 'utf-8');
647
+ console.log(`๐Ÿ“„ Loaded ${options.path} (${requirementsContent.length} characters)\n`);
648
+ }
649
+ else {
650
+ // Default to docs.md if neither prompt nor path provided
651
+ const defaultPath = 'docs.md';
652
+ const requirementsPath = join(projectPath, defaultPath);
653
+ if (!existsSync(requirementsPath)) {
654
+ throw new Error('Requirements not provided. Use either:\n' +
655
+ ' --prompt="Your requirements here"\n' +
656
+ ' --path=requirements.md\n' +
657
+ ' Or create a docs.md file in the project directory');
658
+ }
659
+ requirementsContent = readFileSync(requirementsPath, 'utf-8');
660
+ console.log(`๐Ÿ“„ Loaded ${defaultPath} (${requirementsContent.length} characters)\n`);
358
661
  }
359
- const requirementsContent = readFileSync(requirementsPath, 'utf-8');
360
- console.log(` โœ… Loaded ${path} (${requirementsContent.length} characters)\n`);
361
662
  // 3. Determine output path for incremental writes
362
663
  const outputFilename = options.output || 'tickets.json';
363
664
  const outputPath = join(projectPath, outputFilename);
364
- // 4. Generate tickets in six phases (scaffold + logic for each)
665
+ // 4. Analyze required layers (only in logic-only mode)
666
+ let layerAnalysis = null;
667
+ if (!isScaffoldMode) {
668
+ layerAnalysis = await analyzeRequiredLayers(requirementsContent, projectPath);
669
+ console.log(`\n๐Ÿ“Š Layer Analysis:`);
670
+ console.log(` Schema (DB): ${layerAnalysis.needsSchema ? 'โœ… Required' : 'โญ๏ธ Skip'}`);
671
+ console.log(` Backend (API): ${layerAnalysis.needsBackend ? 'โœ… Required' : 'โญ๏ธ Skip'}`);
672
+ console.log(` Frontend (UI): ${layerAnalysis.needsFrontend ? 'โœ… Required' : 'โญ๏ธ Skip'}`);
673
+ console.log(`\n๐Ÿ’ญ Reasoning: ${layerAnalysis.reasoning}\n`);
674
+ }
675
+ // 5. Generate tickets in the new structure with tests
365
676
  let totalInputTokens = 0;
366
677
  let totalOutputTokens = 0;
367
678
  let totalCacheCreationTokens = 0;
@@ -371,70 +682,125 @@ export async function ticketsCore(options) {
371
682
  const allConversationMessages = [];
372
683
  // Track all tickets
373
684
  let allTickets = [];
374
- // SCAFFOLD TICKETS (Infrastructure changes)
375
- // Phase 1: Schema Scaffold
376
- const schemaScaffoldResult = await generatePhaseTickets('schema', 'scaffold', requirementsContent, projectPath, outputPath, allTickets);
377
- allTickets = [...allTickets, ...schemaScaffoldResult.tickets];
378
- totalInputTokens += schemaScaffoldResult.tokensUsed.input;
379
- totalOutputTokens += schemaScaffoldResult.tokensUsed.output;
380
- totalCacheCreationTokens += schemaScaffoldResult.tokensUsed.cacheCreation;
381
- totalCacheReadTokens += schemaScaffoldResult.tokensUsed.cacheRead;
382
- totalCost += schemaScaffoldResult.cost;
383
- allConversationMessages.push(...schemaScaffoldResult.conversationMessages);
384
- // Phase 2: Backend Scaffold
385
- const backendScaffoldResult = await generatePhaseTickets('backend', 'scaffold', requirementsContent, projectPath, outputPath, allTickets);
386
- allTickets = [...allTickets, ...backendScaffoldResult.tickets];
387
- totalInputTokens += backendScaffoldResult.tokensUsed.input;
388
- totalOutputTokens += backendScaffoldResult.tokensUsed.output;
389
- totalCacheCreationTokens += backendScaffoldResult.tokensUsed.cacheCreation;
390
- totalCacheReadTokens += backendScaffoldResult.tokensUsed.cacheRead;
391
- totalCost += backendScaffoldResult.cost;
392
- allConversationMessages.push(...backendScaffoldResult.conversationMessages);
393
- // Phase 3: Frontend Scaffold
394
- const frontendScaffoldResult = await generatePhaseTickets('frontend', 'scaffold', requirementsContent, projectPath, outputPath, allTickets);
395
- allTickets = [...allTickets, ...frontendScaffoldResult.tickets];
396
- totalInputTokens += frontendScaffoldResult.tokensUsed.input;
397
- totalOutputTokens += frontendScaffoldResult.tokensUsed.output;
398
- totalCacheCreationTokens += frontendScaffoldResult.tokensUsed.cacheCreation;
399
- totalCacheReadTokens += frontendScaffoldResult.tokensUsed.cacheRead;
400
- totalCost += frontendScaffoldResult.cost;
401
- allConversationMessages.push(...frontendScaffoldResult.conversationMessages);
402
- // LOGIC TICKETS (Business functionality)
403
- // Phase 4: Schema Logic
404
- const schemaLogicResult = await generatePhaseTickets('schema', 'logic', requirementsContent, projectPath, outputPath, allTickets);
405
- allTickets = [...allTickets, ...schemaLogicResult.tickets];
406
- totalInputTokens += schemaLogicResult.tokensUsed.input;
407
- totalOutputTokens += schemaLogicResult.tokensUsed.output;
408
- totalCacheCreationTokens += schemaLogicResult.tokensUsed.cacheCreation;
409
- totalCacheReadTokens += schemaLogicResult.tokensUsed.cacheRead;
410
- totalCost += schemaLogicResult.cost;
411
- allConversationMessages.push(...schemaLogicResult.conversationMessages);
412
- // Phase 5: Backend Logic
413
- const backendLogicResult = await generatePhaseTickets('backend', 'logic', requirementsContent, projectPath, outputPath, allTickets);
414
- allTickets = [...allTickets, ...backendLogicResult.tickets];
415
- totalInputTokens += backendLogicResult.tokensUsed.input;
416
- totalOutputTokens += backendLogicResult.tokensUsed.output;
417
- totalCacheCreationTokens += backendLogicResult.tokensUsed.cacheCreation;
418
- totalCacheReadTokens += backendLogicResult.tokensUsed.cacheRead;
419
- totalCost += backendLogicResult.cost;
420
- allConversationMessages.push(...backendLogicResult.conversationMessages);
421
- // Phase 6: Frontend Logic
422
- const frontendLogicResult = await generatePhaseTickets('frontend', 'logic', requirementsContent, projectPath, outputPath, allTickets);
423
- allTickets = [...allTickets, ...frontendLogicResult.tickets];
424
- totalInputTokens += frontendLogicResult.tokensUsed.input;
425
- totalOutputTokens += frontendLogicResult.tokensUsed.output;
426
- totalCacheCreationTokens += frontendLogicResult.tokensUsed.cacheCreation;
427
- totalCacheReadTokens += frontendLogicResult.tokensUsed.cacheRead;
428
- totalCost += frontendLogicResult.cost;
429
- allConversationMessages.push(...frontendLogicResult.conversationMessages);
685
+ // Helper to add metrics from a phase result
686
+ const addMetrics = (result) => {
687
+ allTickets = [...allTickets, ...result.tickets];
688
+ totalInputTokens += result.tokensUsed.input;
689
+ totalOutputTokens += result.tokensUsed.output;
690
+ totalCacheCreationTokens += result.tokensUsed.cacheCreation;
691
+ totalCacheReadTokens += result.tokensUsed.cacheRead;
692
+ totalCost += result.cost;
693
+ allConversationMessages.push(...result.conversationMessages);
694
+ };
695
+ // ==================== SCAFFOLD MODE ====================
696
+ if (isScaffoldMode) {
697
+ console.log('\n' + '='.repeat(80));
698
+ console.log('๐Ÿ—๏ธ SCAFFOLD BATCH - Infrastructure Setup');
699
+ console.log('='.repeat(80));
700
+ // Track batch tickets for test generation
701
+ let batchSchemaTickets = [];
702
+ let batchBackendTickets = [];
703
+ let batchFrontendTickets = [];
704
+ // 1. Schema Scaffold
705
+ const schemaScaffoldResult = await generatePhaseTickets('schema', 'scaffold', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode);
706
+ addMetrics(schemaScaffoldResult);
707
+ batchSchemaTickets = schemaScaffoldResult.tickets;
708
+ // 2. DB Test (validate scaffold schema)
709
+ addMetrics(await generatePhaseTickets('db-test', 'scaffold', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode, { schema: batchSchemaTickets }));
710
+ // 3. Backend Scaffold
711
+ const backendScaffoldResult = await generatePhaseTickets('backend', 'scaffold', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode);
712
+ addMetrics(backendScaffoldResult);
713
+ batchBackendTickets = backendScaffoldResult.tickets;
714
+ // 4. Frontend Scaffold
715
+ const frontendScaffoldResult = await generatePhaseTickets('frontend', 'scaffold', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode);
716
+ addMetrics(frontendScaffoldResult);
717
+ batchFrontendTickets = frontendScaffoldResult.tickets;
718
+ // 5. Web Tests (validate scaffold E2E)
719
+ addMetrics(await generatePhaseTickets('web-test', 'scaffold', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode, {
720
+ backend: batchBackendTickets,
721
+ frontend: batchFrontendTickets,
722
+ webTestStartNumber: 1,
723
+ }));
724
+ // ==================== LOGIC BATCH ====================
725
+ console.log('\n' + '='.repeat(80));
726
+ console.log('๐Ÿ’ก LOGIC BATCH - Business Functionality');
727
+ console.log('='.repeat(80));
728
+ // Reset batch tracking for logic
729
+ batchSchemaTickets = [];
730
+ batchBackendTickets = [];
731
+ batchFrontendTickets = [];
732
+ // 1. Schema Logic
733
+ const schemaLogicResult = await generatePhaseTickets('schema', 'logic', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode);
734
+ addMetrics(schemaLogicResult);
735
+ batchSchemaTickets = schemaLogicResult.tickets;
736
+ // 2. DB Test (validate logic schema)
737
+ addMetrics(await generatePhaseTickets('db-test', 'logic', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode, { schema: batchSchemaTickets }));
738
+ // 3. Backend Logic
739
+ const backendLogicResult = await generatePhaseTickets('backend', 'logic', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode);
740
+ addMetrics(backendLogicResult);
741
+ batchBackendTickets = backendLogicResult.tickets;
742
+ // 4. Frontend Logic
743
+ const frontendLogicResult = await generatePhaseTickets('frontend', 'logic', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode);
744
+ addMetrics(frontendLogicResult);
745
+ batchFrontendTickets = frontendLogicResult.tickets;
746
+ // 5. Web Tests (validate logic E2E)
747
+ const currentWebTestCount = allTickets.filter((t) => t.id.startsWith('WEB-TEST-')).length;
748
+ addMetrics(await generatePhaseTickets('web-test', 'logic', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode, {
749
+ backend: batchBackendTickets,
750
+ frontend: batchFrontendTickets,
751
+ webTestStartNumber: currentWebTestCount + 1,
752
+ }));
753
+ }
754
+ else {
755
+ // ==================== LOGIC-ONLY MODE ====================
756
+ console.log('\n' + '='.repeat(80));
757
+ console.log('๐Ÿ’ก LOGIC-ONLY MODE - Smart Layer Detection');
758
+ console.log('='.repeat(80));
759
+ // Track tickets for test generation
760
+ let schemaTickets = [];
761
+ let backendTickets = [];
762
+ let frontendTickets = [];
763
+ // 1. Schema Logic (if needed)
764
+ if (layerAnalysis?.needsSchema) {
765
+ const schemaLogicResult = await generatePhaseTickets('schema', 'logic', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode);
766
+ addMetrics(schemaLogicResult);
767
+ schemaTickets = schemaLogicResult.tickets;
768
+ // DB Test (validate schema)
769
+ if (schemaTickets.length > 0) {
770
+ addMetrics(await generatePhaseTickets('db-test', 'logic', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode, { schema: schemaTickets }));
771
+ }
772
+ }
773
+ // 2. Backend Logic (if needed)
774
+ if (layerAnalysis?.needsBackend) {
775
+ const backendLogicResult = await generatePhaseTickets('backend', 'logic', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode);
776
+ addMetrics(backendLogicResult);
777
+ backendTickets = backendLogicResult.tickets;
778
+ }
779
+ // 3. Frontend Logic (if needed)
780
+ if (layerAnalysis?.needsFrontend) {
781
+ const frontendLogicResult = await generatePhaseTickets('frontend', 'logic', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode);
782
+ addMetrics(frontendLogicResult);
783
+ frontendTickets = frontendLogicResult.tickets;
784
+ }
785
+ // 4. Web Tests (if backend or frontend tickets generated)
786
+ if (backendTickets.length > 0 || frontendTickets.length > 0) {
787
+ addMetrics(await generatePhaseTickets('web-test', 'logic', requirementsContent, projectPath, outputPath, allTickets, isScaffoldMode, {
788
+ backend: backendTickets,
789
+ frontend: frontendTickets,
790
+ webTestStartNumber: 1,
791
+ }));
792
+ }
793
+ }
430
794
  // Separate tickets by phase for result
431
795
  const schemaTickets = allTickets.filter((t) => t.id.startsWith('SCHEMA-'));
432
796
  const backendTickets = allTickets.filter((t) => t.id.startsWith('BACKEND-'));
433
797
  const frontendTickets = allTickets.filter((t) => t.id.startsWith('FRONTEND-'));
798
+ const testTickets = allTickets.filter((t) => t.id.startsWith('DB-TEST-') || t.id.startsWith('WEB-TEST-'));
434
799
  return {
435
800
  schemaTickets,
436
801
  backendTickets,
437
802
  frontendTickets,
803
+ testTickets,
438
804
  totalTickets: allTickets.length,
439
805
  projectPath,
440
806
  tokensUsed: {
@@ -477,6 +843,8 @@ export async function ticketsCommand(options) {
477
843
  ...result.backendTickets,
478
844
  ...result.frontendTickets,
479
845
  ].filter((t) => t.type === 'logic');
846
+ const dbTestTickets = result.testTickets.filter((t) => t.type === 'db-test');
847
+ const webTestTickets = result.testTickets.filter((t) => t.type === 'web-test');
480
848
  console.log(`\n${'='.repeat(60)}`);
481
849
  console.log('๐Ÿ“Š Ticket Generation Summary');
482
850
  console.log(`${'='.repeat(60)}`);
@@ -488,6 +856,9 @@ export async function ticketsCommand(options) {
488
856
  console.log(` ๐Ÿ—„๏ธ Schema: ${result.schemaTickets.filter((t) => t.type === 'logic').length}`);
489
857
  console.log(` โš™๏ธ Backend: ${result.backendTickets.filter((t) => t.type === 'logic').length}`);
490
858
  console.log(` ๐ŸŽจ Frontend: ${result.frontendTickets.filter((t) => t.type === 'logic').length}`);
859
+ console.log(`\n๐Ÿงช Test Tickets: ${result.testTickets.length}`);
860
+ console.log(` ๐Ÿงช Database Tests: ${dbTestTickets.length}`);
861
+ console.log(` ๐ŸŒ Web Tests: ${webTestTickets.length}`);
491
862
  console.log(`\n๐Ÿ“ Total Tickets: ${result.totalTickets}`);
492
863
  console.log(`${'='.repeat(60)}\n`);
493
864
  // Display cost breakdown