@atlashub/smartstack-cli 3.9.0 → 3.12.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 (48) hide show
  1. package/dist/index.js +2544 -2461
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +479 -6185
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/agents/ba-writer.md +178 -0
  7. package/templates/agents/db-reader.md +149 -0
  8. package/templates/skills/application/references/application-roles-template.md +227 -0
  9. package/templates/skills/application/references/provider-template.md +30 -6
  10. package/templates/skills/application/steps/step-03-roles.md +45 -7
  11. package/templates/skills/application/steps/step-03b-provider.md +13 -6
  12. package/templates/skills/business-analyse/SKILL.md +56 -4
  13. package/templates/skills/business-analyse/references/agent-pooling-best-practices.md +477 -0
  14. package/templates/skills/business-analyse/references/cache-warming-strategy.md +578 -0
  15. package/templates/skills/business-analyse/references/cadrage-vibe-coding.md +9 -19
  16. package/templates/skills/business-analyse/references/consolidation-structural-checks.md +12 -2
  17. package/templates/skills/business-analyse/references/deploy-data-build.md +36 -25
  18. package/templates/skills/business-analyse/references/detection-strategies.md +424 -0
  19. package/templates/skills/business-analyse/references/html-data-mapping.md +4 -0
  20. package/templates/skills/business-analyse/references/prd-generation.md +258 -0
  21. package/templates/skills/business-analyse/references/robustness-checks.md +538 -0
  22. package/templates/skills/business-analyse/references/validate-incremental-html.md +47 -4
  23. package/templates/skills/business-analyse/references/validation-checklist.md +281 -0
  24. package/templates/skills/business-analyse/schemas/sections/specification-schema.json +33 -1
  25. package/templates/skills/business-analyse/steps/step-00-init.md +70 -75
  26. package/templates/skills/business-analyse/steps/step-01-cadrage.md +8 -22
  27. package/templates/skills/business-analyse/steps/step-03a-data.md +20 -410
  28. package/templates/skills/business-analyse/steps/step-03a1-setup.md +356 -0
  29. package/templates/skills/business-analyse/steps/step-03a2-analysis.md +143 -0
  30. package/templates/skills/business-analyse/steps/step-03b-ui.md +3 -0
  31. package/templates/skills/business-analyse/steps/step-03c-compile.md +72 -3
  32. package/templates/skills/business-analyse/steps/step-03d-validate.md +36 -3
  33. package/templates/skills/business-analyse/steps/step-04-consolidation.md +21 -440
  34. package/templates/skills/business-analyse/steps/step-04a-collect.md +304 -0
  35. package/templates/skills/business-analyse/steps/step-04b-analyze.md +239 -0
  36. package/templates/skills/business-analyse/steps/step-04c-decide.md +186 -0
  37. package/templates/skills/business-analyse/steps/step-05a-handoff.md +44 -0
  38. package/templates/skills/business-analyse/steps/step-05b-deploy.md +42 -2
  39. package/templates/skills/business-analyse/steps/step-05c-ralph-readiness.md +518 -0
  40. package/templates/skills/controller/steps/step-03-generate.md +184 -24
  41. package/templates/skills/controller/templates.md +11 -2
  42. package/templates/skills/debug/SKILL.md +156 -53
  43. package/templates/skills/debug/references/team-protocol.md +232 -0
  44. package/templates/skills/ralph-loop/references/category-rules.md +46 -0
  45. package/templates/skills/ralph-loop/references/compact-loop.md +32 -2
  46. package/templates/skills/ralph-loop/references/core-seed-data.md +233 -21
  47. package/templates/skills/ralph-loop/steps/step-00-init.md +64 -1
  48. package/templates/skills/ralph-loop/steps/step-04-check.md +27 -2
@@ -279,7 +279,129 @@ If MCP `generate_permissions` fails, use the template above directly with values
279
279
 
280
280
  ---
281
281
 
282
- ## 4. RolesSeedData.cs
282
+ ## 4. ApplicationRolesSeedData.cs (Application-Level, Once per Application)
283
+
284
+ **File:** `Infrastructure/Persistence/Seeding/Data/ApplicationRolesSeedData.cs`
285
+
286
+ ### Purpose
287
+
288
+ Creates the 4 standard application-scoped roles: Admin, Manager, Contributor, Viewer.
289
+
290
+ **CRITICAL:** This file is created **ONCE per application** (not per module).
291
+ Without these roles, role-permission mappings in `SeedRolePermissionsAsync()` will fail silently.
292
+
293
+ ### GUID Generation Rule
294
+
295
+ ```csharp
296
+ // Deterministic GUID from application ID + role type
297
+ private static Guid GenerateRoleGuid(string roleType)
298
+ {
299
+ using var sha256 = System.Security.Cryptography.SHA256.Create();
300
+ var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"role-{ApplicationId}-{roleType}"));
301
+ return new Guid(hash.Take(16).ToArray());
302
+ }
303
+ // roleType values: "admin", "manager", "contributor", "viewer"
304
+ ```
305
+
306
+ ### Template
307
+
308
+ ```csharp
309
+ using SmartStack.Domain.Platform.Administration.Roles;
310
+
311
+ namespace {BaseNamespace}.Infrastructure.Persistence.Seeding.Data;
312
+
313
+ /// <summary>
314
+ /// Application-scoped role seed data for {AppLabel}.
315
+ /// Defines the 4 standard application roles: Admin, Manager, Contributor, Viewer.
316
+ /// Consumed by IClientSeedDataProvider at application startup.
317
+ /// </summary>
318
+ public static class ApplicationRolesSeedData
319
+ {
320
+ // Application ID from NavigationApplicationSeedData
321
+ private static readonly Guid ApplicationId = {ApplicationGuid};
322
+
323
+ public static readonly Guid AdminRoleId = GenerateRoleGuid("admin");
324
+ public static readonly Guid ManagerRoleId = GenerateRoleGuid("manager");
325
+ public static readonly Guid ContributorRoleId = GenerateRoleGuid("contributor");
326
+ public static readonly Guid ViewerRoleId = GenerateRoleGuid("viewer");
327
+
328
+ public static IEnumerable<ApplicationRoleSeedEntry> GetRoleEntries()
329
+ {
330
+ yield return new ApplicationRoleSeedEntry
331
+ {
332
+ Id = AdminRoleId,
333
+ Code = "admin",
334
+ Name = "{AppLabel} Admin",
335
+ Description = "Full administrative access to {AppLabel}",
336
+ ApplicationId = ApplicationId,
337
+ IsSystem = false,
338
+ IsActive = true,
339
+ DisplayOrder = 1
340
+ };
341
+
342
+ yield return new ApplicationRoleSeedEntry
343
+ {
344
+ Id = ManagerRoleId,
345
+ Code = "manager",
346
+ Name = "{AppLabel} Manager",
347
+ Description = "Management access to {AppLabel} (Create, Read, Update)",
348
+ ApplicationId = ApplicationId,
349
+ IsSystem = false,
350
+ IsActive = true,
351
+ DisplayOrder = 2
352
+ };
353
+
354
+ yield return new ApplicationRoleSeedEntry
355
+ {
356
+ Id = ContributorRoleId,
357
+ Code = "contributor",
358
+ Name = "{AppLabel} Contributor",
359
+ Description = "Contributor access to {AppLabel} (Create, Read)",
360
+ ApplicationId = ApplicationId,
361
+ IsSystem = false,
362
+ IsActive = true,
363
+ DisplayOrder = 3
364
+ };
365
+
366
+ yield return new ApplicationRoleSeedEntry
367
+ {
368
+ Id = ViewerRoleId,
369
+ Code = "viewer",
370
+ Name = "{AppLabel} Viewer",
371
+ Description = "Read-only access to {AppLabel}",
372
+ ApplicationId = ApplicationId,
373
+ IsSystem = false,
374
+ IsActive = true,
375
+ DisplayOrder = 4
376
+ };
377
+ }
378
+
379
+ private static Guid GenerateRoleGuid(string roleType)
380
+ {
381
+ using var sha256 = System.Security.Cryptography.SHA256.Create();
382
+ var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes($"role-{ApplicationId}-{roleType}"));
383
+ return new Guid(hash.Take(16).ToArray());
384
+ }
385
+ }
386
+
387
+ public class ApplicationRoleSeedEntry
388
+ {
389
+ public Guid Id { get; init; }
390
+ public string Code { get; init; } = null!;
391
+ public string Name { get; init; } = null!;
392
+ public string Description { get; init; } = null!;
393
+ public Guid ApplicationId { get; init; }
394
+ public bool IsSystem { get; init; }
395
+ public bool IsActive { get; init; }
396
+ public int DisplayOrder { get; init; }
397
+ }
398
+ ```
399
+
400
+ **Replace placeholders** with values from PRD and navigation metadata.
401
+
402
+ ---
403
+
404
+ ## 5. {Module}RolesSeedData.cs (Per Module)
283
405
 
284
406
  **File:** `Infrastructure/Persistence/Seeding/Data/{ModulePascal}/RolesSeedData.cs`
285
407
 
@@ -335,7 +457,7 @@ public class RolePermissionSeedEntry
335
457
 
336
458
  ---
337
459
 
338
- ## 5. IClientSeedDataProvider Implementation
460
+ ## 6. IClientSeedDataProvider Implementation
339
461
 
340
462
  **File:** `Infrastructure/Persistence/Seeding/{AppPascalName}SeedDataProvider.cs`
341
463
 
@@ -343,10 +465,11 @@ public class RolePermissionSeedEntry
343
465
 
344
466
  | Rule | Description |
345
467
  |------|-------------|
346
- | Factory methods | `NavigationModule.Create(...)`, `Permission.CreateForModule(...)`, `RolePermission.Create(...)` — NEVER `new Entity()` |
468
+ | Factory methods | `NavigationModule.Create(...)`, `Role.Create(...)`, `Permission.CreateForModule(...)`, `RolePermission.Create(...)` — NEVER `new Entity()` |
347
469
  | Idempotence | Each Seed method checks existence before inserting |
348
- | SaveChanges per group | Navigation -> save -> Permissions -> save -> RolePermissions -> save |
349
- | FK resolution by Code | Parent entities found by `Code`, not hardcoded GUID |
470
+ | Execution order | Navigation Roles Permissions RolePermissions (roles MUST exist before mapping) |
471
+ | SaveChanges per group | Navigation -> save -> Roles -> save -> Permissions -> save -> RolePermissions -> save |
472
+ | FK resolution by Code | Parent entities (modules, roles) found by `Code`, not hardcoded GUID |
350
473
  | DI registration | `services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>()` |
351
474
 
352
475
  ### Template
@@ -357,13 +480,14 @@ using SmartStack.Application.Common.Interfaces;
357
480
  using SmartStack.Application.Common.Interfaces.Seeding;
358
481
  using SmartStack.Domain.Navigation;
359
482
  using SmartStack.Domain.Platform.Administration.Roles;
483
+ using {BaseNamespace}.Infrastructure.Persistence.Seeding.Data;
360
484
  using {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{Module1Pascal};
361
485
  // using {BaseNamespace}.Infrastructure.Persistence.Seeding.Data.{Module2Pascal}; // Add per module
362
486
 
363
487
  namespace {BaseNamespace}.Infrastructure.Persistence.Seeding;
364
488
 
365
489
  /// <summary>
366
- /// Seeds {AppLabel} navigation, permissions, and role-permission data
490
+ /// Seeds {AppLabel} navigation, roles, permissions, and role-permission data
367
491
  /// into the SmartStack Core schema at application startup.
368
492
  /// </summary>
369
493
  public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
@@ -420,6 +544,28 @@ public class {AppPascalName}SeedDataProvider : IClientSeedDataProvider
420
544
  await ((DbContext)context).SaveChangesAsync(ct);
421
545
  }
422
546
 
547
+ public async Task SeedRolesAsync(ICoreDbContext context, CancellationToken ct)
548
+ {
549
+ // Check idempotence
550
+ var applicationId = ApplicationRolesSeedData.ApplicationId;
551
+ var exists = await context.Roles
552
+ .AnyAsync(r => r.ApplicationId == applicationId, ct);
553
+ if (exists) return;
554
+
555
+ // Create application-scoped roles (Admin, Manager, Contributor, Viewer)
556
+ foreach (var entry in ApplicationRolesSeedData.GetRoleEntries())
557
+ {
558
+ var role = Role.Create(
559
+ entry.Code,
560
+ entry.Name,
561
+ entry.Description,
562
+ entry.ApplicationId,
563
+ entry.IsSystem);
564
+ context.Roles.Add(role);
565
+ }
566
+ await ((DbContext)context).SaveChangesAsync(ct);
567
+ }
568
+
423
569
  public async Task SeedPermissionsAsync(ICoreDbContext context, CancellationToken ct)
424
570
  {
425
571
  var exists = await context.Permissions
@@ -495,44 +641,110 @@ services.AddScoped<IClientSeedDataProvider, {AppPascalName}SeedDataProvider>();
495
641
 
496
642
  ---
497
643
 
498
- ## 6. Multi-Module Handling
644
+ ## 7. Multi-Module Handling
499
645
 
500
646
  When processing multiple modules in the same ralph-loop run:
501
647
 
502
648
  ### Module 1 (first): Creates everything from scratch
503
649
 
504
- 1. `{Module1}NavigationSeedData.cs`
505
- 2. `{Module1}PermissionsSeedData.cs`
506
- 3. `{Module1}RolesSeedData.cs`
507
- 4. `{AppPascalName}SeedDataProvider.cs` (new)
508
- 5. DI registration (new)
650
+ 1. `ApplicationRolesSeedData.cs` (application-level, once per app)
651
+ 2. `{Module1}NavigationSeedData.cs`
652
+ 3. `{Module1}PermissionsSeedData.cs`
653
+ 4. `{Module1}RolesSeedData.cs`
654
+ 5. `{AppPascalName}SeedDataProvider.cs` (new, with 4 methods)
655
+ 6. DI registration (new)
509
656
 
510
657
  ### Module 2+ (subsequent): Append to existing provider
511
658
 
512
- 1. `{Module2}NavigationSeedData.cs` (new file)
513
- 2. `{Module2}PermissionsSeedData.cs` (new file)
514
- 3. `{Module2}RolesSeedData.cs` (new file)
515
- 4. `{AppPascalName}SeedDataProvider.cs` (**modify** — add using, add entries in each Seed method)
516
- 5. DI registration (already exists skip)
659
+ 1. `ApplicationRolesSeedData.cs` (already exists — skip)
660
+ 2. `{Module2}NavigationSeedData.cs` (new file)
661
+ 3. `{Module2}PermissionsSeedData.cs` (new file)
662
+ 4. `{Module2}RolesSeedData.cs` (new file)
663
+ 5. `{AppPascalName}SeedDataProvider.cs` (**modify**add using, add entries in Navigation/Permissions/RolePermissions methods)
664
+ 6. DI registration (already exists — skip)
517
665
 
518
- **Detection:** Check if `{AppPascalName}SeedDataProvider.cs` exists. If yes, READ it and ADD the new module's entries to each of the 3 methods.
666
+ **Detection:** Check if `{AppPascalName}SeedDataProvider.cs` exists. If yes, READ it and ADD the new module's entries to the appropriate methods (Navigation, Permissions, RolePermissions). Do NOT modify SeedRolesAsync().
519
667
 
520
668
  ---
521
669
 
522
- ## 7. Verification Checklist (BLOCKING)
670
+ ## 8. Verification Checklist (BLOCKING)
523
671
 
524
672
  Before marking the task as completed, verify ALL:
525
673
 
526
674
  - [ ] Deterministic GUIDs (NEVER `Guid.NewGuid()`) — SHA256 of path
527
675
  - [ ] 4 languages for each navigation entity (fr, en, it, de)
676
+ - [ ] `ApplicationRolesSeedData.cs` created (once per application)
677
+ - [ ] 4 application roles defined: Admin, Manager, Contributor, Viewer
678
+ - [ ] Each role has a valid `Code` value ("admin", "manager", "contributor", "viewer")
528
679
  - [ ] `Permissions.cs` constants match seed data paths
529
680
  - [ ] MCP `generate_permissions` called (or fallback used)
530
681
  - [ ] Role-permission mappings assigned (Admin, Manager, Contributor, Viewer)
531
- - [ ] `IClientSeedDataProvider` generated with 3 methods
682
+ - [ ] `IClientSeedDataProvider` generated with 4 methods (Navigation, Roles, Permissions, RolePermissions)
683
+ - [ ] Execution order: Navigation → Roles → Permissions → RolePermissions
532
684
  - [ ] Each Seed method is idempotent (checks existence before inserting)
533
685
  - [ ] Factory methods used throughout (NEVER `new Entity()`)
534
- - [ ] `SaveChangesAsync` called per group (Navigation → Permissions → RolePermissions)
686
+ - [ ] `SaveChangesAsync` called per group (Navigation → Roles → Permissions → RolePermissions)
535
687
  - [ ] DI registration added: `services.AddScoped<IClientSeedDataProvider, ...>()`
536
688
  - [ ] `dotnet build` passes after generation
537
689
 
538
690
  **If ANY check fails, the task status = 'failed'.**
691
+
692
+ ---
693
+
694
+ ## 9. Business Seed Data (DevDataSeeder) — TenantId Rules
695
+
696
+ > **Applies to:** Seed data for business entities (reference types, categories, statuses).
697
+ > **NOT the same as** core seed data above (navigation, permissions, roles).
698
+
699
+ ### Rules
700
+
701
+ | Rule | Description |
702
+ |------|-------------|
703
+ | TenantId MANDATORY | ALL business seed entities MUST set `TenantId` |
704
+ | Deterministic TenantId | Use `SeedConstants.DefaultTenantId` (NEVER inline GUID) |
705
+ | DevDataSeeder pattern | Implement `IDevDataSeeder` with `SeedAsync()` method |
706
+ | Idempotency | Each seeder MUST check `AnyAsync()` before inserting |
707
+ | Order | DevDataSeeder `Order >= 200` (after core seed data at Order 100) |
708
+
709
+ ### Template (Reference Types)
710
+
711
+ ```csharp
712
+ public class {Module}DevDataSeeder : IDevDataSeeder
713
+ {
714
+ public int Order => 200; // After core seed data (Order 100)
715
+
716
+ public async Task SeedAsync(ExtensionsDbContext context, CancellationToken ct)
717
+ {
718
+ if (await context.Set<{EntityType}>().AnyAsync(ct)) return;
719
+
720
+ var items = new[]
721
+ {
722
+ new {EntityType}
723
+ {
724
+ Id = GenerateDeterministicGuid("{entity-type}-{code}"),
725
+ Code = "{code}",
726
+ Name = "{name}",
727
+ TenantId = SeedConstants.DefaultTenantId, // MANDATORY
728
+ IsActive = true
729
+ }
730
+ };
731
+
732
+ context.Set<{EntityType}>().AddRange(items);
733
+ await context.SaveChangesAsync(ct);
734
+ }
735
+
736
+ private static Guid GenerateDeterministicGuid(string seed)
737
+ {
738
+ using var sha256 = System.Security.Cryptography.SHA256.Create();
739
+ var hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(seed));
740
+ return new Guid(hash.Take(16).ToArray());
741
+ }
742
+ }
743
+ ```
744
+
745
+ ### FORBIDDEN
746
+
747
+ - Seeding business entities WITHOUT `TenantId`
748
+ - Using `Guid.NewGuid()` for TenantId
749
+ - Omitting idempotency check (`AnyAsync`)
750
+ - Hardcoding TenantId inline (use `SeedConstants.DefaultTenantId`)
@@ -48,7 +48,70 @@ mcp__context7__resolve-library-id: libraryName: "test" → {mcp_context7} = tru
48
48
 
49
49
  If ANY fails: show error, suggest `smartstack check-mcp`, STOP.
50
50
 
51
- ## 3. Resume Mode
51
+ ## 3. Human-in-the-Loop Checkpoint (RECOMMENDED)
52
+
53
+ > **Best Practice:** Commencer en mode supervisé avant le mode autonome complet.
54
+
55
+ ### Why HITL First?
56
+
57
+ Ralph-loop est puissant mais peut diverger si :
58
+ - Les requirements sont ambigus
59
+ - Les tests sont lents (>30s)
60
+ - Le code généré ne compile pas immédiatement
61
+
62
+ **Stratégie recommandée :**
63
+
64
+ 1. **Première itération supervisée** - Lancer manuellement la première tâche pour vérifier :
65
+ - La qualité du code généré
66
+ - Le temps d'exécution des tests
67
+ - La clarté des messages d'erreur
68
+
69
+ 2. **Mode autonome limité** - Si première itération OK, relancer avec `--max-iterations 5-10`
70
+
71
+ 3. **Mode autonome complet** - Une fois confiant, augmenter à `--max-iterations 50`
72
+
73
+ ### Checkpoint Prompt (Optional)
74
+
75
+ Si vous détectez que c'est la première utilisation de ralph-loop sur ce projet (pas de `.ralph/logs/`), proposer :
76
+
77
+ ```javascript
78
+ if (!dirExists('.ralph/logs') && !resume_mode) {
79
+ AskUserQuestion({
80
+ questions: [{
81
+ question: "Premier usage de ralph-loop détecté. Démarrer en mode supervisé ou autonome ?",
82
+ header: "Mode",
83
+ multiSelect: false,
84
+ options: [
85
+ { label: "Supervisé (recommandé)", description: "Exécuter 1 tâche, puis demander confirmation" },
86
+ { label: "Autonome limité", description: "Max 10 itérations automatiques" },
87
+ { label: "Autonome complet", description: "Jusqu'à 50 itérations (mode AFK)" }
88
+ ]
89
+ }]
90
+ });
91
+
92
+ if (answer === "Supervisé") {
93
+ max_iterations = 1;
94
+ console.log("Mode supervisé : 1 tâche sera exécutée. Relancez avec -r pour continuer.");
95
+ } else if (answer === "Autonome limité") {
96
+ max_iterations = Math.min(max_iterations, 10);
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### Feedback Speed Warning
102
+
103
+ ⚠️ **IMPORTANT :** Si vos tests prennent >30 secondes, ralph-loop peut devenir inefficace.
104
+
105
+ ```
106
+ Temps test recommandés :
107
+ - Unitaires : <5s
108
+ - Intégration : <15s
109
+ - E2E : <30s (à exécuter en dehors du loop)
110
+ ```
111
+
112
+ Si tests lents détectés (via logs), avertir l'utilisateur et suggérer de désactiver tests E2E pendant le loop.
113
+
114
+ ## 4. Resume Mode
52
115
 
53
116
  If `{resume_mode} = true`:
54
117
 
@@ -40,12 +40,20 @@ if [ -d "$TEST_PROJECT" ]; then
40
40
  dotnet test "$TEST_PROJECT" --no-build --verbosity normal
41
41
  if [ $? -ne 0 ]; then
42
42
  echo "TEST REGRESSION — creating fix task"
43
- # Inject fix task into prd.json:
44
43
  prd.tasks.push({ id: maxId+1, description: "Fix test regression — all tests must pass",
45
44
  status: "pending", category: "validation", dependencies: [],
46
45
  acceptance_criteria: "dotnet test exits 0, all tests pass" });
47
46
  writeJSON('.ralph/prd.json', prd);
48
47
  fi
48
+ else
49
+ # CRITICAL: Test project MUST exist if test tasks are marked completed
50
+ const testTasksDone = prd.tasks.filter(t => t.category === 'test' && t.status === 'completed');
51
+ if (testTasksDone.length > 0) {
52
+ echo "ARTIFACT MISSING — test tasks marked completed but no test project exists"
53
+ // Reset test tasks to pending and inject guardrail
54
+ testTasksDone.forEach(t => { t.status = 'pending'; t.error = null; t.completed_at = null; });
55
+ writeJSON('.ralph/prd.json', prd);
56
+ fi
49
57
  fi
50
58
  ```
51
59
 
@@ -99,11 +107,28 @@ if (fileExists(queuePath)) {
99
107
  const queue = readJSON(queuePath);
100
108
  const currentModule = queue.modules[queue.currentIndex];
101
109
 
102
- // MODULE COMPLETENESS CHECK: verify all expected layers
110
+ // MODULE COMPLETENESS CHECK: verify all expected layers AND their artifacts
103
111
  const completedCats = new Set(prd.tasks.filter(t => t.status === 'completed').map(t => t.category));
104
112
  const expected = ['domain', 'infrastructure', 'application', 'api', 'frontend', 'test'];
105
113
  const missing = expected.filter(c => !completedCats.has(c));
106
114
 
115
+ // ARTIFACT EXISTENCE CHECK: categories with completed tasks must have real files
116
+ const artifactChecks = {
117
+ 'infrastructure': () => fs.existsSync('src/*/Persistence/Migrations') && fs.readdirSync('src/*/Persistence/Migrations').length > 0,
118
+ 'test': () => fs.existsSync(`tests/${projectName}.Tests.Unit`) && glob.sync('tests/**/*Tests.cs').length > 0,
119
+ 'api': () => glob.sync('src/*/Controllers/**/*Controller.cs').length > 0,
120
+ 'frontend': () => glob.sync('**/src/pages/**/*.tsx').length > 0
121
+ };
122
+ for (const [cat, check] of Object.entries(artifactChecks)) {
123
+ if (completedCats.has(cat) && !check()) {
124
+ // Tasks marked completed but artifacts missing — reset to pending
125
+ prd.tasks.filter(t => t.category === cat && t.status === 'completed')
126
+ .forEach(t => { t.status = 'pending'; t.error = 'Artifacts missing — re-execute'; t.completed_at = null; });
127
+ missing.push(cat);
128
+ console.log(`ARTIFACT RESET: ${cat} tasks reset to pending (no files found)`);
129
+ }
130
+ }
131
+
107
132
  if (missing.length > 0) {
108
133
  // Inject guardrail tasks for missing layers
109
134
  let maxId = Math.max(...prd.tasks.map(t => t.id));