@anhth2/spec-driven-dev-plugin 0.6.0 → 0.8.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.
Files changed (86) hide show
  1. package/bin/index.js +285 -11
  2. package/commands/debug.md +233 -11
  3. package/commands/debug.tmpl +170 -6
  4. package/commands/define-product.md +68 -6
  5. package/commands/define-product.tmpl +5 -1
  6. package/commands/fix-bug.md +111 -11
  7. package/commands/fix-bug.tmpl +48 -6
  8. package/commands/generate-bdd.md +86 -9
  9. package/commands/generate-bdd.tmpl +23 -4
  10. package/commands/generate-code.md +146 -19
  11. package/commands/generate-code.tmpl +83 -14
  12. package/commands/generate-design-spec.md +754 -0
  13. package/commands/generate-design-spec.tmpl +399 -0
  14. package/commands/generate-prd.md +91 -7
  15. package/commands/generate-prd.tmpl +28 -2
  16. package/commands/generate-spec-manifest.md +519 -0
  17. package/commands/generate-spec-manifest.tmpl +164 -0
  18. package/commands/generate-tech-docs.md +122 -9
  19. package/commands/generate-tech-docs.tmpl +59 -4
  20. package/commands/generate-tests.md +491 -37
  21. package/commands/generate-tests.tmpl +428 -32
  22. package/commands/refine-prd.md +76 -8
  23. package/commands/refine-prd.tmpl +13 -3
  24. package/commands/review-code.md +94 -6
  25. package/commands/review-code.tmpl +31 -1
  26. package/commands/review-context.md +118 -12
  27. package/commands/review-context.tmpl +55 -7
  28. package/commands/review-tech-docs.md +76 -9
  29. package/commands/review-tech-docs.tmpl +13 -4
  30. package/commands/run-tests.md +196 -18
  31. package/commands/run-tests.tmpl +133 -13
  32. package/commands/setup-ai-first.md +192 -6
  33. package/commands/setup-ai-first.tmpl +136 -5
  34. package/commands/smoke-test.md +228 -22
  35. package/commands/smoke-test.tmpl +165 -17
  36. package/commands/validate-traces.md +77 -8
  37. package/commands/validate-traces.tmpl +14 -3
  38. package/core/FRAMEWORK_VERSION +1 -1
  39. package/core/commands/debug.md +233 -11
  40. package/core/commands/define-product.md +68 -6
  41. package/core/commands/fix-bug.md +111 -11
  42. package/core/commands/generate-bdd.md +86 -9
  43. package/core/commands/generate-code.md +146 -19
  44. package/core/commands/generate-design-spec.md +754 -0
  45. package/core/commands/generate-prd.md +91 -7
  46. package/core/commands/generate-spec-manifest.md +519 -0
  47. package/core/commands/generate-tech-docs.md +122 -9
  48. package/core/commands/generate-tests.md +491 -37
  49. package/core/commands/refine-prd.md +76 -8
  50. package/core/commands/review-code.md +94 -6
  51. package/core/commands/review-context.md +118 -12
  52. package/core/commands/review-tech-docs.md +76 -9
  53. package/core/commands/run-tests.md +196 -18
  54. package/core/commands/setup-ai-first.md +192 -6
  55. package/core/commands/smoke-test.md +228 -22
  56. package/core/commands/validate-traces.md +77 -8
  57. package/core/skills/code/SKILL.md +68 -8
  58. package/core/skills/debug/SKILL.md +72 -10
  59. package/core/skills/design-spec/SKILL.md +450 -0
  60. package/core/skills/discovery/SKILL.md +62 -4
  61. package/core/skills/prd/SKILL.md +12 -8
  62. package/core/skills/setup-ai-first/SKILL.md +5 -3
  63. package/core/skills/spec/SKILL.md +11 -7
  64. package/core/skills/test/SKILL.md +130 -12
  65. package/core/steps/context-loader.md +57 -1
  66. package/core/steps/gate.md +1 -1
  67. package/core/steps/report-footer.md +5 -3
  68. package/core/steps/spawn-agent.md +3 -1
  69. package/core/templates/design-spec.template.md +209 -0
  70. package/core/templates/project-context.yaml +29 -0
  71. package/package.json +1 -1
  72. package/skills/code/SKILL.md +68 -8
  73. package/skills/debug/SKILL.md +72 -10
  74. package/skills/design-spec/SKILL.md +450 -0
  75. package/skills/design-spec/SKILL.tmpl +95 -0
  76. package/skills/discovery/SKILL.md +62 -4
  77. package/skills/prd/SKILL.md +12 -8
  78. package/skills/setup-ai-first/SKILL.md +5 -3
  79. package/skills/spec/SKILL.md +11 -7
  80. package/skills/test/SKILL.md +130 -12
  81. package/steps/context-loader.md +57 -1
  82. package/steps/gate.md +1 -1
  83. package/steps/report-footer.md +5 -3
  84. package/steps/spawn-agent.md +3 -1
  85. package/templates/design-spec.template.md +209 -0
  86. package/templates/project-context.yaml +29 -0
@@ -3,55 +3,451 @@
3
3
  ## Gate
4
4
  {{include:steps/gate.md}}
5
5
 
6
- *Note: For this command, the target in Step 1 is a UC-ID or class path. Find files to test: controllers with `@trace.implements={UC-ID}`, service/facade implementations, and the `.feature` file at `{paths.specs_dir}/{domain}/{UC-ID}.feature`.*
6
+ *Note: For this command, the target in Step 1 is a UC-ID or `.feature` file path. Find the feature file at `{paths.specs_dir}/{domain}/{UC-ID}-*.feature` (glob match — filename includes slug suffix) and the implementation files tagged `@trace.implements={UC-ID}`.*
7
7
 
8
8
  ## Context
9
9
  {{include:steps/context-loader.md}}
10
10
 
11
11
  ---
12
12
 
13
+ ## Service Detection
14
+
15
+ Read `@trace.service` and `@trace.module` from the feature file header.
16
+
17
+ | Condition | Action |
18
+ |---|---|
19
+ | `@trace.module` present | Use as `active_module` |
20
+ | `@trace.module` absent | Use `tech_stack.module` from project-context.yaml |
21
+ | `@trace.service` present | Store as `active_service` |
22
+ | `@trace.service` absent | Use `{UC-ID}` domain as fallback |
23
+
24
+ **Platform type classification:**
25
+
26
+ | Platform | Modules |
27
+ |---|---|
28
+ | `backend` | `java-spring`, `golang`, `dotnet`, `php-laravel`, `context-engineering` |
29
+ | `web-frontend` | `react`, `nextjs`, `vue`, `nuxt`, `angular` |
30
+ | `mobile` | `flutter`, `react-native`, `ios-swiftui`, `android-compose` |
31
+
32
+ ---
33
+
13
34
  ## CHECKPOINT — Test Plan
35
+
36
+ Before generating, scan the feature file for scenarios and the implementation files for classes/functions. Display:
37
+
14
38
  ```
15
- Test Plan — {UC-ID}:
16
- Unit Tests:
17
- - {Service}ImplTest: {method} {scenario} {expected}
18
- Integration Tests:
19
- - {Controller}Test: {endpoint} {scenario} HTTP {status}
20
- Total: {N} classes, ~{M} test cases. Proceed? (Y/N)
39
+ Test Plan — {UC-ID} ({active_module})
40
+ ──────────────────────────────────────
41
+ Platform : {backend | web-frontend | mobile}
42
+ Scenarios : {N} scenarios from .feature file
43
+ Impl files : {list of files tagged @trace.implements={UC-ID}}
44
+
45
+ Tests to generate:
46
+ {platform-specific list — see templates below}
47
+
48
+ Proceed? (Y/N)
21
49
  ```
22
50
 
51
+ Wait for explicit "Y" before generating.
52
+
53
+ ---
54
+
23
55
  ## Generate
24
56
 
25
- ### Unit Test Template
26
- ```
57
+ ### If `platform_type = backend`
58
+
59
+ #### java-spring
60
+
61
+ ```java
27
62
  // @trace.verifies={UC-ID}
63
+ // @trace.service={active_service}
28
64
  // @trace.test_type=unit
29
65
  class {Resource}ServiceImplTest {
30
- // Mock dependencies
31
- // Test: methodName_whenValid_shouldReturnExpected()
32
- // Given — set up mocks | When — call method | Then — assert
33
- // Test: methodName_whenNotFound_shouldThrowException()
34
- // Given — mock returns empty | When & Then — assert exception
66
+ @Mock {Repository} {repository};
67
+ @InjectMocks {Resource}ServiceImpl service;
68
+
69
+ // methodName_whenValid_shouldReturnExpected()
70
+ // Given — mock repository returns data
71
+ // When — call service method
72
+ // Then — assert result matches expected
73
+
74
+ // methodName_whenNotFound_shouldThrowException()
75
+ // Given — mock returns Optional.empty()
76
+ // When & Then — assertThrows({NotFoundException}.class, ...)
77
+ }
78
+
79
+ // @trace.verifies={UC-ID}
80
+ // @trace.service={active_service}
81
+ // @trace.test_type=integration
82
+ @WebMvcTest({Resource}Controller.class)
83
+ class {Resource}ControllerTest {
84
+ @MockBean {Facade | Service} facade;
85
+
86
+ // endpoint_shouldReturn200WhenValid()
87
+ // endpoint_shouldReturn400WhenInvalid()
88
+ // endpoint_shouldReturn404WhenNotFound()
89
+ // endpoint_shouldReturn401WhenUnauthenticated()
35
90
  }
36
91
  ```
37
92
 
38
- ### Integration Test Template (HTTP layer)
93
+ Rules:
94
+ - Unit tests: mock at Repository layer, test Service logic
95
+ - Integration tests: mock at Facade/Service layer, test HTTP contract only
96
+ - Never mock the class under test
97
+ - Follow naming: `methodName_whenCondition_shouldOutcome` (from CLAUDE.md §6)
98
+
99
+ #### golang
100
+
101
+ ```go
102
+ // @trace.verifies={UC-ID}
103
+ // Unit: table-driven tests for service layer
104
+ func Test{Resource}Service_{Method}(t *testing.T) {
105
+ tests := []struct {
106
+ name string
107
+ input {InputType}
108
+ want {OutputType}
109
+ wantErr bool
110
+ }{
111
+ {"valid input", ..., ..., false},
112
+ {"not found", ..., nil, true},
113
+ }
114
+ for _, tt := range tests {
115
+ t.Run(tt.name, func(t *testing.T) { ... })
116
+ }
117
+ }
118
+
119
+ // @trace.verifies={UC-ID}
120
+ // Integration: HTTP handler tests using httptest
121
+ func Test{Resource}Handler_{Endpoint}(t *testing.T) {
122
+ // setup router, mock service, fire request
123
+ // assert status code and response body
124
+ }
39
125
  ```
126
+
127
+ #### dotnet
128
+
129
+ ```csharp
130
+ // @trace.verifies={UC-ID}
131
+ // Unit: xUnit + Moq
132
+ public class {Resource}ServiceTests {
133
+ private readonly Mock<I{Repository}> _repoMock = new();
134
+ private readonly {Resource}Service _sut;
135
+
136
+ [Fact]
137
+ public async Task {Method}_WhenValid_Returns{Expected}() { }
138
+
139
+ [Fact]
140
+ public async Task {Method}_WhenNotFound_ThrowsNotFoundException() { }
141
+ }
142
+
143
+ // @trace.verifies={UC-ID}
144
+ // Integration: WebApplicationFactory
145
+ public class {Resource}ControllerTests : IClassFixture<WebApplicationFactory<Program>> {
146
+ [Fact]
147
+ public async Task {Endpoint}_Returns200_WhenValid() { }
148
+
149
+ [Fact]
150
+ public async Task {Endpoint}_Returns400_WhenInvalid() { }
151
+ }
152
+ ```
153
+
154
+ #### php-laravel
155
+
156
+ ```php
157
+ // @trace.verifies={UC-ID}
158
+ // Unit: PHPUnit
159
+ class {Resource}ServiceTest extends TestCase {
160
+ public function test_{method}_when_valid_should_return_expected(): void { }
161
+ public function test_{method}_when_not_found_should_throw(): void { }
162
+ }
163
+
164
+ // @trace.verifies={UC-ID}
165
+ // Feature: Laravel HTTP tests
166
+ class {Resource}ControllerTest extends TestCase {
167
+ use RefreshDatabase;
168
+ public function test_{endpoint}_returns_200_when_valid(): void {
169
+ $response = $this->getJson('/api/{resource}');
170
+ $response->assertStatus(200)->assertJsonStructure([...]);
171
+ }
172
+ }
173
+ ```
174
+
175
+ #### context-engineering
176
+
177
+ Check `tech_stack.language` from project-context.yaml to select the correct test syntax:
178
+
179
+ **If language = Python** (default):
180
+
181
+ ```python
182
+ # @trace.verifies={UC-ID}
183
+ # @trace.test_type=unit
184
+ # Unit: test prompt orchestration functions
185
+
186
+ import pytest
187
+ from unittest.mock import patch, MagicMock
188
+
189
+ class Test{Resource}Prompt:
190
+ # test_{function}_when_valid_input_should_return_expected_output()
191
+ # Given — mock LLM client returns controlled response
192
+ # When — call prompt function with valid input
193
+ # Then — assert output matches expected structure/content
194
+
195
+ # test_{function}_when_llm_unavailable_should_raise()
196
+ # Given — mock LLM client raises connection error
197
+ # When & Then — assert specific exception is raised
198
+
199
+ # test_{function}_trace_assertions()
200
+ # Given — run function with trace capture enabled
201
+ # Then — assert @trace.implements tag present in function definition
202
+ # assert output conforms to expected schema
203
+
204
+ def test_{function}_when_valid_should_return_expected(self, mock_llm):
205
+ # Arrange
206
+ mock_llm.complete.return_value = "{expected response}"
207
+ # Act
208
+ result = {function}(input={test_input})
209
+ # Assert
210
+ assert result == {expected_output}
211
+ mock_llm.complete.assert_called_once_with(...)
212
+
213
+ def test_{function}_when_invalid_input_should_raise(self):
214
+ with pytest.raises({ExpectedError}):
215
+ {function}(input=None)
216
+ ```
217
+
218
+ **If language = TypeScript / JavaScript** (Node.js LangChain.js, etc.):
219
+
220
+ ```typescript
221
+ // @trace.verifies={UC-ID}
222
+ // @trace.test_type=unit
223
+ import { jest } from '@jest/globals'
224
+
225
+ describe('{Resource}Prompt', () => {
226
+ const mockLlm = { complete: jest.fn() }
227
+
228
+ it('{scenario description}', async () => {
229
+ mockLlm.complete.mockResolvedValue('{expected response}')
230
+ const result = await {function}({ input: '{test_input}', llm: mockLlm })
231
+ expect(result).toEqual({expected_output})
232
+ expect(mockLlm.complete).toHaveBeenCalledWith(expect.objectContaining({ ... }))
233
+ })
234
+
235
+ it('throws when input is invalid', async () => {
236
+ await expect({function}({ input: null, llm: mockLlm })).rejects.toThrow('{ExpectedError}')
237
+ })
238
+ })
239
+ ```
240
+
241
+ **If language = Java** (LangChain4j, etc.): use JUnit 5 + Mockito, same patterns as java-spring unit tests above — mock the `ChatLanguageModel` interface.
242
+
243
+ Rules:
244
+ - Mock the LLM client at the boundary — never call real LLM in unit tests
245
+ - Validate input schema and output schema separately
246
+ - Each scenario in `.feature` maps to one test function
247
+ - Use parameterized tests for multiple input variants
248
+
249
+ ---
250
+
251
+ ### If `platform_type = web-frontend`
252
+
253
+ #### react / nextjs / vue / nuxt / angular
254
+
255
+ ```typescript
256
+ // @trace.verifies={UC-ID}
257
+ // @trace.service={active_service}
258
+ // @trace.test_type=component
259
+
260
+ // Component tests (Vitest + Testing Library)
261
+ describe('{ComponentName}', () => {
262
+ it('renders correctly when data is loaded', () => {
263
+ render(<{ComponentName} {...props} />)
264
+ expect(screen.getByText('...')).toBeInTheDocument()
265
+ })
266
+
267
+ it('shows loading state while fetching', () => { })
268
+
269
+ it('shows error message on API failure', () => { })
270
+
271
+ it('calls handler when user interacts', async () => {
272
+ await userEvent.click(screen.getByRole('button', { name: '...' }))
273
+ expect(mockHandler).toHaveBeenCalledWith(...)
274
+ })
275
+ })
276
+
277
+ // @trace.verifies={UC-ID}
278
+ // @trace.test_type=e2e
279
+
280
+ // E2E tests (Playwright or Cypress — use whichever is in project)
281
+ test('{scenario from .feature}', async ({ page }) => {
282
+ await page.goto('/{route}')
283
+ await page.getByRole('button', { name: '...' }).click()
284
+ await expect(page.getByText('...')).toBeVisible()
285
+ })
286
+ ```
287
+
288
+ Rules:
289
+ - One component test file per component involved in the UC
290
+ - One E2E test file per UC covering happy path + key error scenarios
291
+ - Mock API calls at the network layer (MSW or cy.intercept), not at component props
292
+ - Use accessible queries (`getByRole`, `getByLabelText`) — avoid `getByTestId` unless necessary
293
+ - Each test maps to exactly one scenario in the `.feature` file
294
+
295
+ ---
296
+
297
+ ### If `platform_type = mobile`
298
+
299
+ #### flutter
300
+
301
+ ```dart
302
+ // @trace.verifies={UC-ID}
303
+ // @trace.service={active_service}
304
+ // @trace.test_type=widget
305
+
306
+ // Widget tests
307
+ group('{FeatureName} widget tests', () {
308
+ testWidgets('renders correctly when state is loaded', (tester) async {
309
+ await tester.pumpWidget(MaterialApp(home: {Widget}()));
310
+ await tester.pumpAndSettle();
311
+ expect(find.text('...'), findsOneWidget);
312
+ });
313
+
314
+ testWidgets('shows loading indicator while fetching', (tester) async { });
315
+ testWidgets('shows error widget on failure', (tester) async { });
316
+ testWidgets('calls handler on tap', (tester) async {
317
+ await tester.tap(find.byType(ElevatedButton));
318
+ await tester.pumpAndSettle();
319
+ verify(() => mockBloc.add({Event}())).called(1);
320
+ });
321
+ });
322
+
40
323
  // @trace.verifies={UC-ID}
41
324
  // @trace.test_type=integration
42
- class {Resource}ControllerTest {
43
- // Mock Facade/Service
44
- // Test: filter_shouldReturn200()
45
- // Test: create_shouldReturn201()
46
- // Test: create_shouldReturn400WhenInvalid()
325
+ // Integration test (flutter_test / integration_test package)
326
+ void main() {
327
+ IntegrationTestWidgetsFlutterBinding.ensureInitialized();
328
+ testWidgets('{scenario from .feature}', (tester) async {
329
+ app.main();
330
+ await tester.pumpAndSettle();
331
+ // Navigate, interact, assert
332
+ });
333
+ }
334
+ ```
335
+
336
+ #### react-native
337
+
338
+ ```typescript
339
+ // @trace.verifies={UC-ID}
340
+ // @trace.service={active_service}
341
+ // @trace.test_type=component
342
+
343
+ // Jest + React Native Testing Library
344
+ describe('{ComponentName}', () => {
345
+ it('{scenario description}', () => {
346
+ const { getByText, getByRole } = render(<{ComponentName} {...props} />)
347
+ expect(getByText('...')).toBeTruthy()
348
+ })
349
+
350
+ it('calls navigation on button press', () => {
351
+ const mockNavigate = jest.fn()
352
+ const { getByRole } = render(<{ComponentName} navigation={{ navigate: mockNavigate }} />)
353
+ fireEvent.press(getByRole('button'))
354
+ expect(mockNavigate).toHaveBeenCalledWith('...')
355
+ })
356
+ })
357
+ ```
358
+
359
+ #### ios-swiftui
360
+
361
+ ```swift
362
+ // @trace.verifies={UC-ID}
363
+ // @trace.service={active_service}
364
+ // @trace.test_type=unit
365
+
366
+ // XCTest — ViewModel unit tests
367
+ @MainActor
368
+ final class {Feature}ViewModelTests: XCTestCase {
369
+ var sut: {Feature}ViewModel!
370
+ var mockRepo: Mock{Repository}!
371
+
372
+ override func setUp() async throws {
373
+ mockRepo = Mock{Repository}()
374
+ sut = {Feature}ViewModel(repository: mockRepo)
375
+ }
376
+
377
+ func test_{method}_whenValid_shouldUpdate{State}() async throws {
378
+ // Given
379
+ mockRepo.stub{Method}Result = {expected}
380
+ // When
381
+ await sut.{method}()
382
+ // Then
383
+ XCTAssertEqual(sut.{state}, {expected})
384
+ }
385
+
386
+ func test_{method}_whenError_shouldSetErrorState() async throws { }
387
+ }
388
+ ```
389
+
390
+ #### android-compose
391
+
392
+ ```kotlin
393
+ // @trace.verifies={UC-ID}
394
+ // @trace.service={active_service}
395
+ // @trace.test_type=unit
396
+
397
+ // Unit test — ViewModel
398
+ class {Feature}ViewModelTest {
399
+ @get:Rule val mainDispatcherRule = MainDispatcherRule()
400
+ private val mockRepo: {Repository} = mockk()
401
+ private lateinit var sut: {Feature}ViewModel
402
+
403
+ @Before fun setup() { sut = {Feature}ViewModel(mockRepo) }
404
+
405
+ @Test fun `{method} when valid should emit success state`() = runTest {
406
+ coEvery { mockRepo.{method}(any()) } returns Result.success({data})
407
+ sut.{method}({input})
408
+ assertEquals(UiState.Success({data}), sut.uiState.value)
409
+ }
410
+ }
411
+
412
+ // @trace.verifies={UC-ID}
413
+ // @trace.test_type=ui
414
+
415
+ // UI test — Compose
416
+ @HiltAndroidTest
417
+ class {Feature}ScreenTest {
418
+ @get:Rule(order = 0) val hiltRule = HiltAndroidRule(this)
419
+ @get:Rule(order = 1) val composeRule = createAndroidComposeRule<MainActivity>()
420
+
421
+ @Test fun {scenario}_displaysExpectedUi() {
422
+ composeRule.onNodeWithText("...").assertIsDisplayed()
423
+ composeRule.onNodeWithContentDescription("...").performClick()
424
+ composeRule.onNodeWithText("...").assertIsDisplayed()
425
+ }
47
426
  }
48
427
  ```
49
428
 
429
+ ---
430
+
50
431
  ## Checklist
51
- - [ ] `@trace.verifies` on every test class
52
- - [ ] Every .feature scenario has a test
53
- - [ ] Correct layer mocked (no repo mocks in controller tests)
54
- - [ ] Test names follow pattern from CLAUDE.md §6 (e.g., `methodName_whenCondition_shouldOutcome`)
432
+
433
+ **All platforms:**
434
+ - [ ] `@trace.verifies` on every test class / test group
435
+ - [ ] `@trace.service` on every test class / test group
436
+ - [ ] Every scenario in `.feature` file has ≥ 1 corresponding test
437
+ - [ ] Happy path covered
438
+ - [ ] Key error / edge case scenarios covered
439
+
440
+ **Backend only:**
441
+ - [ ] Correct layer mocked (Repository in unit tests, Facade/Service in controller tests)
442
+ - [ ] No real DB calls in unit tests
443
+ - [ ] Test naming follows `methodName_whenCondition_shouldOutcome` (CLAUDE.md §6)
444
+
445
+ **Frontend / Mobile only:**
446
+ - [ ] No hardcoded delays (`sleep`, `setTimeout`) in tests — use `waitFor` / `pumpAndSettle`
447
+ - [ ] Accessible queries used (role, label) not implementation-specific selectors
448
+ - [ ] API calls mocked at network layer, not at component level
449
+
450
+ ---
55
451
 
56
452
  ## Write Trace State
57
453
 
@@ -59,22 +455,22 @@ After generating all test files, update `{paths.trace_dir}/{UC-ID}.tsv` — for
59
455
 
60
456
  | Column | Value |
61
457
  |--------|-------|
62
- | `test_count` | number of test methods covering this SC across all generated test classes |
63
- | `test_classes` | comma-separated list of test class names (e.g., `OrderServiceImplTest,OrderControllerTest`) |
458
+ | `test_count` | number of test methods covering this SC |
459
+ | `test_classes` | comma-separated test class / describe-block names |
64
460
  | `last_updated` | today `YYYY-MM-DD` |
65
461
 
66
- Count `test_count` by scanning generated test files for methods that correspond to this SC (methods whose name or comment references the SC-ID, or all methods in a test class dedicated to this UC).
67
-
68
462
  Leave all other columns unchanged.
69
463
 
464
+ ---
465
+
70
466
  ## Output
71
467
 
72
468
  {{include:steps/report-footer.md}}
73
469
 
74
470
  ```
75
- /generate-tests Complete — {UC-ID}
76
- ✅ {Service}Test ({M} tests)
77
- ✅ {Controller}Test ({M} tests)
78
- Trace: {paths.trace_dir}/{UC-ID}.tsv updated (test_count, test_classes)
471
+ /generate-tests Complete — {UC-ID} ({active_module})
472
+ ✅ {TestClass1} ({N} tests)
473
+ ✅ {TestClass2} ({N} tests)
474
+ Trace: {paths.trace_dir}/{UC-ID}.tsv updated
79
475
  Next: /run-tests {UC-ID}
80
476
  ```
@@ -31,7 +31,7 @@ Display and wait for response:
31
31
  ```
32
32
  ⚙️ MODEL CHECK
33
33
  ──────────────────────────────────────────────────────────────────
34
- Recommended : claude-opus-4-5 (or claude-opus-4)
34
+ Recommended : claude-opus-4 (or latest Opus model)
35
35
  Why needed : Spec analysis, architecture review, code generation
36
36
  require deep reasoning. Smaller models miss edge cases.
37
37
 
@@ -131,6 +131,7 @@ Read `.agent/project-context.yaml`. Extract and store:
131
131
  - `paths.core_entities` → path to core-entities.md
132
132
  - `paths.tech_docs_dir` → technical documentation root
133
133
  - `paths.trace_dir` → trace state directory
134
+ - `paths.design_spec_dir` → Design Spec documents root (FE/App only)
134
135
 
135
136
  If `paths` section is absent, use these defaults:
136
137
  - `specs_dir` = `specs/bdd`
@@ -140,13 +141,46 @@ If `paths` section is absent, use these defaults:
140
141
  - `domain_knowledge_dir` = `specs/domain-knowledge`
141
142
  - `business_dictionary` = `specs/domain-knowledge/business-dictionary.md`
142
143
  - `core_entities` = `specs/domain-knowledge/core-entities.md`
143
- - `tech_docs_dir` = `tech-docs`
144
+ - `tech_docs_dir` = `specs/tech-docs`
144
145
  - `trace_dir` = `.trace`
146
+ - `design_spec_dir` = `specs/design-spec`
145
147
 
146
148
  If `tech_stack.module` is set, also load `.agent/modules/{module}/stack-profile.yaml` if it exists.
147
149
 
148
150
  ---
149
151
 
152
+ ## Step 1.5 — [SERVICE ROUTING] Resolve service paths (umbrella mode)
153
+
154
+ *Skip this step entirely if `setup.mode` is not `"umbrella"` and `services` section is absent from project-context.yaml.*
155
+
156
+ If `services` section is present:
157
+
158
+ **1. Detect active domain** (in priority order):
159
+ - Read `@trace.domain` from target file frontmatter (if Gate loaded a target file)
160
+ - Extract from target file path: segment immediately after `prd_dir` base path
161
+ *(e.g., `specs/prd/user/FEAT-01.md` → domain = `user`)*
162
+ - If `$ARGUMENTS` contains a path, extract the segment after `prd_dir`
163
+
164
+ **2. Route to service** — if active domain matches a key in `services`:
165
+ - Override `paths.specs_dir` → `services.{domain}.specs_dir`
166
+ - Override `paths.tech_docs_dir` → `services.{domain}.tech_docs_dir`
167
+ - Store `active_service` = `services.{domain}.path`
168
+ - Store `active_service_module` = `services.{domain}.module`
169
+ - If service has its own `module` → use it as `active_module` (overrides `tech_stack.module`)
170
+
171
+ **3. Fallback** — if domain not detected or no matching service key:
172
+ - Keep default paths from Step 1
173
+ - Set `active_service = unresolved`
174
+
175
+ **4. Spec source auto-override** — if `setup.spec_source` is set AND the corresponding path was not already explicitly set in `paths:`:
176
+ - Override `paths.prd_dir` → `{spec_source}/specs/prd`
177
+ - Override `paths.design_spec_dir` → `{spec_source}/specs/design-spec`
178
+ - Override `paths.domain_knowledge_dir` → `{spec_source}/specs/domain-knowledge`
179
+ - Override `paths.business_dictionary` → `{spec_source}/specs/domain-knowledge/business-dictionary.md`
180
+ - Override `paths.core_entities` → `{spec_source}/specs/domain-knowledge/core-entities.md`
181
+
182
+ ---
183
+
150
184
  ## Step 2 — [PROJECT-CONFIG] Load module stack profile (conditional)
151
185
 
152
186
  If `tech_stack.module` is set, read `.agent/modules/{module}/stack-profile.yaml`.
@@ -217,6 +251,26 @@ If the file does not exist → skip silently.
217
251
 
218
252
  ---
219
253
 
254
+ ## Step 6.5 — [PLATFORM] Derive active_module and platform_type
255
+
256
+ Using `tech_stack.module` loaded in Step 1, derive and store two variables for use by all downstream commands:
257
+
258
+ ```
259
+ active_module = tech_stack.module (e.g. "java-spring", "react", "flutter")
260
+ ```
261
+
262
+ | `platform_type` | Modules |
263
+ |---|---|
264
+ | `backend` | `java-spring`, `golang`, `dotnet`, `php-laravel`, `context-engineering` |
265
+ | `web-frontend` | `react`, `nextjs`, `vue`, `nuxt`, `angular` |
266
+ | `mobile` | `flutter`, `react-native`, `ios-swiftui`, `android-compose` |
267
+
268
+ If `tech_stack.module` is blank or not recognized → set `platform_type = "unknown"` and flag as ⚠️ in the Step 7 recap.
269
+
270
+ These two variables (`active_module`, `platform_type`) are the canonical source for all branching logic in commands that need platform-specific behavior (generate-tests, debug, fix-bug, smoke-test).
271
+
272
+ ---
273
+
220
274
  ## Step 7 — [RECAP] Working Memory Recap (anti-lost-in-middle)
221
275
 
222
276
  After loading all context, synthesize and output a compact summary block.
@@ -227,10 +281,12 @@ Output exactly this block:
227
281
  ```
228
282
  [CTX LOADED]
229
283
  Stack : {language} / {framework} / {database}
284
+ Platform : {active_module} ({platform_type})
230
285
  Layers : {layer order from CLAUDE.md §2, e.g., Controller → Facade → Service → Repository}
231
286
  Ticket : {ticket_prefix}-
232
287
  Dict : {loaded — N canonical terms, M banned terms | missing}
233
288
  Entities : {loaded — EntityA, EntityB, EntityC | missing}
289
+ Service : {active_service} ({active_service_module}) | single-service
234
290
  Status : {FULL | PARTIAL — missing: CLAUDE.md / business-dict / core-entities | MINIMAL}
235
291
  ```
236
292
 
@@ -324,21 +380,23 @@ Suggest the logical next command based on workflow phase:
324
380
 
325
381
  | Current command | Suggest next |
326
382
  |-------------------------|-----------------------------------------------|
383
+ | /setup-ai-first | `/define-product` to start your first feature |
327
384
  | /define-product | `/generate-prd {product-definition-file}` |
328
385
  | /generate-prd | `/refine-prd {prd-file}` then `/review-context {prd-file}` |
329
386
  | /refine-prd | Open Review Board → update PRD → `/review-context {prd-file}` |
330
- | /review-context (PRD) | `/generate-bdd {prd-file}` if APPROVED; fix PRD if NEEDS_FIX |
387
+ | /review-context (PRD) | FE/App: `/generate-design-spec {prd-file}` (then BDD after sign-off); BE: `/generate-bdd {prd-file}` directly; fix PRD if NEEDS_FIX |
388
+ | /generate-design-spec | Designer review → Figma links confirmed → PO + Designer sign-off → `/generate-bdd {prd-file}` |
331
389
  | /generate-bdd | `/review-context {feature-file}` to verify coverage |
332
390
  | /review-context (BDD) | `/generate-tech-docs {UC-ID}` if APPROVED; regenerate if NEEDS_FIX |
333
391
  | /generate-tech-docs | `/review-tech-docs {tech-design-file}` |
334
392
  | /review-tech-docs | `/generate-code {feature-file}` if APPROVED; fix doc if NEEDS_FIX |
335
- | /generate-code | `/generate-tests {UC-ID}` |
393
+ | /generate-code | First gen → `/review-code {UC-ID}`; re-gen → `/generate-tests {UC-ID}` |
336
394
  | /generate-tests | `/run-tests {UC-ID}` |
337
395
  | /run-tests (passing) | `/review-code {UC-ID}` |
338
396
  | /run-tests (failing) | `/fix-bug {ticket-id}` or `/debug {error}` |
339
397
  | /review-code | `/smoke-test {UC-ID}` or create PR |
340
398
  | /smoke-test | Create PR and link to ticket |
341
- | /validate-traces | `/generate-code {UC-ID}` for gaps |
399
+ | /validate-traces | DRIFT/UNTRACKED → `/generate-code {UC-ID}`; GAP → `/generate-tests {UC-ID}`; all OK → create PR |
342
400
  | /fix-bug | Create PR and link to ticket |
343
401
  | /debug | `/fix-bug {ticket-id}` if fix needed |
344
402
 
@@ -355,7 +413,9 @@ Next : {suggested command with example arguments}
355
413
  /refine-prd Complete — {PRD name}
356
414
  Findings: {total} | 🔴 Critical: {N} | 🟡 Major: {N} | 🟢 Minor: {N}
357
415
  Review: {paths.refinement_dir}/{prd-slug}-findings.yaml
358
- Next: Open in Review Board (right-click file) → Update PRD → /generate-bdd
416
+ Next: Open in Review Board (right-click file) → Update PRD
417
+ → /review-context {prd-file} ← verify PRD quality before generating BDD
418
+ → /generate-bdd {prd-file}
359
419
  ```
360
420
 
361
421
  ---
@@ -374,9 +434,16 @@ Next: Open in Review Board (right-click file) → Update PRD → /generate-bdd
374
434
 
375
435
  For each accepted finding, in order of severity (critical → major → minor):
376
436
  - Navigate to the PRD section indicated by `finding.section`.
377
- - Apply `finding.suggestion` (or the note from `modified` status if applicable).
437
+ - Apply `finding.suggestion`. For `status: "modified"` findings, the human has already edited `finding.suggestion` in the Review Board — that edited value IS the fix to apply.
378
438
  - Do not alter any sections not referenced by an accepted finding.
379
439
 
440
+ ### Phase 2.5 — Update findings file
441
+
442
+ For each finding that was applied (status was `accepted` or `modified`):
443
+ - Set `status: "applied"` in `{paths.refinement_dir}/{prd-slug}-findings.yaml`.
444
+
445
+ Update `summary.status: "applied"` in the findings file.
446
+
380
447
  ### Phase 3 — Bump version & write changelog entry
381
448
 
382
449
  1. Read current `| **Version** |` value from PRD metadata table.
@@ -404,5 +471,6 @@ Changes :
404
471
  - {change 2}
405
472
 
406
473
  ⚠️ BDD may be outdated. Run:
407
- /generate-bdd {prd-file}
474
+ /review-context {prd-file} ← verify PRD quality first
475
+ → /generate-bdd {prd-file}
408
476
  ```