@atlashub/smartstack-cli 4.31.0 → 4.33.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.
- package/.documentation/commands.html +952 -116
- package/.documentation/index.html +2 -2
- package/.documentation/init.html +358 -174
- package/dist/mcp-entry.mjs +271 -44
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/mcp-scaffolding/controller.cs.hbs +54 -128
- package/templates/project/README.md +19 -0
- package/templates/skills/apex/SKILL.md +16 -10
- package/templates/skills/apex/_shared.md +1 -1
- package/templates/skills/apex/references/checks/architecture-checks.sh +154 -0
- package/templates/skills/apex/references/checks/backend-checks.sh +194 -0
- package/templates/skills/apex/references/checks/frontend-checks.sh +448 -0
- package/templates/skills/apex/references/checks/infrastructure-checks.sh +255 -0
- package/templates/skills/apex/references/checks/security-checks.sh +153 -0
- package/templates/skills/apex/references/checks/seed-checks.sh +536 -0
- package/templates/skills/apex/references/frontend-route-wiring-app-tsx.md +49 -192
- package/templates/skills/apex/references/parallel-execution.md +18 -5
- package/templates/skills/apex/references/post-checks.md +124 -2156
- package/templates/skills/apex/references/smartstack-api.md +160 -957
- package/templates/skills/apex/references/smartstack-frontend-compliance.md +23 -1
- package/templates/skills/apex/references/smartstack-frontend.md +134 -1022
- package/templates/skills/apex/references/smartstack-layers.md +12 -6
- package/templates/skills/apex/steps/step-00-init.md +81 -238
- package/templates/skills/apex/steps/step-03-execute.md +25 -751
- package/templates/skills/apex/steps/step-03a-layer0-domain.md +118 -0
- package/templates/skills/apex/steps/step-03b-layer1-seed.md +91 -0
- package/templates/skills/apex/steps/step-03c-layer2-backend.md +240 -0
- package/templates/skills/apex/steps/step-03d-layer3-frontend.md +300 -0
- package/templates/skills/apex/steps/step-03e-layer4-devdata.md +44 -0
- package/templates/skills/apex/steps/step-04-examine.md +70 -150
- package/templates/skills/application/references/frontend-i18n-and-output.md +2 -2
- package/templates/skills/application/references/frontend-route-naming.md +5 -1
- package/templates/skills/application/references/frontend-route-wiring-app-tsx.md +49 -198
- package/templates/skills/application/references/frontend-verification.md +11 -11
- package/templates/skills/application/steps/step-05-frontend.md +26 -15
- package/templates/skills/application/templates-frontend.md +4 -0
- package/templates/skills/cli-app-sync/SKILL.md +2 -2
- package/templates/skills/cli-app-sync/references/comparison-map.md +1 -1
- package/templates/skills/controller/references/controller-code-templates.md +70 -67
- package/templates/skills/controller/references/mcp-scaffold-workflow.md +5 -1
package/package.json
CHANGED
|
@@ -1,192 +1,118 @@
|
|
|
1
|
-
using
|
|
2
|
-
using System.Collections.Generic;
|
|
3
|
-
using System.Threading;
|
|
4
|
-
using System.Threading.Tasks;
|
|
5
|
-
using Microsoft.AspNetCore.Authorization;
|
|
1
|
+
using MediatR;
|
|
6
2
|
using Microsoft.AspNetCore.Mvc;
|
|
7
|
-
using Microsoft.Extensions.Logging;
|
|
8
3
|
using SmartStack.Api.Authorization;
|
|
9
4
|
{{#if navRoute}}
|
|
10
5
|
using SmartStack.Api.Routing;
|
|
11
6
|
{{/if}}
|
|
12
|
-
using SmartStack.Application.Common.
|
|
7
|
+
using SmartStack.Application.Common.Authorization;
|
|
13
8
|
|
|
14
|
-
namespace {{namespace}}
|
|
9
|
+
namespace {{namespace}};
|
|
15
10
|
|
|
16
11
|
/// <summary>
|
|
17
|
-
/// API controller for {{name}} operations
|
|
12
|
+
/// API controller for {{name}} operations.
|
|
18
13
|
/// </summary>
|
|
19
14
|
[ApiController]
|
|
20
15
|
{{#if navRoute}}
|
|
21
|
-
[NavRoute("{{navRoute}}")]
|
|
16
|
+
[NavRoute("{{navRoute}}"{{#if navRouteSuffix}}, Suffix = "{{navRouteSuffix}}"{{/if}}{{#if customSegment}}, CustomSegment = "{{customSegment}}"{{/if}})]
|
|
22
17
|
{{else}}
|
|
23
18
|
[Route("api/[controller]")]
|
|
24
19
|
{{/if}}
|
|
25
|
-
[Authorize]
|
|
20
|
+
[Microsoft.AspNetCore.Authorization.Authorize]
|
|
26
21
|
[Produces("application/json")]
|
|
22
|
+
[Tags("{{namePlural}}")]
|
|
27
23
|
public class {{name}}Controller : ControllerBase
|
|
28
24
|
{
|
|
29
|
-
private readonly
|
|
30
|
-
// private readonly I{{name}}Service _{{nameCamel}}Service;
|
|
25
|
+
private readonly ISender _mediator;
|
|
31
26
|
|
|
32
|
-
public {{name}}Controller(
|
|
33
|
-
ILogger<{{name}}Controller> logger
|
|
34
|
-
// I{{name}}Service {{nameCamel}}Service
|
|
35
|
-
)
|
|
36
|
-
{
|
|
37
|
-
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
38
|
-
// _{{nameCamel}}Service = {{nameCamel}}Service ?? throw new ArgumentNullException(nameof({{nameCamel}}Service));
|
|
39
|
-
}
|
|
27
|
+
public {{name}}Controller(ISender mediator) => _mediator = mediator;
|
|
40
28
|
|
|
41
29
|
/// <summary>
|
|
42
30
|
/// Get all {{namePlural}} with pagination
|
|
43
31
|
/// </summary>
|
|
44
32
|
[HttpGet]
|
|
45
33
|
{{#if navRoute}}
|
|
46
|
-
[RequirePermission(
|
|
34
|
+
[RequirePermission(Permissions.{{module}}.{{namePlural}}.View)]
|
|
47
35
|
{{/if}}
|
|
48
|
-
[ProducesResponseType(typeof(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
[FromQuery] int pageSize = 20,
|
|
53
|
-
CancellationToken cancellationToken = default)
|
|
36
|
+
[ProducesResponseType(typeof(List<{{name}}ListDto>), StatusCodes.Status200OK)]
|
|
37
|
+
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
38
|
+
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
|
39
|
+
public async Task<ActionResult<List<{{name}}ListDto>>> GetAll(CancellationToken ct)
|
|
54
40
|
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// TODO: Implement using service
|
|
58
|
-
// var result = await _{{nameCamel}}Service.GetAllAsync(search, page, pageSize, cancellationToken);
|
|
59
|
-
// return Ok(result);
|
|
60
|
-
|
|
61
|
-
return Ok(PaginatedResult<{{name}}Dto>.Empty(page, pageSize));
|
|
41
|
+
var result = await _mediator.Send(new Get{{namePlural}}Query(), ct);
|
|
42
|
+
return Ok(result);
|
|
62
43
|
}
|
|
63
44
|
|
|
64
45
|
/// <summary>
|
|
65
46
|
/// Get {{nameCamel}} by ID
|
|
66
47
|
/// </summary>
|
|
67
|
-
/// <param name="id">{{name}} ID</param>
|
|
68
|
-
/// <param name="cancellationToken">Cancellation token</param>
|
|
69
|
-
/// <returns>{{name}} details</returns>
|
|
70
48
|
[HttpGet("{id:guid}")]
|
|
71
49
|
{{#if navRoute}}
|
|
72
|
-
[RequirePermission(
|
|
50
|
+
[RequirePermission(Permissions.{{module}}.{{namePlural}}.View)]
|
|
73
51
|
{{/if}}
|
|
74
|
-
[ProducesResponseType(typeof({{name}}
|
|
75
|
-
[ProducesResponseType(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
52
|
+
[ProducesResponseType(typeof({{name}}DetailDto), StatusCodes.Status200OK)]
|
|
53
|
+
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
54
|
+
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
55
|
+
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
|
56
|
+
public async Task<ActionResult<{{name}}DetailDto>> GetById(
|
|
57
|
+
Guid id, CancellationToken ct)
|
|
79
58
|
{
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// var result = await _{{nameCamel}}Service.GetByIdAsync(id, cancellationToken);
|
|
84
|
-
// if (result == null) return NotFound();
|
|
85
|
-
// return Ok(result);
|
|
86
|
-
|
|
87
|
-
return NotFound();
|
|
59
|
+
var result = await _mediator.Send(new Get{{name}}ByIdQuery(id), ct);
|
|
60
|
+
if (result == null) return NotFound(new { message = "{{name}} not found" });
|
|
61
|
+
return Ok(result);
|
|
88
62
|
}
|
|
89
63
|
|
|
90
64
|
/// <summary>
|
|
91
65
|
/// Create new {{nameCamel}}
|
|
92
66
|
/// </summary>
|
|
93
|
-
/// <param name="request">Create request</param>
|
|
94
|
-
/// <param name="cancellationToken">Cancellation token</param>
|
|
95
|
-
/// <returns>Created {{nameCamel}}</returns>
|
|
96
67
|
[HttpPost]
|
|
97
68
|
{{#if navRoute}}
|
|
98
|
-
[RequirePermission(
|
|
69
|
+
[RequirePermission(Permissions.{{module}}.{{namePlural}}.Create)]
|
|
99
70
|
{{/if}}
|
|
100
|
-
[ProducesResponseType(typeof({{name}}
|
|
101
|
-
[ProducesResponseType(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
71
|
+
[ProducesResponseType(typeof({{name}}DetailDto), StatusCodes.Status201Created)]
|
|
72
|
+
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
73
|
+
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
74
|
+
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
|
75
|
+
public async Task<ActionResult<{{name}}DetailDto>> Create(
|
|
76
|
+
[FromBody] Create{{name}}Request request, CancellationToken ct)
|
|
105
77
|
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// TODO: Validate and create using service
|
|
109
|
-
// var result = await _{{nameCamel}}Service.CreateAsync(request, cancellationToken);
|
|
110
|
-
// return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
|
111
|
-
|
|
112
|
-
var id = Guid.NewGuid();
|
|
113
|
-
return CreatedAtAction(nameof(GetById), new { id }, null);
|
|
78
|
+
var result = await _mediator.Send(new Create{{name}}Command(request), ct);
|
|
79
|
+
return CreatedAtAction(nameof(GetById), new { id = result.Id }, result);
|
|
114
80
|
}
|
|
115
81
|
|
|
116
82
|
/// <summary>
|
|
117
83
|
/// Update {{nameCamel}}
|
|
118
84
|
/// </summary>
|
|
119
|
-
/// <param name="id">{{name}} ID</param>
|
|
120
|
-
/// <param name="request">Update request</param>
|
|
121
|
-
/// <param name="cancellationToken">Cancellation token</param>
|
|
122
85
|
[HttpPut("{id:guid}")]
|
|
123
86
|
{{#if navRoute}}
|
|
124
|
-
[RequirePermission(
|
|
87
|
+
[RequirePermission(Permissions.{{module}}.{{namePlural}}.Update)]
|
|
125
88
|
{{/if}}
|
|
126
|
-
[ProducesResponseType(
|
|
127
|
-
[ProducesResponseType(
|
|
128
|
-
[ProducesResponseType(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
89
|
+
[ProducesResponseType(typeof({{name}}DetailDto), StatusCodes.Status200OK)]
|
|
90
|
+
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
91
|
+
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
|
92
|
+
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
93
|
+
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
|
94
|
+
public async Task<ActionResult<{{name}}DetailDto>> Update(
|
|
95
|
+
Guid id, [FromBody] Update{{name}}Request request, CancellationToken ct)
|
|
133
96
|
{
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// await _{{nameCamel}}Service.UpdateAsync(id, request, cancellationToken);
|
|
138
|
-
|
|
139
|
-
return NoContent();
|
|
97
|
+
var result = await _mediator.Send(new Update{{name}}Command(id, request), ct);
|
|
98
|
+
if (result.IsNotFound) return NotFound(new { message = "{{name}} not found" });
|
|
99
|
+
return Ok(result.Data);
|
|
140
100
|
}
|
|
141
101
|
|
|
142
102
|
/// <summary>
|
|
143
103
|
/// Delete {{nameCamel}}
|
|
144
104
|
/// </summary>
|
|
145
|
-
/// <param name="id">{{name}} ID</param>
|
|
146
|
-
/// <param name="cancellationToken">Cancellation token</param>
|
|
147
105
|
[HttpDelete("{id:guid}")]
|
|
148
106
|
{{#if navRoute}}
|
|
149
|
-
[RequirePermission(
|
|
107
|
+
[RequirePermission(Permissions.{{module}}.{{namePlural}}.Delete)]
|
|
150
108
|
{{/if}}
|
|
151
|
-
[ProducesResponseType(
|
|
152
|
-
[ProducesResponseType(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
109
|
+
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
|
110
|
+
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
111
|
+
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
112
|
+
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
|
113
|
+
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
|
|
156
114
|
{
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// TODO: Implement using service
|
|
160
|
-
// await _{{nameCamel}}Service.DeleteAsync(id, cancellationToken);
|
|
161
|
-
|
|
162
|
-
return NoContent();
|
|
115
|
+
var success = await _mediator.Send(new Delete{{name}}Command(id), ct);
|
|
116
|
+
return success ? NoContent() : NotFound(new { message = "{{name}} not found" });
|
|
163
117
|
}
|
|
164
118
|
}
|
|
165
|
-
|
|
166
|
-
// ============================================================================
|
|
167
|
-
// DTOs
|
|
168
|
-
// ============================================================================
|
|
169
|
-
|
|
170
|
-
/// <summary>
|
|
171
|
-
/// {{name}} data transfer object
|
|
172
|
-
/// </summary>
|
|
173
|
-
public record {{name}}Dto(
|
|
174
|
-
Guid Id,
|
|
175
|
-
DateTime CreatedAt,
|
|
176
|
-
DateTime? UpdatedAt
|
|
177
|
-
// TODO: Add additional properties
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
/// <summary>
|
|
181
|
-
/// Request to create a new {{nameCamel}}
|
|
182
|
-
/// </summary>
|
|
183
|
-
public record Create{{name}}Request(
|
|
184
|
-
// TODO: Add creation properties
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
/// <summary>
|
|
188
|
-
/// Request to update a {{nameCamel}}
|
|
189
|
-
/// </summary>
|
|
190
|
-
public record Update{{name}}Request(
|
|
191
|
-
// TODO: Add update properties
|
|
192
|
-
);
|
|
@@ -62,6 +62,25 @@ Migrations must be applied in order:
|
|
|
62
62
|
|
|
63
63
|
This is handled automatically in `Program.cs.template`.
|
|
64
64
|
|
|
65
|
+
## Frontend Setup (React)
|
|
66
|
+
|
|
67
|
+
### Route Registration
|
|
68
|
+
|
|
69
|
+
SmartStack uses **PageRegistry + DynamicRouter** for routing. Register your pages:
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { PageRegistry } from '@atlashub/smartstack';
|
|
73
|
+
import { lazy } from 'react';
|
|
74
|
+
|
|
75
|
+
// Register your application pages
|
|
76
|
+
PageRegistry.register('rh.time.dashboard', lazy(() => import('./pages/TimeDashboard')));
|
|
77
|
+
PageRegistry.register('rh.time.list', lazy(() => import('./pages/TimeList')));
|
|
78
|
+
PageRegistry.register('rh.time.detail', lazy(() => import('./pages/TimeDetail')));
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Navigation entries (menu, permissions, routes) are managed in the database.
|
|
82
|
+
DynamicRouter resolves everything automatically — no manual route wiring needed.
|
|
83
|
+
|
|
65
84
|
## Usage
|
|
66
85
|
|
|
67
86
|
```bash
|
|
@@ -91,14 +91,19 @@ Execute incremental SmartStack development using the APEX methodology. This skil
|
|
|
91
91
|
</entry_point>
|
|
92
92
|
|
|
93
93
|
<step_files>
|
|
94
|
-
**Progressive loading
|
|
94
|
+
**Progressive loading — only load current step. Step 03 dispatches to layer sub-steps.**
|
|
95
95
|
|
|
96
96
|
| Step | File | Model | Purpose |
|
|
97
97
|
|------|------|-------|---------|
|
|
98
98
|
| 00 | `steps/step-00-init.md` | Sonnet | Parse flags, detect application, verify MCP, define hierarchy (4 levels), scope validation |
|
|
99
99
|
| 01 | `steps/step-01-analyze.md` | Opus | Explore existing code (parallel Agent tool or sequential) |
|
|
100
100
|
| 02 | `steps/step-02-plan.md` | Opus | Layer-by-layer plan with skill/MCP mapping |
|
|
101
|
-
| 03 | `steps/step-03-execute.md` | Opus |
|
|
101
|
+
| 03 | `steps/step-03-execute.md` | Opus | **Orchestrator** — dispatches to layer sub-steps below |
|
|
102
|
+
| 03a | `steps/step-03a-layer0-domain.md` | Opus | Layer 0: Domain entities, EF configs, migration |
|
|
103
|
+
| 03b | `steps/step-03b-layer1-seed.md` | Opus | Layer 1: Seed data (navigation, permissions, roles) |
|
|
104
|
+
| 03c | `steps/step-03c-layer2-backend.md` | Opus | Layer 2: Services, controllers, backend tests |
|
|
105
|
+
| 03d | `steps/step-03d-layer3-frontend.md` | Opus | Layer 3: Pages, i18n, routes, frontend tests |
|
|
106
|
+
| 03e | `steps/step-03e-layer4-devdata.md` | Opus | Layer 4: Development test data (optional) |
|
|
102
107
|
| 04 | `steps/step-04-examine.md` | Opus | eXamine: MCP validation, build, POST-CHECKs, acceptance criteria |
|
|
103
108
|
| 05 | `steps/step-05-deep-review.md` | Opus | Deep Review: adversarial code review (if -x) |
|
|
104
109
|
| 06 | `steps/step-06-resolve.md` | Opus | Fix BLOCKING findings (if any) |
|
|
@@ -126,15 +131,16 @@ Execute incremental SmartStack development using the APEX methodology. This skil
|
|
|
126
131
|
|
|
127
132
|
| File | Purpose | Loaded by | Stays in context for |
|
|
128
133
|
|------|---------|-----------|---------------------|
|
|
129
|
-
| `references/smartstack-api.md` | BaseEntity, interfaces, entity/config/controller patterns | step-01 | step-
|
|
130
|
-
| `references/smartstack-layers.md` | Layer rules, skill/MCP mapping, planning templates, delegate fast path | step-02 | step-
|
|
131
|
-
| `references/core-seed-data.md` | Comprehensive seed data templates (navigation, permissions, roles) | step-
|
|
132
|
-
| `references/smartstack-frontend.md` | Frontend patterns, EntityLookup, i18n (sections 1-6) | step-
|
|
133
|
-
| `references/smartstack-frontend-compliance.md` | Documentation, form testing, compliance gates (sections 7-9) | step-
|
|
134
|
+
| `references/smartstack-api.md` | BaseEntity, interfaces, entity/config/controller patterns | step-01 | step-03a/03b/03c (released after L2) |
|
|
135
|
+
| `references/smartstack-layers.md` | Layer rules, skill/MCP mapping, planning templates, delegate fast path | step-02 | step-03a/03b/03c (released after L2) |
|
|
136
|
+
| `references/core-seed-data.md` | Comprehensive seed data templates (navigation, permissions, roles) | step-03b (Layer 1) | released after Layer 1 |
|
|
137
|
+
| `references/smartstack-frontend.md` | Frontend patterns, EntityLookup, i18n (sections 1-6) | step-03d (Layer 3, deferred) | step-04 |
|
|
138
|
+
| `references/smartstack-frontend-compliance.md` | Documentation, form testing, compliance gates (sections 7-9) | step-03d (Layer 3, deferred) | step-04 |
|
|
134
139
|
| `references/challenge-questions.md` | Hierarchy rules, challenge questions, delegate mode skip | step-00 | — |
|
|
135
140
|
| `references/error-classification.md` | Build error diagnosis categories A-F | step-03 (build failure), step-04 | — |
|
|
136
|
-
| `references/parallel-execution.md` | Agent tool launch patterns, task coordination, decision matrix | step-01, step-
|
|
137
|
-
| `references/post-checks.md` |
|
|
141
|
+
| `references/parallel-execution.md` | Agent tool launch patterns, task coordination, decision matrix | step-01, step-03c/03d (if NOT economy_mode) | — |
|
|
142
|
+
| `references/post-checks.md` | Compact checklist — indexes checks in `references/checks/*.sh` | step-04 | — |
|
|
143
|
+
| `references/checks/*.sh` | Bash check scripts (security, backend, frontend, seed, architecture, infrastructure) | step-04 (executed via bash) | — |
|
|
138
144
|
|
|
139
145
|
**Context propagation rule:** Files loaded in step N remain in conversation context for step N+1. Steps mark "do NOT re-read" to avoid duplicate reads.
|
|
140
146
|
</reference_files>
|
|
@@ -148,7 +154,7 @@ Execute incremental SmartStack development using the APEX methodology. This skil
|
|
|
148
154
|
- **Layer order** - Layer 0 (domain+infra+migration) → Layer 1 (seed data) → Layer 2 (backend+tests) → Layer 3 (frontend+tests) → Layer 4 (devdata)
|
|
149
155
|
- **Parallel Agent tool** - Parallel execution for scan (step-01) and within Layer 2/3 (step-03) for multi-entity, unless economy_mode
|
|
150
156
|
- **Tests inline** - Backend tests run after Layer 2, frontend tests run after Layer 3 (max 3 fix iterations each). Step-07 = final sweep (security + coverage).
|
|
151
|
-
- **Exception: seed data** — The templates in core-seed-data.md and person-extension-pattern.md are generated directly because no MCP tool covers seed data creation. This is a documented exception to the "orchestrate, never generate" rule.
|
|
157
|
+
- **Exception: seed data** — The templates in core-seed-data.md and person-extension-pattern.md are generated directly because no MCP tool covers seed data creation yet. This is a documented exception to the "orchestrate, never generate" rule. <!-- TODO: Remove exception when MCP scaffold_seed_data (B1) is ready -->
|
|
152
158
|
- **Frontend pages: ALWAYS via Skill("ui-components")** — economy_mode affects parallelization only, NOT whether /ui-components is called. NEVER generate .tsx pages directly, even in delegate or economy mode.
|
|
153
159
|
- **Save outputs** if `{save_mode}` = true
|
|
154
160
|
- **Commits per layer** - Atomic commits after each execution layer
|
|
@@ -156,7 +156,7 @@ Write back to {delegate_prd_path}
|
|
|
156
156
|
| `scaffold_extension` | Create files manually following `smartstack-api.md` entity/service/controller patterns |
|
|
157
157
|
| `suggest_migration` | Name format: `{context}_v{version}_{sequence}_{Description}` (see existing migrations for version) |
|
|
158
158
|
| `generate_permissions` | Write `HasData()` code manually following `core-seed-data.md` permission section |
|
|
159
|
-
| `scaffold_routes` | Create `
|
|
159
|
+
| `scaffold_routes` | Create `componentRegistry.generated.ts` with `PageRegistry.register()` calls manually following `smartstack-frontend.md` §1 (or legacy `applicationRoutes` array for pre-v3.7 projects) |
|
|
160
160
|
| `validate_frontend_routes` | Run POST-CHECK bash scripts from `post-checks.md` |
|
|
161
161
|
| `validate_security` | Run security POST-CHECKs S1-S6 from `post-checks.md` |
|
|
162
162
|
| `check_migrations` | Run `dotnet ef migrations has-pending-model-changes` manually |
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# POST-CHECK: Architecture — Clean Architecture Layer Isolation
|
|
5
|
+
# A1-A8: Layer boundary enforcement, DTO usage, handoff compliance
|
|
6
|
+
|
|
7
|
+
FAIL=false
|
|
8
|
+
|
|
9
|
+
# POST-CHECK A1: Domain must not import other layers (BLOCKING)
|
|
10
|
+
DOMAIN_FILES=$(find src/ -path "*/Domain/*" -name "*.cs" 2>/dev/null)
|
|
11
|
+
if [ -n "$DOMAIN_FILES" ]; then
|
|
12
|
+
BAD_IMPORTS=$(grep -Pn 'using\s+[\w.]*\.(Application|Infrastructure|Api)[\w.]*;' $DOMAIN_FILES 2>/dev/null || true)
|
|
13
|
+
if [ -n "$BAD_IMPORTS" ]; then
|
|
14
|
+
echo "BLOCKING: Domain layer imports Application/Infrastructure/Api — violates Clean Architecture"
|
|
15
|
+
echo "Domain is the core, it must not depend on any other layer"
|
|
16
|
+
echo "$BAD_IMPORTS"
|
|
17
|
+
echo "Fix: Move shared types to Domain or remove the dependency"
|
|
18
|
+
FAIL=true
|
|
19
|
+
fi
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# POST-CHECK A2: Application must not import Infrastructure or Api (BLOCKING)
|
|
23
|
+
APP_FILES=$(find src/ -path "*/Application/*" -name "*.cs" 2>/dev/null)
|
|
24
|
+
if [ -n "$APP_FILES" ]; then
|
|
25
|
+
BAD_IMPORTS=$(grep -Pn 'using\s+[\w.]*\.(Infrastructure|Api)[\w.]*;' $APP_FILES 2>/dev/null || true)
|
|
26
|
+
if [ -n "$BAD_IMPORTS" ]; then
|
|
27
|
+
echo "BLOCKING: Application layer imports Infrastructure/Api — violates Clean Architecture"
|
|
28
|
+
echo "Application defines interfaces, Infrastructure implements them"
|
|
29
|
+
echo "$BAD_IMPORTS"
|
|
30
|
+
echo "Fix: Define an interface in Application and implement it in Infrastructure"
|
|
31
|
+
FAIL=true
|
|
32
|
+
fi
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# POST-CHECK A3: Controllers must not inject DbContext (BLOCKING)
|
|
36
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
37
|
+
if [ -n "$CTRL_FILES" ]; then
|
|
38
|
+
BAD_DBCONTEXT=$(grep -Pn 'private\s+readonly\s+\w*DbContext|DbContext\s+\w+[,)]' $CTRL_FILES 2>/dev/null || true)
|
|
39
|
+
if [ -n "$BAD_DBCONTEXT" ]; then
|
|
40
|
+
echo "BLOCKING: Controller injects DbContext directly — violates Clean Architecture"
|
|
41
|
+
echo "Controllers must use Application services, not access the database directly"
|
|
42
|
+
echo "$BAD_DBCONTEXT"
|
|
43
|
+
echo "Fix: Create an Application service with the required business logic and inject it instead"
|
|
44
|
+
FAIL=true
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# POST-CHECK A4: API must return DTOs, not Domain entities (WARNING)
|
|
49
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
50
|
+
ENTITY_FILES=$(find src/ -path "*/Domain/Entities/*" -name "*.cs" 2>/dev/null)
|
|
51
|
+
if [ -n "$CTRL_FILES" ] && [ -n "$ENTITY_FILES" ]; then
|
|
52
|
+
ENTITY_NAMES=$(grep -ohP 'public\s+class\s+(\w+)\s*:' $ENTITY_FILES 2>/dev/null | grep -oP '\w+(?=\s*:)' | grep -v '^public$' | sort -u || true)
|
|
53
|
+
for ENTITY in $ENTITY_NAMES; do
|
|
54
|
+
BAD_RETURN=$(grep -Pn "ActionResult<$ENTITY>|ActionResult<IEnumerable<$ENTITY>>|ActionResult<List<$ENTITY>>" $CTRL_FILES 2>/dev/null || true)
|
|
55
|
+
if [ -n "$BAD_RETURN" ]; then
|
|
56
|
+
echo "WARNING: Controller returns Domain entity '$ENTITY' instead of a DTO"
|
|
57
|
+
echo "$BAD_RETURN"
|
|
58
|
+
echo "Fix: Return ${ENTITY}ResponseDto instead of $ENTITY"
|
|
59
|
+
fi
|
|
60
|
+
done
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# POST-CHECK A5: Service interfaces in Application, implementations in Infrastructure (WARNING)
|
|
64
|
+
APP_SERVICES=$(find src/ -path "*/Application/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
65
|
+
if [ -n "$APP_SERVICES" ]; then
|
|
66
|
+
for f in $APP_SERVICES; do
|
|
67
|
+
if grep -q 'public class.*Service' "$f" 2>/dev/null; then
|
|
68
|
+
echo "WARNING: Service implementation found in Application layer: $f"
|
|
69
|
+
echo "Fix: Move implementation to Infrastructure/Services/. Application should only contain interfaces."
|
|
70
|
+
fi
|
|
71
|
+
done
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
DOMAIN_INTERFACES=$(find src/ -path "*/Domain/*" -name "I*Service.cs" 2>/dev/null)
|
|
75
|
+
API_INTERFACES=$(find src/ -path "*/Api/*" -name "I*Service.cs" 2>/dev/null)
|
|
76
|
+
for f in $DOMAIN_INTERFACES $API_INTERFACES; do
|
|
77
|
+
if [ -n "$f" ] && grep -q 'public interface.*Service' "$f" 2>/dev/null; then
|
|
78
|
+
echo "WARNING: Service interface found outside Application layer: $f"
|
|
79
|
+
echo "Fix: Move to Application/Interfaces/"
|
|
80
|
+
fi
|
|
81
|
+
done
|
|
82
|
+
|
|
83
|
+
# POST-CHECK A6: No EF Core attributes in Domain entities (BLOCKING)
|
|
84
|
+
DOMAIN_FILES=$(find src/ -path "*/Domain/*" -name "*.cs" 2>/dev/null)
|
|
85
|
+
if [ -n "$DOMAIN_FILES" ]; then
|
|
86
|
+
BAD_EF=$(grep -Pn '\[Table\(|\[Column\(|\[Index\(|using\s+Microsoft\.EntityFrameworkCore' $DOMAIN_FILES 2>/dev/null || true)
|
|
87
|
+
if [ -n "$BAD_EF" ]; then
|
|
88
|
+
echo "BLOCKING: EF Core attributes or using directives found in Domain layer"
|
|
89
|
+
echo "Domain entities must be persistence-ignorant — EF configuration belongs in Infrastructure"
|
|
90
|
+
echo "$BAD_EF"
|
|
91
|
+
echo "Fix: Move [Table], [Column], [Index] to IEntityTypeConfiguration<T> in Infrastructure/Persistence/Configurations/"
|
|
92
|
+
FAIL=true
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# POST-CHECK A7: No direct repository usage in controllers (WARNING)
|
|
97
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
98
|
+
if [ -n "$CTRL_FILES" ]; then
|
|
99
|
+
BAD_REPO=$(grep -Pn 'IRepository<|IGenericRepository<|private\s+readonly\s+IRepository|private\s+readonly\s+IGenericRepository' $CTRL_FILES 2>/dev/null || true)
|
|
100
|
+
if [ -n "$BAD_REPO" ]; then
|
|
101
|
+
echo "WARNING: Controller injects repository directly — should use Application services"
|
|
102
|
+
echo "$BAD_REPO"
|
|
103
|
+
echo "Fix: Controllers should depend on Application services (I*Service), not repositories"
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# POST-CHECK A8: API endpoints must match handoff apiEndpointSummary (BLOCKING)
|
|
108
|
+
PRD_FILE=".ralph/prd.json"
|
|
109
|
+
if [ ! -f "$PRD_FILE" ]; then
|
|
110
|
+
if [ -f ".ralph/modules-queue.json" ]; then
|
|
111
|
+
PRD_FILE=$(cat .ralph/modules-queue.json | grep -o '"prdFile":"[^"]*"' | tail -1 | cut -d'"' -f4)
|
|
112
|
+
fi
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
if [ -f "$PRD_FILE" ]; then
|
|
116
|
+
OPERATIONS=$(cat "$PRD_FILE" | grep -o '"operation"\s*:\s*"[^"]*"' | cut -d'"' -f4 2>/dev/null || true)
|
|
117
|
+
|
|
118
|
+
if [ -n "$OPERATIONS" ]; then
|
|
119
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
120
|
+
MISSING_OPS=""
|
|
121
|
+
TOTAL_OPS=0
|
|
122
|
+
FOUND_OPS=0
|
|
123
|
+
|
|
124
|
+
for op in $OPERATIONS; do
|
|
125
|
+
TOTAL_OPS=$((TOTAL_OPS + 1))
|
|
126
|
+
FOUND=false
|
|
127
|
+
if [ -n "$CTRL_FILES" ]; then
|
|
128
|
+
for f in $CTRL_FILES; do
|
|
129
|
+
if grep -q "$op" "$f" 2>/dev/null; then
|
|
130
|
+
FOUND=true
|
|
131
|
+
break
|
|
132
|
+
fi
|
|
133
|
+
done
|
|
134
|
+
fi
|
|
135
|
+
if [ "$FOUND" = true ]; then
|
|
136
|
+
FOUND_OPS=$((FOUND_OPS + 1))
|
|
137
|
+
else
|
|
138
|
+
MISSING_OPS="$MISSING_OPS $op"
|
|
139
|
+
fi
|
|
140
|
+
done
|
|
141
|
+
|
|
142
|
+
if [ -n "$MISSING_OPS" ]; then
|
|
143
|
+
echo "BLOCKING: API endpoints missing from controllers (handoff contract violation)"
|
|
144
|
+
echo "Found: $FOUND_OPS/$TOTAL_OPS operations"
|
|
145
|
+
echo "Missing operations:$MISSING_OPS"
|
|
146
|
+
echo "Fix: Implement missing endpoints in the appropriate Controller"
|
|
147
|
+
FAIL=true
|
|
148
|
+
fi
|
|
149
|
+
fi
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
if [ "$FAIL" = true ]; then
|
|
153
|
+
exit 1
|
|
154
|
+
fi
|