@atlashub/smartstack-cli 3.14.0 → 3.16.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 (33) hide show
  1. package/dist/index.js +26 -28
  2. package/dist/index.js.map +1 -1
  3. package/dist/mcp-entry.mjs +626 -141
  4. package/dist/mcp-entry.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/agents/efcore/migration.md +15 -0
  7. package/templates/skills/apex/steps/step-04-validate.md +64 -5
  8. package/templates/skills/application/references/frontend-verification.md +20 -0
  9. package/templates/skills/application/steps/step-04-backend.md +17 -1
  10. package/templates/skills/application/steps/step-05-frontend.md +49 -23
  11. package/templates/skills/application/templates-seed.md +14 -4
  12. package/templates/skills/business-analyse/html/ba-interactive.html +165 -0
  13. package/templates/skills/business-analyse/html/src/scripts/01-data-init.js +2 -0
  14. package/templates/skills/business-analyse/html/src/scripts/06-render-consolidation.js +85 -0
  15. package/templates/skills/business-analyse/html/src/styles/05-modules.css +65 -0
  16. package/templates/skills/business-analyse/html/src/template.html +13 -0
  17. package/templates/skills/business-analyse/schemas/application-schema.json +5 -0
  18. package/templates/skills/business-analyse/schemas/sections/metadata-schema.json +1 -0
  19. package/templates/skills/business-analyse/steps/step-01-cadrage.md +90 -0
  20. package/templates/skills/business-analyse/steps/step-02-decomposition.md +4 -0
  21. package/templates/skills/business-analyse/steps/step-03a1-setup.md +39 -0
  22. package/templates/skills/efcore/steps/shared/step-00-init.md +55 -0
  23. package/templates/skills/ralph-loop/SKILL.md +1 -0
  24. package/templates/skills/ralph-loop/references/category-rules.md +131 -27
  25. package/templates/skills/ralph-loop/references/compact-loop.md +61 -3
  26. package/templates/skills/ralph-loop/references/core-seed-data.md +251 -5
  27. package/templates/skills/ralph-loop/references/error-classification.md +143 -0
  28. package/templates/skills/ralph-loop/steps/step-05-report.md +54 -0
  29. package/templates/skills/review-code/references/smartstack-conventions.md +16 -0
  30. package/templates/skills/validate-feature/SKILL.md +11 -1
  31. package/templates/skills/validate-feature/steps/step-00-dependencies.md +121 -0
  32. package/templates/skills/validate-feature/steps/step-04-api-smoke.md +61 -13
  33. package/templates/skills/validate-feature/steps/step-05-db-validation.md +250 -0
@@ -2,10 +2,95 @@
2
2
  CONSOLIDATION
3
3
  ============================================ */
4
4
  function renderConsolidation() {
5
+ renderDataModel();
5
6
  renderConsolInteractions();
6
7
  renderConsolPermissions();
7
8
  }
8
9
 
10
+ /* ============================================
11
+ DATA MODEL (cross-module entity overview)
12
+ ============================================ */
13
+ function renderDataModel() {
14
+ const container = document.getElementById('dataModelContainer');
15
+ if (!container) return;
16
+
17
+ // Collect all entities from all modules
18
+ const allEntities = [];
19
+ data.modules.forEach(m => {
20
+ const spec = data.moduleSpecs[m.code] || {};
21
+ (spec.entities || []).forEach(ent => {
22
+ allEntities.push({ ...ent, moduleCode: m.code, moduleName: m.name || m.code });
23
+ });
24
+ });
25
+
26
+ if (allEntities.length === 0) {
27
+ container.innerHTML = '<p style="color:var(--text-muted);text-align:center;padding:2rem;">Aucune entite definie. Specifiez les donnees dans chaque domaine (Phase 3 > onglet Donnees).</p>';
28
+ return;
29
+ }
30
+
31
+ // Build relationship index for cross-module links
32
+ const entityModuleMap = {};
33
+ allEntities.forEach(e => { entityModuleMap[e.name] = e.moduleName; });
34
+
35
+ // Render grouped by module
36
+ let html = '';
37
+
38
+ // Summary bar
39
+ const moduleCount = data.modules.filter(m => (data.moduleSpecs[m.code]?.entities || []).length > 0).length;
40
+ const relCount = allEntities.reduce((sum, e) => sum + (e.relationships || []).length, 0);
41
+ html += `
42
+ <div class="dm-summary">
43
+ <div class="dm-summary-item"><span class="dm-summary-value">${allEntities.length}</span><span class="dm-summary-label">Entites</span></div>
44
+ <div class="dm-summary-item"><span class="dm-summary-value">${moduleCount}</span><span class="dm-summary-label">Domaines</span></div>
45
+ <div class="dm-summary-item"><span class="dm-summary-value">${relCount}</span><span class="dm-summary-label">Relations</span></div>
46
+ </div>`;
47
+
48
+ // Entity cards grouped by module
49
+ data.modules.forEach(m => {
50
+ const spec = data.moduleSpecs[m.code] || {};
51
+ const entities = spec.entities || [];
52
+ if (entities.length === 0) return;
53
+
54
+ html += `<div class="dm-module-group">`;
55
+ html += `<div class="dm-module-header">
56
+ <span class="dm-module-name">${m.name || m.code}</span>
57
+ <span class="dm-module-count">${entities.length} entite${entities.length > 1 ? 's' : ''}</span>
58
+ </div>`;
59
+
60
+ html += `<div class="dm-entity-grid">`;
61
+ entities.forEach(ent => {
62
+ const attrs = ent.attributes || [];
63
+ const rels = ent.relationships || [];
64
+ html += `
65
+ <div class="dm-entity-card">
66
+ <div class="dm-entity-header">
67
+ <span class="dm-entity-name">${ent.name}</span>
68
+ <span class="dm-entity-attr-count">${attrs.length} champ${attrs.length > 1 ? 's' : ''}</span>
69
+ </div>
70
+ ${ent.description ? `<div class="dm-entity-desc">${ent.description}</div>` : ''}
71
+ ${attrs.length > 0 ? `
72
+ <table class="dm-attr-table">
73
+ <thead><tr><th>Champ</th><th>Description</th></tr></thead>
74
+ <tbody>
75
+ ${attrs.map(a => `<tr><td class="dm-attr-name">${a.name}</td><td class="dm-attr-desc">${a.description || ''}</td></tr>`).join('')}
76
+ </tbody>
77
+ </table>` : ''}
78
+ ${rels.length > 0 ? `
79
+ <div class="dm-relations">
80
+ <div class="dm-relations-title">Relations</div>
81
+ ${rels.map(r => {
82
+ const relText = typeof r === 'string' ? r : (r.target + ' (' + r.type + ') - ' + (r.description || ''));
83
+ return `<div class="dm-relation-item">${relText}</div>`;
84
+ }).join('')}
85
+ </div>` : ''}
86
+ </div>`;
87
+ });
88
+ html += `</div></div>`;
89
+ });
90
+
91
+ container.innerHTML = html;
92
+ }
93
+
9
94
  function renderConsolInteractions() {
10
95
  const container = document.getElementById('consolInteractions');
11
96
  if (!container || data.dependencies.length === 0) return;
@@ -323,3 +323,68 @@
323
323
  }
324
324
  .e2e-step-module { font-weight: 600; color: var(--primary-light); font-size: 0.65rem; }
325
325
  .e2e-step-action { color: var(--text-bright); }
326
+
327
+ /* ============================================
328
+ DATA MODEL (Consolidation)
329
+ ============================================ */
330
+ .dm-summary {
331
+ display: flex; gap: 1.5rem; margin-bottom: 1.5rem;
332
+ padding: 1rem 1.5rem; background: var(--bg-card);
333
+ border: 1px solid var(--border); border-radius: 10px;
334
+ }
335
+ .dm-summary-item { display: flex; align-items: baseline; gap: 0.4rem; }
336
+ .dm-summary-value { font-size: 1.4rem; font-weight: 700; color: var(--text-bright); }
337
+ .dm-summary-label { font-size: 0.8rem; color: var(--text-muted); }
338
+ .dm-module-group { margin-bottom: 1.5rem; }
339
+ .dm-module-header {
340
+ display: flex; align-items: center; justify-content: space-between;
341
+ padding: 0.5rem 0; margin-bottom: 0.75rem;
342
+ border-bottom: 2px solid var(--primary);
343
+ }
344
+ .dm-module-name { font-weight: 700; color: var(--primary-light); font-size: 1rem; }
345
+ .dm-module-count { font-size: 0.75rem; color: var(--text-muted); }
346
+ .dm-entity-grid {
347
+ display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); gap: 1rem;
348
+ }
349
+ .dm-entity-card {
350
+ background: var(--bg-card); border: 1px solid var(--border);
351
+ border-radius: 10px; overflow: hidden;
352
+ transition: border-color var(--transition-fast);
353
+ }
354
+ .dm-entity-card:hover { border-color: var(--border-light); }
355
+ .dm-entity-header {
356
+ display: flex; align-items: center; justify-content: space-between;
357
+ padding: 0.6rem 0.75rem; background: var(--bg-hover);
358
+ border-bottom: 1px solid var(--border);
359
+ }
360
+ .dm-entity-name { font-weight: 600; color: var(--text-bright); font-size: 0.95rem; }
361
+ .dm-entity-attr-count {
362
+ font-size: 0.65rem; color: var(--text-muted);
363
+ background: rgba(99,102,241,0.1); padding: 0.1rem 0.4rem; border-radius: 4px;
364
+ }
365
+ .dm-entity-desc {
366
+ padding: 0.5rem 0.75rem; font-size: 0.8rem; color: var(--text-muted);
367
+ border-bottom: 1px solid var(--border);
368
+ }
369
+ .dm-attr-table { width: 100%; border-collapse: collapse; }
370
+ .dm-attr-table th {
371
+ text-align: left; font-size: 0.65rem; text-transform: uppercase;
372
+ letter-spacing: 0.05em; color: var(--text-muted); padding: 0.4rem 0.75rem;
373
+ border-bottom: 1px solid var(--border); font-weight: 600;
374
+ }
375
+ .dm-attr-table td { padding: 0.35rem 0.75rem; font-size: 0.8rem; border-bottom: 1px solid rgba(51,65,85,0.2); }
376
+ .dm-attr-name { font-weight: 500; color: var(--text-bright); white-space: nowrap; }
377
+ .dm-attr-desc { color: var(--text-muted); }
378
+ .dm-relations {
379
+ padding: 0.5rem 0.75rem; border-top: 1px solid var(--border);
380
+ background: rgba(6,182,212,0.03);
381
+ }
382
+ .dm-relations-title {
383
+ font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.05em;
384
+ color: var(--accent); font-weight: 600; margin-bottom: 0.3rem;
385
+ }
386
+ .dm-relation-item {
387
+ font-size: 0.8rem; color: var(--text); padding: 0.15rem 0;
388
+ padding-left: 0.75rem; border-left: 2px solid var(--accent);
389
+ margin-bottom: 0.2rem;
390
+ }
@@ -96,6 +96,10 @@
96
96
  <!-- Phase 4 : Consolidation -->
97
97
  <div class="nav-group">
98
98
  <div class="nav-group-title">4. Consolidation</div>
99
+ <a class="nav-item" onclick="showSection('consol-datamodel')" data-section="consol-datamodel">
100
+ <span class="nav-icon">&#9679;</span> Modele de donnees
101
+ <span class="nav-badge" id="entityCount">0</span>
102
+ </a>
99
103
  <a class="nav-item" onclick="showSection('consol-interactions')" data-section="consol-interactions">
100
104
  <span class="nav-icon">&#9679;</span> Interactions
101
105
  </a>
@@ -500,6 +504,15 @@
500
504
  PHASE 4 : CONSOLIDATION
501
505
  ================================================================ -->
502
506
 
507
+ <!-- SECTION: Modele de donnees -->
508
+ <div class="section" id="consol-datamodel" style="display:none;">
509
+ <h2 class="section-title">Modele de donnees</h2>
510
+ <p class="section-subtitle">Vue d'ensemble de toutes les entites metier, leurs attributs et leurs relations entre domaines.</p>
511
+ <div id="dataModelContainer">
512
+ <p style="color:var(--text-muted);text-align:center;padding:2rem;">Le modele de donnees sera genere a partir des specifications de chaque domaine.</p>
513
+ </div>
514
+ </div>
515
+
503
516
  <!-- SECTION: Interactions cross-module -->
504
517
  <div class="section" id="consol-interactions" style="display:none;">
505
518
  <h2 class="section-title">Interactions entre domaines</h2>
@@ -48,6 +48,11 @@
48
48
  "const": "interactive",
49
49
  "description": "Analysis mode — always interactive (AI listens, reformulates, challenges, validates)"
50
50
  },
51
+ "tablePrefix": {
52
+ "type": "string",
53
+ "pattern": "^[a-z]{2,5}_$",
54
+ "description": "Application table prefix for database tables (e.g., rh_, fi_, crm_). Defined during cadrage, used by all entities in this application."
55
+ },
51
56
  "workflow": {
52
57
  "type": "object",
53
58
  "description": "Iterative module loop state",
@@ -25,6 +25,7 @@
25
25
  "previousVersion": { "type": ["string", "null"] },
26
26
  "changeReason": { "type": ["string", "null"] },
27
27
  "analysisMode": { "type": "string", "const": "interactive", "description": "Analysis mode — always interactive (AI listens, reformulates, challenges, validates)" },
28
+ "tablePrefix": { "type": "string", "pattern": "^[a-z]{2,5}_$", "description": "Application table prefix inherited from parent (e.g., rh_, fi_, crm_)" },
28
29
  "mcpAvailable": { "type": "boolean" },
29
30
  "scope": { "type": "string", "enum": ["application", "module"], "default": "module" },
30
31
  "applicationRef": { "type": ["string", "null"], "description": "Parent application feature ID (FEAT-XXX)" },
@@ -241,6 +241,49 @@ Use these techniques:
241
241
 
242
242
  Ask challenge questions via AskUserQuestion (1-2 batches of max 4).
243
243
 
244
+ #### 4d-bis. Entity Sourcing Pattern (MANDATORY for detected dependencies)
245
+
246
+ > **When the analysis detects a referenced entity/concept (e.g., "Projets" referenced by activities, "Clients" referenced by orders), ALWAYS ask how this entity is sourced.**
247
+ > This question detects potential new modules that were not mentioned in the original brief.
248
+
249
+ For EACH referenced entity/concept detected during phases 2-4 that is NOT already covered by an identified module:
250
+
251
+ Ask via AskUserQuestion:
252
+ ```
253
+ question: "{language == 'fr'
254
+ ? 'Les {entity_plural} auxquel(le)s les {parent_entity_plural} sont lié(e)s : comment sont-ils gérés ?'
255
+ : 'The {entity_plural} that {parent_entity_plural} are linked to: how are they managed?'}"
256
+ header: "{entity_name}"
257
+ options:
258
+ - label: "{language == 'fr' ? 'Gérés dans l\'app' : 'Managed in app'}"
259
+ description: "{language == 'fr'
260
+ ? 'Les {entity_plural} sont créés et gérés dans cette application (liste simple)'
261
+ : '{entity_plural} are created and managed in this application (simple list)'}"
262
+ - label: "{language == 'fr' ? 'Nouveau module dédié' : 'New dedicated module'}"
263
+ description: "{language == 'fr'
264
+ ? 'Les {entity_plural} méritent leur propre module avec CRUD complet, permissions et navigation'
265
+ : '{entity_plural} deserve their own module with full CRUD, permissions and navigation'}"
266
+ - label: "{language == 'fr' ? 'Système externe' : 'External system'}"
267
+ description: "{language == 'fr'
268
+ ? 'Les {entity_plural} viennent d\'un autre système (import ou API)'
269
+ : '{entity_plural} come from another system (import or API)'}"
270
+ - label: "{language == 'fr' ? 'Liste simple en config' : 'Simple config list'}"
271
+ description: "{language == 'fr'
272
+ ? 'Juste une liste configurable par l\'admin, pas un module complet'
273
+ : 'Just a configurable list managed by admin, not a full module'}"
274
+ ```
275
+
276
+ **Processing each answer:**
277
+
278
+ | Choice | Action |
279
+ |--------|--------|
280
+ | **Gérés dans l'app** | Entity stays as a child/section of the current module. Note in coverageMatrix. |
281
+ | **Nouveau module dédié** | **Add new module to `{pre_analysis}.detected_modules[]`**. Flag for inclusion in decomposition (step-02). Add to `coverageMatrix` as mustHave with anticipated sections (list, detail, create, edit). This module becomes a dependency of the referencing module. |
282
+ | **Système externe** | Flag for integration specification. Add to `coverageMatrix` as integration. Load `questionnaire/05-integrations.md` if not already loaded. |
283
+ | **Liste simple en config** | Entity becomes a lookup/reference table (no dedicated module). Note in coverageMatrix as config data. |
284
+
285
+ > **CRITICAL:** The "Nouveau module dédié" option is essential for detecting modules not mentioned in the original brief. Without it, complex entities get reduced to simple config lists when they deserve full CRUD management.
286
+
244
287
  #### 4e. Risks & Success Criteria (ALWAYS — from `questionnaire/14-risk-assumptions.md` and `questionnaire/15-success-metrics.md`)
245
288
 
246
289
  Select the most pertinent questions:
@@ -350,6 +393,45 @@ options:
350
393
  IF single-module mode:
351
394
  Same role definition but inferred from stakeholders
352
395
 
396
+ ### 6b. Table Prefix Definition (MANDATORY)
397
+
398
+ > **Every business application MUST define a table prefix.**
399
+ > This prefix identifies which application a database table belongs to (e.g., `rh_Employees`, `fi_Invoices`).
400
+
401
+ **Process:**
402
+
403
+ 1. Derive a suggested prefix from the application name:
404
+ - Use the most recognizable abbreviation (2-5 lowercase letters + `_`)
405
+ - Examples: HumanResources → `rh_`, SalesManagement → `sales_`, Finance → `fi_`, CRM → `crm_`
406
+
407
+ 2. **Display as markdown** (direct text output):
408
+
409
+ ```
410
+ {language == "fr"
411
+ ? "### Préfixe de table\n\nToutes les tables de cette application en base de données porteront un préfixe pour identifier leur appartenance.\n\nPréfixe suggéré : **`{suggested_prefix}`**\n\nExemple : `{suggested_prefix}Employees`, `{suggested_prefix}Contracts`"
412
+ : "### Table Prefix\n\nAll database tables for this application will carry a prefix to identify their ownership.\n\nSuggested prefix: **`{suggested_prefix}`**\n\nExample: `{suggested_prefix}Employees`, `{suggested_prefix}Contracts`"}
413
+ ```
414
+
415
+ 3. Ask via AskUserQuestion:
416
+ ```
417
+ question: "{language == 'fr' ? 'Ce préfixe de table vous convient-il ?' : 'Does this table prefix work for you?'}"
418
+ header: "Préfixe"
419
+ options:
420
+ - label: "{language == 'fr' ? 'Oui, parfait' : 'Yes, perfect'}"
421
+ description: "{language == 'fr' ? 'Utiliser le préfixe suggéré' : 'Use the suggested prefix'}"
422
+ - label: "{language == 'fr' ? 'Personnaliser' : 'Customize'}"
423
+ description: "{language == 'fr' ? 'Choisir un autre préfixe (2-5 lettres minuscules + _)' : 'Choose a different prefix (2-5 lowercase letters + _)'}"
424
+ ```
425
+
426
+ 4. IF "Customize" → ask for custom prefix via AskUserQuestion (open-ended "Other" option)
427
+
428
+ 5. **Validate the prefix:**
429
+ - Format: `^[a-z]{2,5}_$` (2-5 lowercase letters followed by underscore)
430
+ - Must NOT collide with platform prefixes: `auth_`, `nav_`, `usr_`, `ai_`, `cfg_`, `wkf_`, `support_`, `entra_`, `ref_`, `loc_`, `lic_`, `tenant_`
431
+ - If collision detected → ask the user to choose another prefix
432
+
433
+ 6. Store in `metadata.tablePrefix`
434
+
353
435
  ### 7. Coverage Matrix with Sections & Resources (MANDATORY)
354
436
 
355
437
  > **ENRICHMENT: The coverage matrix now includes anticipated sections (Level 4) and resources (Level 5).**
@@ -416,6 +498,14 @@ Use ba-writer to enrich master feature.json. **Follow the STRUCTURE CARDS exactl
416
498
  See [references/cadrage-structure-cards.md](../references/cadrage-structure-cards.md) for exact JSON formats of: `stakeholders[]`, `applicationRoles[]`, `risks[]`, `acceptanceCriteria[]`, `coverageMatrix[]`, `codebaseContext`.
417
499
 
418
500
  ```
501
+ ba-writer.enrichSection({
502
+ featureId: {feature_id},
503
+ section: "metadata",
504
+ data: {
505
+ tablePrefix: "{from Phase 5, section 6b — validated prefix, e.g., rh_}"
506
+ }
507
+ })
508
+
419
509
  ba-writer.enrichSection({
420
510
  featureId: {feature_id},
421
511
  section: "cadrage",
@@ -100,6 +100,10 @@ Display the identified modules as markdown:
100
100
  ```
101
101
  ## Modules identifiés pour {application_name}
102
102
 
103
+ {language == "fr"
104
+ ? "**Préfixe de table :** `{metadata.tablePrefix}` — toutes les tables de cette application seront préfixées (ex: `{metadata.tablePrefix}Customers`, `{metadata.tablePrefix}Orders`)"
105
+ : "**Table prefix:** `{metadata.tablePrefix}` — all tables in this application will be prefixed (e.g., `{metadata.tablePrefix}Customers`, `{metadata.tablePrefix}Orders`)"}
106
+
103
107
  | # | Module | Type | Priorité | Complexité | Sections pressenties | Entités |
104
108
  |---|--------|------|----------|------------|---------------------|---------|
105
109
  | 1 | Customers | data-centric | must | medium | list, detail, create, edit | Customer, Address, Contact |
@@ -349,6 +349,45 @@ options:
349
349
  description: "Pas de référence à {completedModule}"
350
350
  ```
351
351
 
352
+ #### 5-bis. Unresolved Entity Dependencies (New Module Detection)
353
+
354
+ > **When an entity reference is detected that does NOT belong to any completed or planned module, apply the Entity Sourcing Pattern from step-01-cadrage (section 4d-bis).**
355
+
356
+ For each entity referenced by {currentModule} that is NOT covered by any existing/planned module:
357
+
358
+ ```
359
+ question: "{language == 'fr'
360
+ ? 'Les {entity_plural} référencés par {currentModule} ne font partie d\'aucun module. Comment les gérer ?'
361
+ : '{entity_plural} referenced by {currentModule} are not part of any module. How should they be managed?'}"
362
+ header: "{entity_name}"
363
+ options:
364
+ - label: "{language == 'fr' ? 'Dans ce module' : 'In this module'}"
365
+ description: "{language == 'fr'
366
+ ? 'Gérer les {entity_plural} comme une section/entité de {currentModule}'
367
+ : 'Manage {entity_plural} as a section/entity of {currentModule}'}"
368
+ - label: "{language == 'fr' ? 'Nouveau module dédié' : 'New dedicated module'}"
369
+ description: "{language == 'fr'
370
+ ? 'Créer un module complet avec CRUD, permissions et navigation'
371
+ : 'Create a full module with CRUD, permissions and navigation'}"
372
+ - label: "{language == 'fr' ? 'Système externe' : 'External system'}"
373
+ description: "{language == 'fr'
374
+ ? 'Import ou API depuis un autre système'
375
+ : 'Import or API from another system'}"
376
+ - label: "{language == 'fr' ? 'Liste de référence' : 'Reference list'}"
377
+ description: "{language == 'fr'
378
+ ? 'Table de lookup configurable par l\'admin'
379
+ : 'Admin-configurable lookup table'}"
380
+ ```
381
+
382
+ **IF "Nouveau module dédié" is selected:**
383
+ 1. Add new module to master `modules[]` via ba-writer (status: "pending", sortOrder: after current module)
384
+ 2. Update dependency graph: current module depends on the new module
385
+ 3. **WARNING:** The new module will need specification AFTER the current one. Display:
386
+ ```
387
+ "⚠ Nouveau module '{entity_name}' ajouté. Il sera spécifié après {currentModule}."
388
+ ```
389
+ 4. Update `metadata.workflow.moduleOrder` to include the new module (inserted after its dependents)
390
+
352
391
  ---
353
392
 
354
393
  ## NEXT STEP
@@ -16,6 +16,61 @@ Detect EF Core project structure, identify DbContext (Core vs Extensions), and l
16
16
 
17
17
  ## EXECUTION SEQUENCE:
18
18
 
19
+ ### 0. Ensure dotnet ef Available (Platform-Aware)
20
+
21
+ > **CRITICAL:** On Windows (Git Bash, MSYS2), `dotnet ef` may not be on the shell PATH
22
+ > even though it's installed. This step fixes PATH before any EF Core command.
23
+
24
+ ```bash
25
+ ensure_dotnet_ef() {
26
+ # Quick check: already available?
27
+ if dotnet ef --version &>/dev/null; then
28
+ DOTNET_EF_VERSION=$(dotnet ef --version 2>/dev/null)
29
+ echo "dotnet-ef: $DOTNET_EF_VERSION"
30
+ return 0
31
+ fi
32
+
33
+ echo "dotnet-ef not on PATH, attempting platform-aware fix..."
34
+
35
+ # Try all known .NET global tools locations
36
+ # - $USERPROFILE/.dotnet/tools → Windows via Git Bash (USERPROFILE=C:\Users\xxx)
37
+ # - $HOME/.dotnet/tools → Linux/macOS or Git Bash fallback
38
+ # - $LOCALAPPDATA/Microsoft/dotnet/tools → Windows alternate location
39
+ for TOOLS_DIR in \
40
+ "$USERPROFILE/.dotnet/tools" \
41
+ "$HOME/.dotnet/tools" \
42
+ "$LOCALAPPDATA/Microsoft/dotnet/tools"; do
43
+ if [ -n "$TOOLS_DIR" ] && [ -d "$TOOLS_DIR" ]; then
44
+ export PATH="$TOOLS_DIR:$PATH"
45
+ echo " PATH += $TOOLS_DIR"
46
+ fi
47
+ done
48
+
49
+ # Verify after PATH fix
50
+ if dotnet ef --version &>/dev/null; then
51
+ DOTNET_EF_VERSION=$(dotnet ef --version 2>/dev/null)
52
+ echo "dotnet-ef: $DOTNET_EF_VERSION (found after PATH fix)"
53
+ return 0
54
+ fi
55
+
56
+ echo "ERROR: dotnet-ef not available"
57
+ echo "Install with: dotnet tool install --global dotnet-ef"
58
+ echo ""
59
+ echo "If installed but not found, check:"
60
+ echo " Windows: %USERPROFILE%\\.dotnet\\tools must be in PATH"
61
+ echo " Linux: ~/.dotnet/tools must be in PATH"
62
+ exit 1
63
+ }
64
+
65
+ ensure_dotnet_ef
66
+ ```
67
+
68
+ **IMPORTANT — WSL pitfall:**
69
+ If the shell resolves `$HOME` to `/home/{user}` (WSL path) instead of `/c/Users/{user}` (Git Bash),
70
+ the .NET SDK may not be found even if `dotnet-ef` is. Use `$USERPROFILE` first on Windows.
71
+
72
+ ---
73
+
19
74
  ### 1. Detect EF Core Project
20
75
 
21
76
  ```bash
@@ -115,6 +115,7 @@ LOAD → GENERATE → COMPILE → TEST → [FAIL?] → FIX → RE-TEST → [PASS
115
115
  | `references/category-rules.md` | Step-02 and compact loop (execution rules per category) |
116
116
  | `references/compact-loop.md` | Step-04 section 5 (inline execution after first iteration) |
117
117
  | `references/core-seed-data.md` | Infrastructure task with seed data keywords |
118
+ | `references/error-classification.md` | Build failure in compact loop or validation (error diagnosis) |
118
119
  | `references/team-orchestration.md` | Step-00 when multi-module detected (2+ PRDs) |
119
120
  </step_files>
120
121