@atlashub/smartstack-cli 3.25.0 → 3.27.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/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +11 -5
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -2
- package/templates/skills/apex/SKILL.md +26 -5
- package/templates/skills/apex/_shared.md +3 -3
- package/templates/skills/apex/references/agent-teams-protocol.md +8 -8
- package/templates/skills/apex/references/challenge-questions.md +165 -0
- package/templates/skills/apex/{steps/step-04-validate.md → references/post-checks.md} +82 -214
- package/templates/skills/apex/references/smartstack-api.md +91 -14
- package/templates/skills/apex/references/smartstack-layers.md +16 -4
- package/templates/skills/apex/steps/step-00-init.md +84 -56
- package/templates/skills/apex/steps/step-01-analyze.md +73 -87
- package/templates/skills/apex/steps/step-03-execute.md +2 -2
- package/templates/skills/apex/steps/step-04-examine.md +198 -0
- package/templates/skills/apex/steps/{step-05-examine.md → step-05-deep-review.md} +6 -6
- package/templates/skills/apex/steps/step-06-resolve.md +2 -2
- package/templates/skills/business-analyse/SKILL.md +28 -0
- package/templates/skills/business-analyse/references/agent-module-prompt.md +255 -0
- package/templates/skills/business-analyse/references/agent-pooling-best-practices.md +26 -10
- package/templates/skills/business-analyse/references/team-orchestration.md +437 -0
- package/templates/skills/business-analyse/steps/step-02-decomposition.md +31 -4
- package/templates/skills/business-analyse/steps/step-03a1-setup.md +21 -0
- package/templates/skills/business-analyse/steps/step-03d-validate.md +84 -0
- package/templates/skills/ralph-loop/references/core-seed-data.md +45 -10
- package/templates/skills/ralph-loop/steps/step-02-execute.md +47 -0
|
@@ -202,12 +202,13 @@ public class {Name}Configuration : IEntityTypeConfiguration<{Name}>
|
|
|
202
202
|
|
|
203
203
|
## Service Pattern (tenant-scoped, MANDATORY)
|
|
204
204
|
|
|
205
|
-
> **CRITICAL:** ALL services MUST inject `
|
|
205
|
+
> **CRITICAL:** ALL services MUST inject `ICurrentUserService` + `ICurrentTenantService` and filter by `TenantId`. Missing TenantId = OWASP A01 vulnerability.
|
|
206
206
|
|
|
207
207
|
```csharp
|
|
208
208
|
using Microsoft.EntityFrameworkCore;
|
|
209
209
|
using Microsoft.Extensions.Logging;
|
|
210
210
|
using SmartStack.Application.Common.Interfaces.Identity;
|
|
211
|
+
using SmartStack.Application.Common.Interfaces.Tenants;
|
|
211
212
|
using SmartStack.Application.Common.Interfaces.Persistence;
|
|
212
213
|
|
|
213
214
|
namespace {ProjectName}.Infrastructure.Services.{Context}.{App}.{Module};
|
|
@@ -215,16 +216,19 @@ namespace {ProjectName}.Infrastructure.Services.{Context}.{App}.{Module};
|
|
|
215
216
|
public class {Name}Service : I{Name}Service
|
|
216
217
|
{
|
|
217
218
|
private readonly IExtensionsDbContext _db;
|
|
218
|
-
private readonly
|
|
219
|
+
private readonly ICurrentUserService _currentUser;
|
|
220
|
+
private readonly ICurrentTenantService _currentTenant;
|
|
219
221
|
private readonly ILogger<{Name}Service> _logger;
|
|
220
222
|
|
|
221
223
|
public {Name}Service(
|
|
222
224
|
IExtensionsDbContext db,
|
|
223
|
-
|
|
225
|
+
ICurrentUserService currentUser,
|
|
226
|
+
ICurrentTenantService currentTenant,
|
|
224
227
|
ILogger<{Name}Service> logger)
|
|
225
228
|
{
|
|
226
229
|
_db = db;
|
|
227
230
|
_currentUser = currentUser;
|
|
231
|
+
_currentTenant = currentTenant;
|
|
228
232
|
_logger = logger;
|
|
229
233
|
}
|
|
230
234
|
|
|
@@ -234,8 +238,12 @@ public class {Name}Service : I{Name}Service
|
|
|
234
238
|
int pageSize = 20,
|
|
235
239
|
CancellationToken ct = default)
|
|
236
240
|
{
|
|
241
|
+
// MANDATORY guard — throws 401 if no tenant context (e.g., missing X-Tenant-Slug header)
|
|
242
|
+
var tenantId = _currentTenant.TenantId
|
|
243
|
+
?? throw new UnauthorizedAccessException("Tenant context is required");
|
|
244
|
+
|
|
237
245
|
var query = _db.{Name}s
|
|
238
|
-
.Where(x => x.TenantId ==
|
|
246
|
+
.Where(x => x.TenantId == tenantId) // MANDATORY tenant filter
|
|
239
247
|
.AsNoTracking();
|
|
240
248
|
|
|
241
249
|
// Search filter — enables EntityLookup on frontend
|
|
@@ -259,8 +267,11 @@ public class {Name}Service : I{Name}Service
|
|
|
259
267
|
|
|
260
268
|
public async Task<{Name}ResponseDto?> GetByIdAsync(Guid id, CancellationToken ct)
|
|
261
269
|
{
|
|
270
|
+
var tenantId = _currentTenant.TenantId
|
|
271
|
+
?? throw new UnauthorizedAccessException("Tenant context is required");
|
|
272
|
+
|
|
262
273
|
return await _db.{Name}s
|
|
263
|
-
.Where(x => x.Id == id && x.TenantId ==
|
|
274
|
+
.Where(x => x.Id == id && x.TenantId == tenantId) // MANDATORY
|
|
264
275
|
.AsNoTracking()
|
|
265
276
|
.Select(x => new {Name}ResponseDto(x.Id, x.Code, x.Name, x.CreatedAt))
|
|
266
277
|
.FirstOrDefaultAsync(ct);
|
|
@@ -268,8 +279,11 @@ public class {Name}Service : I{Name}Service
|
|
|
268
279
|
|
|
269
280
|
public async Task<{Name}ResponseDto> CreateAsync(Create{Name}Dto dto, CancellationToken ct)
|
|
270
281
|
{
|
|
282
|
+
var tenantId = _currentTenant.TenantId
|
|
283
|
+
?? throw new UnauthorizedAccessException("Tenant context is required");
|
|
284
|
+
|
|
271
285
|
var entity = {Name}.Create(
|
|
272
|
-
tenantId:
|
|
286
|
+
tenantId: tenantId, // MANDATORY — never Guid.Empty
|
|
273
287
|
code: dto.Code,
|
|
274
288
|
name: dto.Name);
|
|
275
289
|
|
|
@@ -279,15 +293,18 @@ public class {Name}Service : I{Name}Service
|
|
|
279
293
|
await _db.SaveChangesAsync(ct);
|
|
280
294
|
|
|
281
295
|
_logger.LogInformation("Created {Entity} {Id} for tenant {TenantId}",
|
|
282
|
-
nameof({Name}), entity.Id,
|
|
296
|
+
nameof({Name}), entity.Id, tenantId);
|
|
283
297
|
|
|
284
298
|
return new {Name}ResponseDto(entity.Id, entity.Code, entity.Name, entity.CreatedAt);
|
|
285
299
|
}
|
|
286
300
|
|
|
287
301
|
public async Task DeleteAsync(Guid id, CancellationToken ct)
|
|
288
302
|
{
|
|
303
|
+
var tenantId = _currentTenant.TenantId
|
|
304
|
+
?? throw new UnauthorizedAccessException("Tenant context is required");
|
|
305
|
+
|
|
289
306
|
var entity = await _db.{Name}s
|
|
290
|
-
.FirstOrDefaultAsync(x => x.Id == id && x.TenantId ==
|
|
307
|
+
.FirstOrDefaultAsync(x => x.Id == id && x.TenantId == tenantId, ct)
|
|
291
308
|
?? throw new KeyNotFoundException($"{Name} {id} not found");
|
|
292
309
|
|
|
293
310
|
_db.{Name}s.Remove(entity);
|
|
@@ -296,14 +313,24 @@ public class {Name}Service : I{Name}Service
|
|
|
296
313
|
}
|
|
297
314
|
```
|
|
298
315
|
|
|
299
|
-
**Key interfaces:**
|
|
300
|
-
- `
|
|
316
|
+
**Key interfaces (from SmartStack NuGet package):**
|
|
317
|
+
- `ICurrentUserService` (from `SmartStack.Application.Common.Interfaces.Identity`): provides `UserId` (Guid?), `Email` (string?), `IsAuthenticated` (bool)
|
|
318
|
+
- `ICurrentTenantService` (from `SmartStack.Application.Common.Interfaces.Tenants`): provides `TenantId` (Guid?), `HasTenant` (bool), `TenantSlug` (string?)
|
|
301
319
|
- `IExtensionsDbContext` (for client extensions) or `ICoreDbContext` (for platform)
|
|
302
320
|
|
|
321
|
+
**MANDATORY guard clause (first line of every method):**
|
|
322
|
+
```csharp
|
|
323
|
+
var tenantId = _currentTenant.TenantId
|
|
324
|
+
?? throw new UnauthorizedAccessException("Tenant context is required");
|
|
325
|
+
```
|
|
326
|
+
This converts a null TenantId into a clean 401 response via `GlobalExceptionHandlerMiddleware`.
|
|
327
|
+
|
|
303
328
|
**FORBIDDEN in services:**
|
|
304
|
-
- `
|
|
305
|
-
-
|
|
329
|
+
- `_currentTenant.TenantId!.Value` — throws `InvalidOperationException` (500) instead of clean 401
|
|
330
|
+
- `tenantId: Guid.Empty` — always use validated tenantId from guard clause
|
|
331
|
+
- Queries WITHOUT `.Where(x => x.TenantId == tenantId)` — data leak
|
|
306
332
|
- Missing `ILogger<T>` — undiagnosable in production
|
|
333
|
+
- Using `ICurrentUser` (does NOT exist) — use `ICurrentUserService` + `ICurrentTenantService`
|
|
307
334
|
|
|
308
335
|
---
|
|
309
336
|
|
|
@@ -405,14 +432,25 @@ private static string ToKebabCase(string value)
|
|
|
405
432
|
|
|
406
433
|
### SeedConstants Pattern
|
|
407
434
|
|
|
435
|
+
> **CRITICAL — NavigationContext IDs are NEVER generated.**
|
|
436
|
+
> Contexts (`business`, `platform`, `personal`) are pre-seeded by SmartStack core with hardcoded GUIDs.
|
|
437
|
+
> You MUST query the context by code at runtime in the SeedDataProvider:
|
|
438
|
+
> `var ctx = await db.NavigationContexts.FirstOrDefaultAsync(c => c.Code == "business", ct);`
|
|
439
|
+
> **FORBIDDEN:** `DeterministicGuid("nav:business")` or any ContextId constant in SeedConstants.
|
|
440
|
+
|
|
408
441
|
```csharp
|
|
409
442
|
public static class SeedConstants
|
|
410
443
|
{
|
|
411
444
|
// Deterministic GUIDs (SHA256-based, reproducible across environments)
|
|
445
|
+
// NOTE: Application/Module/Section/Resource IDs are deterministic.
|
|
446
|
+
// Context IDs are NOT — they are pre-seeded by SmartStack core.
|
|
412
447
|
public static readonly Guid ApplicationId = DeterministicGuid("nav:business.humanresources");
|
|
413
448
|
public static readonly Guid ModuleId = DeterministicGuid("nav:business.humanresources.employees");
|
|
414
449
|
public static readonly Guid SectionId = DeterministicGuid("nav:business.humanresources.employees.list");
|
|
415
450
|
|
|
451
|
+
// FORBIDDEN — Context IDs are NOT deterministic, they come from SmartStack core:
|
|
452
|
+
// public static readonly Guid BusinessContextId = DeterministicGuid("nav:business"); // WRONG!
|
|
453
|
+
|
|
416
454
|
private static Guid DeterministicGuid(string input)
|
|
417
455
|
{
|
|
418
456
|
var hash = System.Security.Cryptography.SHA256.HashData(
|
|
@@ -429,6 +467,11 @@ public static class SeedConstants
|
|
|
429
467
|
### Navigation Seed Data Example
|
|
430
468
|
|
|
431
469
|
```csharp
|
|
470
|
+
// CRITICAL: Look up the context by code — NEVER use a hardcoded/deterministic GUID
|
|
471
|
+
var businessCtx = await db.NavigationContexts
|
|
472
|
+
.FirstOrDefaultAsync(c => c.Code == "business", ct);
|
|
473
|
+
if (businessCtx == null) return; // Context not yet seeded by SmartStack core
|
|
474
|
+
|
|
432
475
|
// Application: /business/human-resources
|
|
433
476
|
var app = NavigationApplication.Create(
|
|
434
477
|
businessCtx.Id, "humanresources", "Human Resources", "HR Management",
|
|
@@ -458,6 +501,7 @@ var section = NavigationSection.Create(
|
|
|
458
501
|
| `"humanresources"` as route | Must be `"/business/human-resources"` (full path, kebab-case) |
|
|
459
502
|
| `"employees"` as route | Must be `"/business/human-resources/employees"` (includes parent) |
|
|
460
503
|
| `Guid.NewGuid()` in seed data | Must use deterministic GUIDs (SHA256) |
|
|
504
|
+
| `DeterministicGuid("nav:business")` for ContextId | Context IDs are pre-seeded by SmartStack core — look up by code |
|
|
461
505
|
| Missing translations | Must have 4 languages: fr, en, it, de |
|
|
462
506
|
| Missing NavigationApplicationSeedData | Menu invisible without Application level |
|
|
463
507
|
|
|
@@ -498,8 +542,10 @@ services.AddValidatorsFromAssemblyContaining<Create{Name}DtoValidator>();
|
|
|
498
542
|
| `[Route] + [NavRoute]` | Only `[NavRoute]` needed (resolves route from DB) |
|
|
499
543
|
| `SmartStack.Domain.Common.Interfaces` | Wrong — interfaces are in `SmartStack.Domain.Common` directly |
|
|
500
544
|
| `[Authorize]` without `[RequirePermission]` | No RBAC enforcement — always use `[RequirePermission]` |
|
|
501
|
-
| `tenantId: Guid.Empty` in services | OWASP A01 — always use `
|
|
502
|
-
| Service without `
|
|
545
|
+
| `tenantId: Guid.Empty` in services | OWASP A01 — always use validated `_currentTenant.TenantId` |
|
|
546
|
+
| Service without `ICurrentTenantService` | All tenant data leaks — inject `ICurrentTenantService` |
|
|
547
|
+
| `ICurrentUser` in service code | Does NOT exist — use `ICurrentUserService` + `ICurrentTenantService` |
|
|
548
|
+
| `_currentTenant.TenantId!.Value` | Crashes with 500 — use `?? throw new UnauthorizedAccessException(...)` |
|
|
503
549
|
| Route `"humanresources"` in seed data | Must be full path `"/business/human-resources"` |
|
|
504
550
|
| Route without leading `/` | All routes must start with `/` |
|
|
505
551
|
| `Permission.Create()` | Does NOT exist — use `CreateForModule()`, `CreateForSection()`, etc. |
|
|
@@ -648,3 +694,34 @@ public class UpdateMyEntityDtoValidator : AbstractValidator<UpdateMyEntityDto>
|
|
|
648
694
|
```
|
|
649
695
|
|
|
650
696
|
**Why it's wrong:** Without an `UpdateValidator`, the Update endpoint accepts **any data without validation**.
|
|
697
|
+
|
|
698
|
+
---
|
|
699
|
+
|
|
700
|
+
### Anti-Pattern 6: `TenantId!.Value` null-forgiving operator (RUNTIME CRASH)
|
|
701
|
+
|
|
702
|
+
The `!` (null-forgiving) operator followed by `.Value` on a `Guid?` suppresses compiler warnings but **throws `InvalidOperationException` at runtime** when TenantId is null.
|
|
703
|
+
|
|
704
|
+
**INCORRECT — Crashes with 500 Internal Server Error:**
|
|
705
|
+
```csharp
|
|
706
|
+
// WRONG: Throws InvalidOperationException("Nullable object must have a value") → 500 error
|
|
707
|
+
public async Task<PaginatedResult<MyEntityDto>> GetAllAsync(...)
|
|
708
|
+
{
|
|
709
|
+
var tenantId = _currentTenant.TenantId!.Value; // CRASH if no tenant context
|
|
710
|
+
var query = _db.MyEntities.Where(x => x.TenantId == tenantId);
|
|
711
|
+
// ...
|
|
712
|
+
}
|
|
713
|
+
```
|
|
714
|
+
|
|
715
|
+
**CORRECT — Clean 401 via GlobalExceptionHandlerMiddleware:**
|
|
716
|
+
```csharp
|
|
717
|
+
// CORRECT: Throws UnauthorizedAccessException → middleware converts to 401
|
|
718
|
+
public async Task<PaginatedResult<MyEntityDto>> GetAllAsync(...)
|
|
719
|
+
{
|
|
720
|
+
var tenantId = _currentTenant.TenantId
|
|
721
|
+
?? throw new UnauthorizedAccessException("Tenant context is required");
|
|
722
|
+
var query = _db.MyEntities.Where(x => x.TenantId == tenantId);
|
|
723
|
+
// ...
|
|
724
|
+
}
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
**Why it's wrong:** When a user hits an API via Swagger with a valid JWT but no tenant context (missing `X-Tenant-Slug` header), `TenantId` is null. The `!.Value` pattern produces an opaque `500 Internal Server Error` instead of a clear `401 Unauthorized` with an actionable message. The `?? throw new UnauthorizedAccessException(...)` pattern is caught by `GlobalExceptionHandlerMiddleware` and returned as a proper 401 response.
|
|
@@ -62,13 +62,14 @@
|
|
|
62
62
|
| Validate | MCP `validate_conventions` |
|
|
63
63
|
|
|
64
64
|
**Rules:**
|
|
65
|
-
- **ALL services MUST inject `
|
|
65
|
+
- **ALL services MUST inject `ICurrentUserService` + `ICurrentTenantService` and filter by `TenantId`** (see `smartstack-api.md` Service Pattern)
|
|
66
|
+
- **ALL services MUST use guard clause:** `var tenantId = _currentTenant.TenantId ?? throw new UnauthorizedAccessException("Tenant context is required");`
|
|
66
67
|
- **ALL services MUST inject `ILogger<T>`** for production diagnostics
|
|
67
68
|
- CQRS with MediatR
|
|
68
69
|
- FluentValidation for all commands
|
|
69
70
|
- DTOs separate from domain entities
|
|
70
71
|
- Service interfaces in Application, implementations in Infrastructure
|
|
71
|
-
- **FORBIDDEN:** `tenantId: Guid.Empty`, queries without TenantId filter,
|
|
72
|
+
- **FORBIDDEN:** `tenantId: Guid.Empty`, `TenantId!.Value`, queries without TenantId filter, `ICurrentUser` (does not exist — use `ICurrentUserService` + `ICurrentTenantService`)
|
|
72
73
|
|
|
73
74
|
---
|
|
74
75
|
|
|
@@ -118,18 +119,28 @@
|
|
|
118
119
|
3. **NavigationSectionSeedData.cs** — Section-level navigation (if sections defined)
|
|
119
120
|
4. **NavigationResourceSeedData.cs** — Resource-level navigation (if resources defined)
|
|
120
121
|
5. **PermissionsSeedData.cs** — MCP `generate_permissions` first, fallback template
|
|
121
|
-
6. **RolesSeedData.cs** —
|
|
122
|
+
6. **RolesSeedData.cs** — Code-based role mapping: Admin=CRUD, Manager=CRU, Contributor=CR, Viewer=R. **NEVER use deterministic GUIDs for roles** — system roles are pre-seeded by SmartStack core, look up by Code at runtime.
|
|
122
123
|
7. **{App}SeedDataProvider.cs** — Implements IClientSeedDataProvider
|
|
123
124
|
- `SeedNavigationAsync()` — seeds Application → Module → Section → Resource + translations
|
|
124
|
-
- `SeedPermissionsAsync()` + `SeedRolePermissionsAsync()`
|
|
125
|
+
- `SeedPermissionsAsync()` + `SeedRolePermissionsAsync()` (roles resolved by Code, NOT by GUID)
|
|
125
126
|
- DI: `services.AddScoped<IClientSeedDataProvider, {App}SeedDataProvider>()`
|
|
126
127
|
|
|
127
128
|
### Deterministic GUID Pattern
|
|
128
129
|
|
|
130
|
+
> **CRITICAL — NavigationContext IDs are NEVER generated.**
|
|
131
|
+
> Contexts (`business`, `platform`, `personal`) are pre-seeded by SmartStack core with hardcoded GUIDs.
|
|
132
|
+
> Look up the context at runtime: `db.NavigationContexts.FirstOrDefaultAsync(c => c.Code == "business", ct)`
|
|
133
|
+
> **FORBIDDEN:** `GenerateDeterministicGuid("nav:business")` or any ContextId in SeedConstants.
|
|
134
|
+
|
|
129
135
|
```csharp
|
|
130
136
|
// Use SHA256 for deterministic GUIDs (reproducible across environments)
|
|
137
|
+
// NOTE: Application/Module/Section/Resource IDs are deterministic.
|
|
138
|
+
// Context IDs are NOT — they are pre-seeded by SmartStack core.
|
|
131
139
|
public static readonly Guid ModuleId = GenerateDeterministicGuid("nav-module-{app}-{module}");
|
|
132
140
|
|
|
141
|
+
// FORBIDDEN — Context IDs must NOT be generated:
|
|
142
|
+
// public static readonly Guid BusinessContextId = GenerateDeterministicGuid("nav:business"); // WRONG!
|
|
143
|
+
|
|
133
144
|
private static Guid GenerateDeterministicGuid(string input)
|
|
134
145
|
{
|
|
135
146
|
var hash = System.Security.Cryptography.SHA256.HashData(
|
|
@@ -181,6 +192,7 @@ var sectionRoute = $"{moduleRoute}/{ToKebabCase(sectionCode)}";
|
|
|
181
192
|
|
|
182
193
|
**FORBIDDEN:**
|
|
183
194
|
- `Guid.NewGuid()` → use deterministic GUIDs (SHA256)
|
|
195
|
+
- `DeterministicGuid("nav:business")` for ContextId → context IDs are pre-seeded by SmartStack core, look up by code at runtime
|
|
184
196
|
- Missing translations (must have fr, en, it, de)
|
|
185
197
|
- Empty seed classes with no seeding logic
|
|
186
198
|
- PascalCase in route URLs → always kebab-case
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: step-00-init
|
|
3
|
-
description: Parse flags, detect
|
|
3
|
+
description: Parse flags, detect context, verify MCP, define hierarchy (5 levels), challenge the need
|
|
4
4
|
model: sonnet
|
|
5
5
|
next_step: steps/step-01-analyze.md
|
|
6
6
|
---
|
|
@@ -67,21 +67,7 @@ Scan the project to identify the SmartStack hierarchy:
|
|
|
67
67
|
- `{prd_path}`: `.ralph/prd-{module_code}.json` if exists
|
|
68
68
|
- `{feature_path}`: `docs/business/{app}/{module}/business-analyse/*/feature.json` if exists
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
```yaml
|
|
73
|
-
questions:
|
|
74
|
-
- header: "Context"
|
|
75
|
-
question: "What is the SmartStack context for this work?"
|
|
76
|
-
options:
|
|
77
|
-
- label: "Business (Recommended)"
|
|
78
|
-
description: "Business application module"
|
|
79
|
-
- label: "Platform"
|
|
80
|
-
description: "Platform administration or support"
|
|
81
|
-
- label: "Personal"
|
|
82
|
-
description: "Personal user space"
|
|
83
|
-
multiSelect: false
|
|
84
|
-
```
|
|
70
|
+
If hierarchy cannot be inferred → load context detection question from `references/challenge-questions.md`.
|
|
85
71
|
|
|
86
72
|
---
|
|
87
73
|
|
|
@@ -96,70 +82,112 @@ IF failure → warn user, continue in degraded mode (manual tools only)
|
|
|
96
82
|
|
|
97
83
|
---
|
|
98
84
|
|
|
99
|
-
## 4.
|
|
85
|
+
## 4. Define Navigation Hierarchy (5 Levels)
|
|
86
|
+
|
|
87
|
+
> **BLOCKING RULE:** SmartStack uses a 5-level navigation hierarchy:
|
|
88
|
+
> **Context → Application → Module → Section → Resource**
|
|
89
|
+
>
|
|
90
|
+
> **Every module MUST have at least one section.** A module without sections produces an incomplete navigation tree, broken seed data, and missing frontend routes.
|
|
91
|
+
|
|
92
|
+
### 4a. Validate Application Level
|
|
93
|
+
|
|
94
|
+
Load and execute application question from `references/challenge-questions.md` section 4a.
|
|
95
|
+
|
|
96
|
+
### 4b. Validate Module Level
|
|
97
|
+
|
|
98
|
+
Load and execute module question from `references/challenge-questions.md` section 4b.
|
|
99
|
+
|
|
100
|
+
### 4c. Define Sections (MANDATORY — at least one)
|
|
101
|
+
|
|
102
|
+
Load and execute section questions from `references/challenge-questions.md` section 4c (includes validation and storage format).
|
|
100
103
|
|
|
104
|
+
### 4d. Resources (Optional)
|
|
105
|
+
|
|
106
|
+
If the task or feature.json mentions specific resources (export, import, bulk actions):
|
|
107
|
+
```
|
|
108
|
+
For each section, check if resources are needed.
|
|
109
|
+
Resources are OPTIONAL — only define if explicitly mentioned or inferred.
|
|
101
110
|
```
|
|
102
|
-
needs_seed_data = true IF:
|
|
103
|
-
- New module or new section with navigation entries
|
|
104
|
-
- Task mentions "navigation", "permissions", "roles", "seed"
|
|
105
111
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
112
|
+
### 4e. Hierarchy Summary
|
|
113
|
+
|
|
114
|
+
Display before proceeding:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
{context_code} → {app_name} ({app_code}) → {module_code} → [{sections[].code}]
|
|
118
|
+
Routes: /{context_code}/{app-kebab}/{module-kebab}/{section-kebab}
|
|
109
119
|
```
|
|
110
120
|
|
|
111
121
|
---
|
|
112
122
|
|
|
113
|
-
## 5.
|
|
123
|
+
## 5. Challenge the Need
|
|
124
|
+
|
|
125
|
+
> **Objective:** Even without a formal Business Analysis (`/business-analyse`), the need MUST be challenged to avoid obvious gaps. This is a rapid scope validation — 3-4 targeted questions to ensure the developer has thought through the fundamentals.
|
|
126
|
+
|
|
127
|
+
### 5a. Entity Scope & Relationships
|
|
128
|
+
|
|
129
|
+
Load and execute entity questions from `references/challenge-questions.md` section 5a.
|
|
130
|
+
|
|
131
|
+
### 5b. Dependencies & Key Properties
|
|
132
|
+
|
|
133
|
+
Load and execute dependency questions from `references/challenge-questions.md` section 5b.
|
|
114
134
|
|
|
135
|
+
### 5c. Store Responses
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
{entities}: string[] # Main entities to manage
|
|
139
|
+
{module_complexity}: string # "simple-crud" | "crud-rules" | "crud-workflow" | "complex"
|
|
140
|
+
{has_dependencies}: string # "none" | "references" | "unknown"
|
|
141
|
+
{key_properties}: string[] # Key business properties mentioned by user
|
|
115
142
|
```
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
143
|
+
|
|
144
|
+
These values are propagated to:
|
|
145
|
+
- **step-01 (Analyze):** Guide code exploration focus
|
|
146
|
+
- **step-02 (Plan):** Inform layer mapping (e.g., workflow → needs /workflow skill)
|
|
147
|
+
- **step-03 (Execute):** Entity scaffolding with correct properties and relationships
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 6. Determine Needs
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
needs_seed_data = {sections}.length > 0 OR task mentions navigation/permissions/roles/seed
|
|
155
|
+
needs_migration = {entities}.length > 0 OR task mentions entity/field/table/column/migration
|
|
156
|
+
needs_workflow = {module_complexity} == "crud-workflow" OR task mentions workflow/email/approval
|
|
157
|
+
needs_notification = {module_complexity} in ["crud-workflow","complex"] OR task mentions notification/alert
|
|
122
158
|
```
|
|
123
159
|
|
|
124
160
|
---
|
|
125
161
|
|
|
126
|
-
##
|
|
162
|
+
## 7. Resume Mode (if -r)
|
|
127
163
|
|
|
128
164
|
```
|
|
129
|
-
IF
|
|
130
|
-
|
|
131
|
-
2. Create .claude/output/apex/{task_id}/
|
|
132
|
-
3. Write 00-context.md with:
|
|
133
|
-
- Timestamp
|
|
134
|
-
- All state variables (generic + SmartStack)
|
|
135
|
-
- Flags
|
|
136
|
-
- Detected hierarchy
|
|
165
|
+
IF resume_mode:
|
|
166
|
+
Read .claude/output/apex/ → find latest task → restore state from 00-context.md → SKIP to next step
|
|
137
167
|
```
|
|
138
168
|
|
|
139
169
|
---
|
|
140
170
|
|
|
141
|
-
##
|
|
171
|
+
## 8. Save Mode Setup (if -s)
|
|
142
172
|
|
|
143
173
|
```
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
174
|
+
IF save_mode:
|
|
175
|
+
Generate task_id → create .claude/output/apex/{task_id}/ → write 00-context.md
|
|
176
|
+
(timestamp, state variables, flags, hierarchy, challenge responses)
|
|
177
|
+
```
|
|
148
178
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
| Context | {context_code} / {app_name} / {module_code} |
|
|
153
|
-
| Sections | {sections} |
|
|
154
|
-
| PRD | {prd_path || "none"} |
|
|
155
|
-
| Feature | {feature_path || "none"} |
|
|
156
|
-
| Flags | {active_flags} |
|
|
157
|
-
| MCP | {available|degraded} |
|
|
158
|
-
| Needs migration | {yes|no} |
|
|
159
|
-
| Needs seed data | {yes|no} |
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 9. Display Context Summary
|
|
160
182
|
|
|
183
|
+
```
|
|
184
|
+
APEX INIT COMPLETE — v{{SMARTSTACK_VERSION}}
|
|
185
|
+
Task: {task_description}
|
|
186
|
+
Context: {context_code} → {app_name} → {module_code} → {sections[].code}
|
|
187
|
+
Entities: {entities} | Complexity: {module_complexity} | Deps: {has_dependencies}
|
|
188
|
+
PRD: {prd_path||none} | Feature: {feature_path||none} | Flags: {active_flags}
|
|
189
|
+
MCP: {available|degraded} | Needs: migration/seed/workflow/notification = {yes|no}
|
|
161
190
|
NEXT STEP: step-01-analyze
|
|
162
|
-
═══════════════════════════════════════════════════════════════
|
|
163
191
|
```
|
|
164
192
|
|
|
165
193
|
---
|