@atlashub/smartstack-cli 4.37.0 → 4.39.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlashub/smartstack-cli",
3
- "version": "4.37.0",
3
+ "version": "4.39.0",
4
4
  "description": "SmartStack Claude Code automation toolkit - GitFlow, EF Core migrations, prompts and more",
5
5
  "author": {
6
6
  "name": "SmartStack",
@@ -47,6 +47,7 @@ SmartStack uses two separate DbContexts with separate migration histories:
47
47
  | `extensions` | ExtensionsDbContext | `extensions` | `extensions.__EFMigrationsHistory` | Client-specific entities |
48
48
 
49
49
  **Detection Logic:**
50
+ 0. If `--context` flag provided → use specified DbContext (skip auto-detection)
50
51
  1. If project contains `SmartStack.Domain` → use `CoreDbContext`
51
52
  2. If project contains `{ClientName}.Domain` with SmartStack NuGet → use `ExtensionsDbContext`
52
53
  3. If unclear → ask user which context to use
@@ -80,6 +81,8 @@ MCP returns the fully compliant name: `{context}_v{version}_{sequence}_{Descript
80
81
 
81
82
  ## Commands
82
83
 
84
+ > **IMPORTANT:** Project detection, migration search, and migration creation should run in a single Bash call to preserve shell variables (`$INFRA_PROJECT`, `$MIGRATIONS_DIR`, `$STARTUP_PROJECT`).
85
+
83
86
  **Prerequisite — ensure `dotnet ef` is on PATH (MANDATORY before any command):**
84
87
 
85
88
  ```bash
@@ -96,66 +99,35 @@ fi
96
99
  > NEVER rely on `$HOME` alone — in WSL it resolves to `/home/{user}` where .NET SDK is absent.
97
100
 
98
101
  ```bash
102
+ # Detect project paths
103
+ INFRA_PROJECT=$(find src -name "*Infrastructure.csproj" | head -1)
104
+ INFRA_DIR=$(dirname "$INFRA_PROJECT")
105
+ MIGRATIONS_DIR=$(find "$INFRA_DIR" -type d -name "Migrations" -path "*/Persistence/*" | head -1)
106
+ STARTUP_PROJECT=$(find src -name "*.Api.csproj" | head -1)
107
+
99
108
  # Existing migrations for Core
100
- find Migrations -name "core_*.cs" | grep -v Designer | grep -v Snapshot
109
+ find "$MIGRATIONS_DIR" -name "core_*.cs" | grep -v Designer | grep -v Snapshot
101
110
 
102
111
  # Existing migrations for Extensions
103
- find Persistence/Migrations -name "ext_*.cs" | grep -v Designer | grep -v Snapshot
112
+ find "$MIGRATIONS_DIR" -name "ext_*.cs" | grep -v Designer | grep -v Snapshot
104
113
 
105
- # Create Core migration (use MCP_NAME from suggest_migration)
106
- dotnet ef migrations add $MCP_NAME --context CoreDbContext -o Persistence/Migrations
114
+ # Create Core migration (use MCP_MIGRATION_NAME from suggest_migration)
115
+ dotnet ef migrations add "$MCP_MIGRATION_NAME" --context CoreDbContext -o Persistence/Migrations --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT"
107
116
 
108
- # Create Extensions migration (use MCP_NAME from suggest_migration)
109
- dotnet ef migrations add $MCP_NAME --context ExtensionsDbContext -o Persistence/Migrations
117
+ # Create Extensions migration (use MCP_MIGRATION_NAME from suggest_migration)
118
+ dotnet ef migrations add "$MCP_MIGRATION_NAME" --context ExtensionsDbContext -o Persistence/Migrations --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT"
110
119
 
111
120
  # Delete existing migration
112
- rm Persistence/Migrations/*${OLD_NAME}*.cs
121
+ rm -f "$MIGRATIONS_DIR"/*${OLD_NAME}*.cs
113
122
  ```
114
123
 
115
- > **Note:** `$MCP_NAME` is obtained via MCP call, never computed locally.
116
-
117
- ## SQL Objects Post-Injection (MANDATORY after every migration creation)
124
+ > **Note:** `$MCP_MIGRATION_NAME` is obtained via MCP call, never computed locally.
118
125
 
119
- **This step is NON-OPTIONAL.** After EVERY `dotnet ef migrations add`, you MUST check for SQL objects and inject them into the generated migration. Skipping this step causes runtime 500 errors on ALL endpoints (TVFs like `fn_GetUserGroupHierarchy` won't exist in the database).
126
+ ## SQL Objects (NO injection needed)
120
127
 
121
- ```bash
122
- # Detect the generated migration file (the main .cs, not .Designer.cs)
123
- MIGRATION_FILE=$(find "$MIGRATIONS_DIR" -name "*${MCP_NAME}.cs" ! -name "*.Designer.cs" | head -1)
124
- SQL_OBJECTS_DIR="$INFRA_PROJECT_DIR/Persistence/SqlObjects"
125
- SQL_FILES=$(find "$SQL_OBJECTS_DIR" -name "*.sql" 2>/dev/null | wc -l)
126
-
127
- if [ "$SQL_FILES" -gt 0 ]; then
128
- echo "Found $SQL_FILES SQL object(s) — injecting SqlObjectHelper.ApplyAll()..."
129
-
130
- # Add using directive if not present
131
- if ! grep -q "using SmartStack.Infrastructure.Persistence.SqlObjects;" "$MIGRATION_FILE"; then
132
- sed -i '1s/^/using SmartStack.Infrastructure.Persistence.SqlObjects;\n/' "$MIGRATION_FILE"
133
- fi
134
-
135
- # Add SqlObjectHelper.ApplyAll at the end of Up() method
136
- # Use Edit tool to insert before the closing brace of Up()
137
- # Target: the last " }" before "protected override void Down"
138
- sed -i '/protected override void Down/i\
139
- \ // Apply SQL objects (TVF, Views, SP) from embedded resources\
140
- \ SqlObjectHelper.ApplyAll(migrationBuilder);' "$MIGRATION_FILE"
141
-
142
- # VERIFY injection succeeded — FAIL if not
143
- if ! grep -q "SqlObjectHelper.ApplyAll" "$MIGRATION_FILE"; then
144
- echo "ERROR: Auto-injection failed. Use Edit tool to manually add:"
145
- echo " 1. Add 'using SmartStack.Infrastructure.Persistence.SqlObjects;' at top"
146
- echo " 2. Add 'SqlObjectHelper.ApplyAll(migrationBuilder);' at end of Up() method"
147
- echo " File: $MIGRATION_FILE"
148
- # DO NOT CONTINUE — fix this before proceeding
149
- else
150
- echo " SqlObjectHelper.ApplyAll(migrationBuilder) injected successfully"
151
- find "$SQL_OBJECTS_DIR" -name "*.sql" -exec basename {} \; | while read f; do echo " - $f"; done
152
- fi
153
- else
154
- echo "No SQL objects found in SqlObjects/ — skipping injection"
155
- fi
156
- ```
128
+ SQL objects (TVFs, views, stored procedures) are applied **automatically at application startup** via `SqlObjectHelper.ApplyAllAsync(dbContext)` in `SmartStackExtensions.InitializeSmartStackAsync()`, right after `MigrateAsync()`.
157
129
 
158
- > **CRITICAL:** If the bash `sed` injection fails (indentation mismatch), use the **Edit tool** to manually insert the lines. NEVER skip this step it causes cascading 500 errors at runtime.
130
+ **You do NOT need to add `SqlObjectHelper.ApplyAll(migrationBuilder)` in migrations.** The startup process handles this with `CREATE OR ALTER` (idempotent). Modifying a `.sql` file only requires an application restart no new migration needed.
159
131
 
160
132
  ## Context Detection
161
133
 
@@ -223,17 +195,6 @@ After rebase on develop:
223
195
  3. Report the error with dotnet output
224
196
  4. Suggest corrective action
225
197
 
226
- ## SQL in Migrations (STRICT RULE)
227
-
228
- **FORBIDDEN:** Raw SQL inline in migrations (e.g., `migrationBuilder.Sql("CREATE FUNCTION ...")`)
229
-
230
- **ONLY** SQL objects defined in `src/{Project}.Infrastructure/Persistence/SqlObjects/` are authorized.
231
-
232
- - All SQL objects (functions, views, procedures, TVFs) MUST be `.sql` files in `Persistence/SqlObjects/`
233
- - Migrations reference these via `SqlObjectHelper.ApplyAll(migrationBuilder)` in the `Up()` method
234
- - If `Persistence/SqlObjects/*.sql` files exist, inject the call automatically after migration creation
235
- - **Never** write raw SQL directly inside migration `Up()` or `Down()` methods
236
-
237
198
  ## Important Notes
238
199
 
239
200
  - **Core migrations** go to `CoreDbContextModelSnapshot.cs`
@@ -21,12 +21,9 @@ if [ -f .git ] && grep -q '^gitdir: [A-Za-z]:' .git 2>/dev/null && grep -qi micr
21
21
  fi
22
22
  ```
23
23
 
24
- ## SQL in Migrations (STRICT RULE)
24
+ ## SQL Objects
25
25
 
26
- **FORBIDDEN:** Raw SQL inline in migrations (e.g., `migrationBuilder.Sql("CREATE FUNCTION ...")`)
27
-
28
- **ONLY** SQL objects defined in `src/{Project}.Infrastructure/Persistence/SqlObjects/` are authorized.
29
- Migrations reference these via `SqlObjectHelper.ApplyAll(migrationBuilder)` — never inline SQL.
26
+ SQL objects (TVFs, views, SPs) are applied **at startup** via `SqlObjectHelper.ApplyAllAsync()` never inject in migrations.
30
27
 
31
28
  ## Workflow
32
29
 
@@ -36,75 +33,128 @@ Migrations reference these via `SqlObjectHelper.ApplyAll(migrationBuilder)` —
36
33
  4. **Regenerate** consolidated migration
37
34
  5. **Validate** build OK
38
35
 
36
+ ## Shared Functions
37
+
38
+ > **Reference:** See `references/shared-init-functions.md` for `ensure_dotnet_ef()`, `detect_efcore_project()`, `detect_dbcontext()`, `determine_base_branch()`.
39
+
39
40
  ## Key Commands
40
41
 
42
+ > **IMPORTANT:** Steps 1-2 MUST run in a single Bash call to preserve shell variables. Step 2b (confirm) is a separate interactive call. Step 3 requires variables from steps 1-2.
43
+
44
+ ### Step 1: Initialize
45
+
41
46
  ```bash
42
- # Detect project and DbContext
43
- detect_efcore_project # $DBCONTEXT, $DBCONTEXT_TYPE, $SCHEMA
44
- determine_base_branch # $BASE_BRANCH, $BRANCH_TYPE
47
+ # Determine parent branch
48
+ CURRENT_BRANCH=$(git branch --show-current)
49
+ case "$CURRENT_BRANCH" in
50
+ feature/*) BASE_BRANCH="develop"; BRANCH_TYPE="feature" ;;
51
+ release/*) BASE_BRANCH="main"; BRANCH_TYPE="release" ;;
52
+ hotfix/*) BASE_BRANCH="main"; BRANCH_TYPE="hotfix" ;;
53
+ develop) BASE_BRANCH="main"; BRANCH_TYPE="develop" ;;
54
+ main|master) echo "BLOCKED: Cannot rebase on main/master"; exit 1 ;;
55
+ *) BASE_BRANCH="develop"; BRANCH_TYPE="unknown" ;;
56
+ esac
57
+
58
+ # Detect project
59
+ INFRA_PROJECT=$(find src -name "*Infrastructure.csproj" | head -1)
60
+ INFRA_DIR=$(dirname "$INFRA_PROJECT")
61
+ MIGRATIONS_DIR=$(find "$INFRA_DIR" -type d -name "Migrations" -path "*/Persistence/*" | head -1)
62
+ MIGRATIONS_REL=$(realpath --relative-to="$(git rev-parse --show-toplevel)" "$MIGRATIONS_DIR")
63
+ STARTUP_PROJECT=$(find src -name "*.Api.csproj" | head -1)
64
+
65
+ # Detect DbContext (override via --context flag, else auto-detect)
66
+ CONTEXT_OVERRIDE="${CONTEXT:-}"
67
+ if [ "$CONTEXT_OVERRIDE" = "CoreDbContext" ] || [ "$CONTEXT_OVERRIDE" = "core" ]; then
68
+ DBCONTEXT="CoreDbContext"; DBCONTEXT_TYPE="core"
69
+ elif [ "$CONTEXT_OVERRIDE" = "ExtensionsDbContext" ] || [ "$CONTEXT_OVERRIDE" = "extensions" ]; then
70
+ DBCONTEXT="ExtensionsDbContext"; DBCONTEXT_TYPE="extensions"
71
+ elif find src -type d -name "SmartStack.Domain" | grep -q .; then
72
+ DBCONTEXT="CoreDbContext"; DBCONTEXT_TYPE="core"
73
+ else
74
+ DBCONTEXT="ExtensionsDbContext"; DBCONTEXT_TYPE="extensions"
75
+ fi
76
+
77
+ echo "Branch: $CURRENT_BRANCH → Base: $BASE_BRANCH"
78
+ echo "Migrations: $MIGRATIONS_DIR"
79
+ echo "DbContext: $DBCONTEXT"
80
+ ```
45
81
 
46
- # Backup
82
+ ### Step 2: Backup
83
+
84
+ ```bash
47
85
  BACKUP_DIR=".claude/gitflow/backup/migrations/rebase_$(date +%Y%m%d_%H%M%S)"
48
86
  mkdir -p "$BACKUP_DIR"
49
- cp Migrations/*.cs "$BACKUP_DIR/"
87
+ cp "$MIGRATIONS_DIR"/*.cs "$BACKUP_DIR/"
88
+ echo "Backup: $BACKUP_DIR"
89
+ ```
90
+
91
+ ### Step 2b: Confirm (skip if `--force`)
92
+
93
+ If `--force` flag is NOT set:
50
94
 
51
- # Reset snapshot to parent branch (develop or main depending on context)
95
+ ```javascript
96
+ AskUserQuestion({
97
+ question: "Rebase snapshot will:\n1. Reset ModelSnapshot to " + BASE_BRANCH + "\n2. Delete branch-specific migrations\n3. Regenerate consolidated migration\n\nBranch-specific migrations to recreate:\n" + BRANCH_ONLY_MIGS + "\n\nProceed?",
98
+ header: "EF Core Rebase-Snapshot",
99
+ options: [
100
+ { label: "Yes, rebase", description: "Backup + rebase snapshot" },
101
+ { label: "Cancel", description: "Do nothing" }
102
+ ]
103
+ })
104
+ ```
105
+
106
+ ### Step 3: Identify branch-specific migrations + Reset
107
+
108
+ ```bash
52
109
  git fetch origin "$BASE_BRANCH"
53
- git checkout "origin/$BASE_BRANCH" -- Migrations/*ModelSnapshot.cs
110
+
111
+ # List parent branch migrations (names only)
112
+ BASE_MIGS=$(git ls-tree -r --name-only "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL/" 2>/dev/null | grep "\.cs$" | grep -v "Designer\|Snapshot" | xargs -I{} basename {} .cs)
113
+
114
+ # List local migrations (names only)
115
+ LOCAL_MIGS=$(find "$MIGRATIONS_DIR" -maxdepth 1 -name "*.cs" ! -name "*Designer*" ! -name "*Snapshot*" | xargs -I{} basename {} .cs)
116
+
117
+ # Calculate branch-specific migrations (in local but NOT in base)
118
+ BRANCH_ONLY_MIGS=""
119
+ for mig in $LOCAL_MIGS; do
120
+ if ! echo "$BASE_MIGS" | grep -qx "$mig"; then
121
+ BRANCH_ONLY_MIGS="$BRANCH_ONLY_MIGS $mig"
122
+ fi
123
+ done
124
+ BRANCH_ONLY_MIGS=$(echo "$BRANCH_ONLY_MIGS" | xargs)
125
+
126
+ echo "Branch-specific migrations to recreate:"
127
+ for mig in $BRANCH_ONLY_MIGS; do echo " - $mig"; done
128
+
129
+ # Reset snapshot to parent branch
130
+ git checkout "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL"/*ModelSnapshot.cs
54
131
 
55
132
  # Retrieve parent branch migrations
56
- BASE_MIGS=$(git ls-tree -r --name-only "origin/$BASE_BRANCH" -- Migrations/ | grep "\.cs$" | grep -v "Designer\|Snapshot")
57
133
  for base_mig in $BASE_MIGS; do
58
- base_name=$(basename "${base_mig%.cs}")
59
- git checkout "origin/$BASE_BRANCH" -- "Migrations/${base_name}.cs" 2>/dev/null || true
60
- git checkout "origin/$BASE_BRANCH" -- "Migrations/${base_name}.Designer.cs" 2>/dev/null || true
134
+ git checkout "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL/${base_mig}.cs" 2>/dev/null || true
135
+ git checkout "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL/${base_mig}.Designer.cs" 2>/dev/null || true
136
+ done
137
+
138
+ # Delete branch-specific migrations by exact name (NOT by branch type pattern)
139
+ for mig in $BRANCH_ONLY_MIGS; do
140
+ rm -f "$MIGRATIONS_DIR"/*"${mig}"*.cs
141
+ echo " Deleted: $mig"
61
142
  done
143
+ ```
62
144
 
63
- # Delete branch migrations only
64
- rm -f Migrations/*${BRANCH_TYPE}_*.cs
65
- rm -f Migrations/*${BRANCH_TYPE}_*.Designer.cs
145
+ ### Step 4: Regenerate + Validate
66
146
 
147
+ ```bash
67
148
  # Regenerate with MCP naming - MANDATORY
68
149
  mcp__smartstack__suggest_migration({ description: "...", context: DBCONTEXT_TYPE })
69
- # Result: core_v1.7.0_001_UserAuthConsolidated
70
-
71
- dotnet ef migrations add "$MIGRATION_NAME" --context "$DBCONTEXT"
72
150
 
73
- # ═══════════════════════════════════════════════════════════════════════════
74
- # MANDATORY: SQL Objects Post-Injection
75
- # Without this, TVFs (fn_GetUserGroupHierarchy etc.) won't exist in DB
76
- # and ALL endpoints will return 500 at runtime
77
- # ═══════════════════════════════════════════════════════════════════════════
78
- MIGRATION_FILE=$(find Migrations -name "*${MIGRATION_NAME}.cs" ! -name "*.Designer.cs" | head -1)
79
- SQL_OBJECTS_DIR="$INFRA_PROJECT_DIR/Persistence/SqlObjects"
80
- SQL_FILES=$(find "$SQL_OBJECTS_DIR" -name "*.sql" 2>/dev/null | wc -l)
151
+ dotnet ef migrations add "$MCP_MIGRATION_NAME" \
152
+ --context "$DBCONTEXT" \
153
+ --project "$INFRA_PROJECT" \
154
+ --startup-project "$STARTUP_PROJECT" \
155
+ -o Persistence/Migrations
81
156
 
82
- if [ "$SQL_FILES" -gt 0 ]; then
83
- echo "Found $SQL_FILES SQL object(s) — injecting SqlObjectHelper.ApplyAll()..."
84
-
85
- # Add using directive if not present
86
- if ! grep -q "using SmartStack.Infrastructure.Persistence.SqlObjects;" "$MIGRATION_FILE"; then
87
- sed -i '1s/^/using SmartStack.Infrastructure.Persistence.SqlObjects;\n/' "$MIGRATION_FILE"
88
- fi
89
-
90
- # Inject SqlObjectHelper.ApplyAll before Down() method
91
- sed -i '/protected override void Down/i\
92
- \ // Apply SQL objects (TVF, Views, SP) from embedded resources\
93
- \ SqlObjectHelper.ApplyAll(migrationBuilder);' "$MIGRATION_FILE"
94
-
95
- # VERIFY — FAIL if injection didn't work
96
- if ! grep -q "SqlObjectHelper.ApplyAll" "$MIGRATION_FILE"; then
97
- echo "ERROR: Auto-injection failed. Use Edit tool to manually add:"
98
- echo " 1. 'using SmartStack.Infrastructure.Persistence.SqlObjects;' at top"
99
- echo " 2. 'SqlObjectHelper.ApplyAll(migrationBuilder);' at end of Up()"
100
- echo " File: $MIGRATION_FILE"
101
- else
102
- echo " SqlObjectHelper.ApplyAll(migrationBuilder) injected"
103
- find "$SQL_OBJECTS_DIR" -name "*.sql" -exec basename {} \; | while read f; do echo " - $f"; done
104
- fi
105
- else
106
- echo "No SQL objects found — skipping injection"
107
- fi
157
+ # NOTE: SQL objects are applied automatically at startup — no injection needed.
108
158
 
109
159
  # Validate
110
160
  dotnet build
@@ -130,7 +180,6 @@ core_v1.7.0_001_ReleaseInitial
130
180
  - [ ] Backup created
131
181
  - [ ] Build OK after rebase
132
182
  - [ ] SQL script can be generated
133
- - [ ] SQL objects injected (if `Persistence/SqlObjects/*.sql` exists)
134
183
 
135
184
  ## Error Recovery
136
185
 
@@ -140,7 +189,7 @@ core_v1.7.0_001_ReleaseInitial
140
189
  | MCP unavailable | Check `.claude/mcp-status.json`, run `/mcp healthcheck` |
141
190
  | Snapshot fetch fails | Verify `origin/$BASE_BRANCH` exists: `git fetch origin $BASE_BRANCH` |
142
191
  | Migration creation fails | Restore backup, check `dotnet build` output |
143
- | SQL objects injection fails | Manual step: add `SqlObjectHelper.ApplyAll(migrationBuilder)` in Up() |
192
+ | SQL objects missing at runtime | SQL objects are applied at startup — restart the application |
144
193
 
145
194
  **Automatic recovery:** On ANY failure after backup:
146
195
  1. Restore all files from backup directory
@@ -3,7 +3,7 @@ name: efcore-squash
3
3
  description: EF Core migration squasher - combine multiple migrations into one
4
4
  color: magenta
5
5
  model: sonnet
6
- tools: Bash, Glob, Read
6
+ tools: Bash, Glob, Read, Edit
7
7
  ---
8
8
 
9
9
  # EF Core Squash Agent
@@ -21,12 +21,9 @@ if [ -f .git ] && grep -q '^gitdir: [A-Za-z]:' .git 2>/dev/null && grep -qi micr
21
21
  fi
22
22
  ```
23
23
 
24
- ## SQL in Migrations (STRICT RULE)
24
+ ## SQL Objects
25
25
 
26
- **FORBIDDEN:** Raw SQL inline in migrations (e.g., `migrationBuilder.Sql("CREATE FUNCTION ...")`)
27
-
28
- **ONLY** SQL objects defined in `src/{Project}.Infrastructure/Persistence/SqlObjects/` are authorized.
29
- Migrations reference these via `SqlObjectHelper.ApplyAll(migrationBuilder)` — never inline SQL.
26
+ SQL objects (TVFs, views, SPs) are applied **at startup** via `SqlObjectHelper.ApplyAllAsync()` never inject in migrations.
30
27
 
31
28
  ## Core Principle
32
29
 
@@ -51,93 +48,160 @@ WARNING: NEVER retrieve only the snapshot without the migrations!
51
48
  5. **Fetch**: Retrieve **ModelSnapshot AND migrations** from parent branch (CRUCIAL!)
52
49
  6. **Delete**: Remove branch-specific migrations ONLY
53
50
  7. **Create**: Create consolidated migration (MCP mandatory)
54
- 8. **Inject SQL Objects**: If `Persistence/SqlObjects/` contains `.sql` files, inject `SqlObjectHelper.ApplyAll(migrationBuilder)` in `Up()`
55
- 9. **Validate**: Build + script OK
51
+ 8. **Validate**: Build + script OK
52
+
53
+ ## Shared Functions
54
+
55
+ > **Reference:** See `references/shared-init-functions.md` for `ensure_dotnet_ef()`, `detect_efcore_project()`, `detect_dbcontext()`, `determine_base_branch()`, `block_production()`.
56
56
 
57
57
  ## Key Commands
58
58
 
59
+ > **IMPORTANT:** Steps 1-3 MUST run in a single Bash call to preserve shell variables ($BASE_BRANCH, $MIGRATIONS_DIR, $BRANCH_ONLY_MIGS, etc.). Step 4 (guard) and Step 5 (confirm) are separate calls. Step 6 requires variables from steps 1-3.
60
+
61
+ ### Step 1: Initialize
62
+
63
+ ```bash
64
+ # Determine parent branch
65
+ CURRENT_BRANCH=$(git branch --show-current)
66
+ case "$CURRENT_BRANCH" in
67
+ feature/*) BASE_BRANCH="develop"; BRANCH_TYPE="feature" ;;
68
+ release/*) BASE_BRANCH="main"; BRANCH_TYPE="release" ;;
69
+ hotfix/*) BASE_BRANCH="main"; BRANCH_TYPE="hotfix" ;;
70
+ develop) BASE_BRANCH="main"; BRANCH_TYPE="develop" ;;
71
+ main|master) echo "BLOCKED: Cannot squash on main/master"; exit 1 ;;
72
+ *) BASE_BRANCH="develop"; BRANCH_TYPE="unknown" ;;
73
+ esac
74
+ echo "Branch: $CURRENT_BRANCH ($BRANCH_TYPE) → Base: $BASE_BRANCH"
75
+
76
+ # Context override from --context flag (passed via agent prompt)
77
+ CONTEXT_OVERRIDE="${CONTEXT:-}"
78
+ ```
79
+
80
+ ### Step 2: Detect project and migrations directory
81
+
82
+ ```bash
83
+ # Find Infrastructure project and migrations directory
84
+ INFRA_PROJECT=$(find src -name "*Infrastructure.csproj" | head -1)
85
+ INFRA_DIR=$(dirname "$INFRA_PROJECT")
86
+ MIGRATIONS_DIR=$(find "$INFRA_DIR" -type d -name "Migrations" -path "*/Persistence/*" | head -1)
87
+
88
+ # Detect DbContext (override via --context flag, else auto-detect)
89
+ if [ "$CONTEXT_OVERRIDE" = "CoreDbContext" ] || [ "$CONTEXT_OVERRIDE" = "core" ]; then
90
+ DBCONTEXT="CoreDbContext"; DBCONTEXT_TYPE="core"
91
+ elif [ "$CONTEXT_OVERRIDE" = "ExtensionsDbContext" ] || [ "$CONTEXT_OVERRIDE" = "extensions" ]; then
92
+ DBCONTEXT="ExtensionsDbContext"; DBCONTEXT_TYPE="extensions"
93
+ elif find src -type d -name "SmartStack.Domain" | grep -q .; then
94
+ DBCONTEXT="CoreDbContext"; DBCONTEXT_TYPE="core"
95
+ else
96
+ DBCONTEXT="ExtensionsDbContext"; DBCONTEXT_TYPE="extensions"
97
+ fi
98
+
99
+ echo "Migrations: $MIGRATIONS_DIR"
100
+ echo "DbContext: $DBCONTEXT"
101
+ ```
102
+
103
+ ### Step 3: Identify branch-specific migrations
104
+
105
+ ```bash
106
+ git fetch origin "$BASE_BRANCH"
107
+
108
+ # Get migration file paths RELATIVE TO GIT ROOT
109
+ MIGRATIONS_REL=$(realpath --relative-to="$(git rev-parse --show-toplevel)" "$MIGRATIONS_DIR")
110
+
111
+ # List parent branch migrations (names only, no Designer/Snapshot)
112
+ BASE_MIGS=$(git ls-tree -r --name-only "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL/" 2>/dev/null | grep "\.cs$" | grep -v "Designer\|Snapshot" | xargs -I{} basename {} .cs)
113
+
114
+ # List local migrations (names only)
115
+ LOCAL_MIGS=$(find "$MIGRATIONS_DIR" -maxdepth 1 -name "*.cs" ! -name "*Designer*" ! -name "*Snapshot*" | xargs -I{} basename {} .cs)
116
+
117
+ # Calculate branch-specific migrations (in local but NOT in base)
118
+ BRANCH_ONLY_MIGS=""
119
+ for mig in $LOCAL_MIGS; do
120
+ if ! echo "$BASE_MIGS" | grep -qx "$mig"; then
121
+ BRANCH_ONLY_MIGS="$BRANCH_ONLY_MIGS $mig"
122
+ fi
123
+ done
124
+ BRANCH_ONLY_MIGS=$(echo "$BRANCH_ONLY_MIGS" | xargs) # trim
125
+
126
+ BRANCH_COUNT=$(echo "$BRANCH_ONLY_MIGS" | wc -w)
127
+ BASE_COUNT=$(echo "$BASE_MIGS" | wc -w)
128
+
129
+ echo ""
130
+ echo "Parent ($BASE_BRANCH): $BASE_COUNT migration(s)"
131
+ echo "Branch-specific: $BRANCH_COUNT migration(s)"
132
+ for mig in $BRANCH_ONLY_MIGS; do echo " - $mig"; done
133
+ ```
134
+
135
+ ### Step 4: Guard — nothing to squash
136
+
59
137
  ```bash
60
- # Determine parent branch and DbContext
61
- determine_base_branch # BASE_BRANCH, BRANCH_TYPE
62
- detect_efcore_project # → $DBCONTEXT, $DBCONTEXT_TYPE, $SCHEMA
138
+ if [ "$BRANCH_COUNT" -eq 0 ]; then
139
+ echo "Nothing to squash — no branch-specific migrations found."
140
+ exit 0
141
+ fi
142
+ if [ "$BRANCH_COUNT" -eq 1 ]; then
143
+ echo "Only 1 branch-specific migration — squash not needed."
144
+ exit 0
145
+ fi
146
+ ```
147
+
148
+ ### Step 5: Confirm (skip if `--force`)
149
+
150
+ If `--force` flag is NOT set, display the plan and ask confirmation:
151
+
152
+ ```javascript
153
+ AskUserQuestion({
154
+ question: "Squash will consolidate " + BRANCH_COUNT + " migrations into 1.\n\nMigrations to squash:\n" + BRANCH_ONLY_MIGS_LIST + "\n\nParent: " + BASE_BRANCH + " (" + BASE_COUNT + " migrations will be preserved)\n\nProceed?",
155
+ header: "EF Core Squash",
156
+ options: [
157
+ { label: "Yes, squash", description: "Backup + consolidate" },
158
+ { label: "Cancel", description: "Do nothing" }
159
+ ]
160
+ })
161
+ ```
63
162
 
64
- # Identify branch-specific migrations
65
- BASE_MIGS=$(git ls-tree -r --name-only "origin/$BASE_BRANCH" -- Migrations/ | grep "\.cs$" | grep -v "Designer\|Snapshot")
66
- LOCAL_MIGS=$(find Migrations -name "*.cs" | grep -v Designer | grep -v Snapshot)
163
+ ### Step 6: Backup + Fetch + Delete + Create
67
164
 
165
+ ```bash
68
166
  # Backup
69
167
  BACKUP_DIR=".claude/gitflow/backup/migrations/squash_$(date +%Y%m%d_%H%M%S)"
70
- mkdir -p "$BACKUP_DIR" && cp Migrations/*.cs "$BACKUP_DIR/"
168
+ mkdir -p "$BACKUP_DIR" && cp "$MIGRATIONS_DIR"/*.cs "$BACKUP_DIR/"
169
+ echo "Backup: $BACKUP_DIR"
71
170
 
72
171
  # ═══════════════════════════════════════════════════════════════════════════
73
172
  # CRUCIAL: Retrieve SNAPSHOT + MIGRATIONS from parent branch
74
173
  # ═══════════════════════════════════════════════════════════════════════════
75
- git fetch origin "$BASE_BRANCH"
76
174
 
77
175
  # 1. Retrieve ModelSnapshot
78
- git checkout "origin/$BASE_BRANCH" -- Migrations/*ModelSnapshot.cs
176
+ git checkout "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL"/*ModelSnapshot.cs
79
177
  echo "Snapshot retrieved from origin/$BASE_BRANCH"
80
178
 
81
179
  # 2. Retrieve ALL migrations from parent branch
82
- # MANDATORY: Without this, develop/main migrations will be lost!
83
180
  for base_mig in $BASE_MIGS; do
84
- base_name=$(basename "${base_mig%.cs}")
85
- git checkout "origin/$BASE_BRANCH" -- "Migrations/${base_name}.cs" 2>/dev/null || true
86
- git checkout "origin/$BASE_BRANCH" -- "Migrations/${base_name}.Designer.cs" 2>/dev/null || true
87
- echo " $base_name retrieved"
181
+ git checkout "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL/${base_mig}.cs" 2>/dev/null || true
182
+ git checkout "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL/${base_mig}.Designer.cs" 2>/dev/null || true
88
183
  done
89
- echo "$(echo "$BASE_MIGS" | wc -l) migrations retrieved from origin/$BASE_BRANCH"
184
+ echo "$BASE_COUNT migration(s) retrieved from origin/$BASE_BRANCH"
90
185
 
91
- # Delete branch-specific migrations only
92
- for mig in $BRANCH_ONLY_MIGRATIONS; do
93
- rm -f "Migrations/${mig}"*
186
+ # 3. Delete branch-specific migrations only
187
+ for mig in $BRANCH_ONLY_MIGS; do
188
+ rm -f "$MIGRATIONS_DIR"/*"${mig}"*.cs
189
+ echo " Deleted: $mig"
94
190
  done
95
191
 
96
- # Create consolidated migration - MCP MANDATORY
97
- # $DBCONTEXT_TYPE = "core" or "extensions" (from detect_efcore_project)
192
+ # 4. Create consolidated migration - MCP MANDATORY
98
193
  mcp__smartstack__suggest_migration({ description: "...", context: DBCONTEXT_TYPE })
99
- # Result: core_v1.9.0_001_Description
100
-
101
- dotnet ef migrations add "$MIGRATION_NAME_FROM_MCP" --context "$DBCONTEXT"
102
-
103
- # ═══════════════════════════════════════════════════════════════════════════
104
- # MANDATORY: SQL Objects Post-Injection
105
- # Without this, TVFs (fn_GetUserGroupHierarchy etc.) won't exist in DB
106
- # and ALL endpoints will return 500 at runtime
107
- # ═══════════════════════════════════════════════════════════════════════════
108
- MIGRATION_FILE=$(find Migrations -name "*${MIGRATION_NAME_FROM_MCP}.cs" ! -name "*.Designer.cs" | head -1)
109
- SQL_OBJECTS_DIR="$INFRA_PROJECT_DIR/Persistence/SqlObjects"
110
- SQL_FILES=$(find "$SQL_OBJECTS_DIR" -name "*.sql" 2>/dev/null | wc -l)
194
+ # Use the name returned by MCP in $MCP_MIGRATION_NAME:
195
+ dotnet ef migrations add "$MCP_MIGRATION_NAME" \
196
+ --context "$DBCONTEXT" \
197
+ --project "$INFRA_PROJECT" \
198
+ --startup-project "$(find src -name "*.Api.csproj" | head -1)" \
199
+ -o Persistence/Migrations
111
200
 
112
- if [ "$SQL_FILES" -gt 0 ]; then
113
- echo "Found $SQL_FILES SQL object(s) — injecting SqlObjectHelper.ApplyAll()..."
114
-
115
- # Add using directive if not present
116
- if ! grep -q "using SmartStack.Infrastructure.Persistence.SqlObjects;" "$MIGRATION_FILE"; then
117
- sed -i '1s/^/using SmartStack.Infrastructure.Persistence.SqlObjects;\n/' "$MIGRATION_FILE"
118
- fi
119
-
120
- # Inject SqlObjectHelper.ApplyAll before Down() method
121
- sed -i '/protected override void Down/i\
122
- \ // Apply SQL objects (TVF, Views, SP) from embedded resources\
123
- \ SqlObjectHelper.ApplyAll(migrationBuilder);' "$MIGRATION_FILE"
124
-
125
- # VERIFY — FAIL if injection didn't work
126
- if ! grep -q "SqlObjectHelper.ApplyAll" "$MIGRATION_FILE"; then
127
- echo "ERROR: Auto-injection failed. Use Edit tool to manually add:"
128
- echo " 1. 'using SmartStack.Infrastructure.Persistence.SqlObjects;' at top"
129
- echo " 2. 'SqlObjectHelper.ApplyAll(migrationBuilder);' at end of Up()"
130
- echo " File: $MIGRATION_FILE"
131
- else
132
- echo " SqlObjectHelper.ApplyAll(migrationBuilder) injected"
133
- find "$SQL_OBJECTS_DIR" -name "*.sql" -exec basename {} \; | while read f; do echo " - $f"; done
134
- fi
135
- else
136
- echo "No SQL objects found — skipping injection"
137
- fi
201
+ # NOTE: SQL objects are applied automatically at startup — no injection needed.
138
202
 
139
- # Validate
140
- dotnet build && dotnet ef migrations script --idempotent > /dev/null
203
+ # 5. Validate
204
+ dotnet build && dotnet ef migrations script --idempotent --context "$DBCONTEXT" > /dev/null
141
205
  ```
142
206
 
143
207
  ## Safety Checks
@@ -149,7 +213,6 @@ dotnet build && dotnet ef migrations script --idempotent > /dev/null
149
213
  - [ ] **Parent snapshot retrieved** (origin/$BASE_BRANCH)
150
214
  - [ ] **Parent migrations retrieved** (.cs AND .Designer.cs files)
151
215
  - [ ] Branch-specific migrations deleted (not inherited ones!)
152
- - [ ] **SQL Objects injected** (if `Persistence/SqlObjects/*.sql` exists)
153
216
  - [ ] Build OK after squash
154
217
  - [ ] Script can be generated
155
218
 
@@ -165,7 +228,7 @@ SQUASH - {current_branch}
165
228
 
166
229
  Consolidated:
167
230
  Before: {M} migrations (branch-specific)
168
- After: 1 migration ({MIGRATION_NAME} via MCP)
231
+ After: 1 migration ({MCP_MIGRATION_NAME} via MCP)
169
232
 
170
233
  Backup: .claude/gitflow/backup/migrations/squash_{timestamp}/
171
234
 
@@ -189,7 +252,7 @@ Next: /efcore db-reset, /efcore db-deploy
189
252
  | MCP unavailable | Check `.claude/mcp-status.json`, run `/mcp healthcheck` |
190
253
  | Snapshot fetch fails | Verify `origin/$BASE_BRANCH` exists: `git fetch origin $BASE_BRANCH` |
191
254
  | Script generation fails | Restore backup, then investigate migration content |
192
- | SQL objects injection fails | Manual step: add `SqlObjectHelper.ApplyAll(migrationBuilder)` in Up() |
255
+ | SQL objects missing at runtime | SQL objects are applied at startup — restart the application |
193
256
 
194
257
  **Automatic recovery:** On ANY failure after backup:
195
258
  1. Restore all files from backup directory
@@ -43,9 +43,9 @@ fi
43
43
  | Categorie | Condition | Action |
44
44
  |-----------|-----------|--------|
45
45
  | Permanent | main, develop | Protege - jamais supprime |
46
- | Actif | Branche existe + recent | Conserver |
47
- | Orphelin | Branche supprimee | Supprimer |
48
- | Stale | Inactif > 30 jours | Proposer suppression |
46
+ | Actif | Branche existe + non mergee + recent | Conserver |
47
+ | Orphelin | Repertoire manquant ou branche supprimee | Supprimer (worktree + local + remote + parent dir) |
48
+ | Stale | Remote supprime OU branche mergee dans develop/main | Supprimer (worktree + local + remote + parent dir) |
49
49
  | Dirty | Modifications non commitees | Warning - confirmation requise |
50
50
 
51
51
  ## Commandes
@@ -64,9 +64,20 @@ git branch --list $BRANCH
64
64
  # Derniere activite
65
65
  git log -1 --format=%ci $BRANCH
66
66
 
67
- # Supprimer worktree
68
- git worktree remove "$WORKTREE_PATH" --force
69
- git worktree prune
67
+ # Supprimer worktree (3 etapes obligatoires)
68
+ git worktree remove "$WORKTREE_PATH" --force # 1. Remove worktree
69
+ git push origin --delete "$BRANCH" 2>/dev/null # 2. Delete remote branch
70
+ git branch -D "$BRANCH" 2>/dev/null # 3. Delete local branch
71
+ git worktree prune # 4. Prune stale refs
72
+ ```
73
+
74
+ ## Post-cleanup: empty parent directories
75
+
76
+ ```bash
77
+ # Remove empty parent directories (features/, releases/, hotfixes/)
78
+ for dir in "$FEATURES_DIR" "$RELEASES_DIR" "$HOTFIXES_DIR"; do
79
+ [ -d "$dir" ] && [ -z "$(ls -A "$dir" 2>/dev/null)" ] && rm -rf "$dir"
80
+ done
70
81
  ```
71
82
 
72
83
  ## Output Format