@atlashub/smartstack-cli 4.38.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.38.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,23 +99,29 @@ 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.
124
+ > **Note:** `$MCP_MIGRATION_NAME` is obtained via MCP call, never computed locally.
116
125
 
117
126
  ## SQL Objects (NO injection needed)
118
127
 
@@ -186,16 +195,6 @@ After rebase on develop:
186
195
  3. Report the error with dotnet output
187
196
  4. Suggest corrective action
188
197
 
189
- ## SQL in Migrations (STRICT RULE)
190
-
191
- **FORBIDDEN:** Raw SQL inline in migrations (e.g., `migrationBuilder.Sql("CREATE FUNCTION ...")`)
192
-
193
- **ONLY** SQL objects defined in `src/{Project}.Infrastructure/Persistence/SqlObjects/` are authorized.
194
-
195
- - All SQL objects (functions, views, procedures, TVFs) MUST be `.sql` files in `Persistence/SqlObjects/`
196
- - SQL objects are applied **automatically at startup** via `SqlObjectHelper.ApplyAllAsync()` — no migration injection needed
197
- - **Never** write raw SQL directly inside migration `Up()` or `Down()` methods
198
-
199
198
  ## Important Notes
200
199
 
201
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
- SQL objects are applied **automatically at startup** via `SqlObjectHelper.ApplyAllAsync()` — no migration injection needed.
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,42 +33,128 @@ SQL objects are applied **automatically at startup** via `SqlObjectHelper.ApplyA
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
+ ```
81
+
82
+ ### Step 2: Backup
45
83
 
46
- # Backup
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
+ ```
50
90
 
51
- # Reset snapshot to parent branch (develop or main depending on context)
91
+ ### Step 2b: Confirm (skip if `--force`)
92
+
93
+ If `--force` flag is NOT set:
94
+
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
150
 
71
- dotnet ef migrations add "$MIGRATION_NAME" --context "$DBCONTEXT"
151
+ dotnet ef migrations add "$MCP_MIGRATION_NAME" \
152
+ --context "$DBCONTEXT" \
153
+ --project "$INFRA_PROJECT" \
154
+ --startup-project "$STARTUP_PROJECT" \
155
+ -o Persistence/Migrations
72
156
 
73
- # NOTE: SQL objects (TVFs, views, SPs) are applied automatically at startup
74
- # via SqlObjectHelper.ApplyAllAsync() — no injection needed in migrations.
157
+ # NOTE: SQL objects are applied automatically at startup — no injection needed.
75
158
 
76
159
  # Validate
77
160
  dotnet build
@@ -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
- SQL objects are applied **automatically at startup** via `SqlObjectHelper.ApplyAllAsync()` — no migration injection needed.
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
 
@@ -53,57 +50,158 @@ WARNING: NEVER retrieve only the snapshot without the migrations!
53
50
  7. **Create**: Create consolidated migration (MCP mandatory)
54
51
  8. **Validate**: Build + script OK
55
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
  ## Key Commands
57
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
+
58
63
  ```bash
59
- # Determine parent branch and DbContext
60
- determine_base_branch # BASE_BRANCH, BRANCH_TYPE
61
- detect_efcore_project # $DBCONTEXT, $DBCONTEXT_TYPE, $SCHEMA
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
+ ```
62
79
 
63
- # Identify branch-specific migrations
64
- BASE_MIGS=$(git ls-tree -r --name-only "origin/$BASE_BRANCH" -- Migrations/ | grep "\.cs$" | grep -v "Designer\|Snapshot")
65
- LOCAL_MIGS=$(find Migrations -name "*.cs" | grep -v Designer | grep -v Snapshot)
80
+ ### Step 2: Detect project and migrations directory
66
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
+
137
+ ```bash
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
+ ```
162
+
163
+ ### Step 6: Backup + Fetch + Delete + Create
164
+
165
+ ```bash
67
166
  # Backup
68
167
  BACKUP_DIR=".claude/gitflow/backup/migrations/squash_$(date +%Y%m%d_%H%M%S)"
69
- 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"
70
170
 
71
171
  # ═══════════════════════════════════════════════════════════════════════════
72
172
  # CRUCIAL: Retrieve SNAPSHOT + MIGRATIONS from parent branch
73
173
  # ═══════════════════════════════════════════════════════════════════════════
74
- git fetch origin "$BASE_BRANCH"
75
174
 
76
175
  # 1. Retrieve ModelSnapshot
77
- git checkout "origin/$BASE_BRANCH" -- Migrations/*ModelSnapshot.cs
176
+ git checkout "origin/$BASE_BRANCH" -- "$MIGRATIONS_REL"/*ModelSnapshot.cs
78
177
  echo "Snapshot retrieved from origin/$BASE_BRANCH"
79
178
 
80
179
  # 2. Retrieve ALL migrations from parent branch
81
- # MANDATORY: Without this, develop/main migrations will be lost!
82
180
  for base_mig in $BASE_MIGS; do
83
- base_name=$(basename "${base_mig%.cs}")
84
- git checkout "origin/$BASE_BRANCH" -- "Migrations/${base_name}.cs" 2>/dev/null || true
85
- git checkout "origin/$BASE_BRANCH" -- "Migrations/${base_name}.Designer.cs" 2>/dev/null || true
86
- 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
87
183
  done
88
- echo "$(echo "$BASE_MIGS" | wc -l) migrations retrieved from origin/$BASE_BRANCH"
184
+ echo "$BASE_COUNT migration(s) retrieved from origin/$BASE_BRANCH"
89
185
 
90
- # Delete branch-specific migrations only
91
- for mig in $BRANCH_ONLY_MIGRATIONS; do
92
- 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"
93
190
  done
94
191
 
95
- # Create consolidated migration - MCP MANDATORY
96
- # $DBCONTEXT_TYPE = "core" or "extensions" (from detect_efcore_project)
192
+ # 4. Create consolidated migration - MCP MANDATORY
97
193
  mcp__smartstack__suggest_migration({ description: "...", context: DBCONTEXT_TYPE })
98
- # Result: core_v1.9.0_001_Description
99
-
100
- dotnet ef migrations add "$MIGRATION_NAME_FROM_MCP" --context "$DBCONTEXT"
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
101
200
 
102
- # NOTE: SQL objects (TVFs, views, SPs) are applied automatically at startup
103
- # via SqlObjectHelper.ApplyAllAsync() — no injection needed in migrations.
201
+ # NOTE: SQL objects are applied automatically at startup — no injection needed.
104
202
 
105
- # Validate
106
- dotnet build && dotnet ef migrations script --idempotent > /dev/null
203
+ # 5. Validate
204
+ dotnet build && dotnet ef migrations script --idempotent --context "$DBCONTEXT" > /dev/null
107
205
  ```
108
206
 
109
207
  ## Safety Checks
@@ -130,7 +228,7 @@ SQUASH - {current_branch}
130
228
 
131
229
  Consolidated:
132
230
  Before: {M} migrations (branch-specific)
133
- After: 1 migration ({MIGRATION_NAME} via MCP)
231
+ After: 1 migration ({MCP_MIGRATION_NAME} via MCP)
134
232
 
135
233
  Backup: .claude/gitflow/backup/migrations/squash_{timestamp}/
136
234
 
@@ -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
@@ -3,7 +3,7 @@ name: dev-start
3
3
  description: Launch SmartStack dev environment (backend + frontend + admin credentials)
4
4
  argument-hint: "[--stop] [--reset] [--backend-only] [--frontend-only]"
5
5
  model: haiku
6
- allowed-tools: Read, Bash, Glob, Write
6
+ allowed-tools: Read, Bash, Glob, Write, TaskOutput
7
7
  ---
8
8
 
9
9
  ## Arguments
@@ -106,9 +106,11 @@ If `--stop` argument → skip this step entirely (--stop uses netstat only).
106
106
 
107
107
  **Skip if CACHE_HIT=true (values loaded from cache in Step 0).**
108
108
 
109
- Find the API and Web directories:
110
- - API: `src/*.Api` (first match)
111
- - Web: `web/*-web` (first match)
109
+ Find the API and Web directories using bash (Glob doesn't match directories):
110
+ ```bash
111
+ API_DIR=$(ls -d src/*.Api 2>/dev/null | head -1)
112
+ WEB_DIR=$(ls -d web/*-web 2>/dev/null | head -1)
113
+ ```
112
114
 
113
115
  If neither found, display error and exit.
114
116
 
@@ -129,6 +131,11 @@ Check if `appsettings.Local.json` exists in the API directory:
129
131
  - **Dev backend port**: Default `5142`
130
132
  - **Dev frontend port**: Default `6173`
131
133
 
134
+ **IMPORTANT: Read config files in parallel.** Use a single message with multiple Read tool calls for:
135
+ - `{API_DIR}/appsettings.json` (or `appsettings.Local.json`)
136
+ - `{WEB_DIR}/.env.development` (or `.env.standalone`)
137
+ - `{API_DIR}/Properties/launchSettings.json`
138
+
132
139
  ## Step 3: Validate config coherence
133
140
 
134
141
  **Skip if CACHE_HIT=true (values loaded from cache in Step 0).**
@@ -242,7 +249,7 @@ Config Coherence Check:
242
249
  3. Write `.claude/config/dev-run.json` using the Write tool with all detected values:
243
250
  ```json
244
251
  {
245
- "version": 1,
252
+ "version": 2,
246
253
  "api_dir": "{API_DIR}",
247
254
  "web_dir": "{WEB_DIR}",
248
255
  "mode": "{MODE}",
@@ -250,6 +257,7 @@ Config Coherence Check:
250
257
  "web_port": {WEB_PORT},
251
258
  "backend_cmd": "{BACKEND_CMD}",
252
259
  "frontend_cmd": "{FRONTEND_CMD}",
260
+ "connection_string": "{CONN_STR}",
253
261
  "sql_server": "{SQL_SERVER}",
254
262
  "sql_database": "{SQL_DATABASE}",
255
263
  "sql_status": "{ok|unreachable|db_missing|skipped}",
@@ -277,7 +285,20 @@ If NOT already running and NOT `--frontend-only`:
277
285
  cd {API_DIR} && dotnet run {LAUNCH_ARGS}
278
286
  ```
279
287
 
280
- Use `Bash(run_in_background=true)` so it runs in background.
288
+ Use `Bash(run_in_background=true)` so it runs in background. **Save the task ID** returned by the background command.
289
+
290
+ ### Fail-fast crash detection
291
+
292
+ After launching, wait 5 seconds then check if the process crashed:
293
+ ```bash
294
+ sleep 5
295
+ ```
296
+ Then call `TaskOutput(task_id="{TASK_ID}", block=false)` to read the background output.
297
+
298
+ **If the output contains a stack trace, "Unhandled exception", or the process has already exited:**
299
+ 1. Display the error output to the user
300
+ 2. Display: `[FAIL] Backend crashed on startup. Fix the error above and retry /dev-start`
301
+ 3. **STOP immediately. Do NOT attempt to diagnose, fix, rebuild, or retry.**
281
302
 
282
303
  ## Step 6: Launch frontend
283
304
 
@@ -294,25 +315,29 @@ Use `Bash(run_in_background=true)` so it runs in background.
294
315
 
295
316
  1. If `.claude/dev-session.json` exists and contains a password and `--reset` NOT specified -> reuse stored credentials
296
317
  2. If `--reset` OR no stored password:
297
- a. If backend was just launched, wait for it to be UP first:
318
+ a. If backend was just launched, wait for it to be UP first.
319
+ Run the full wait in a **single bash command** to avoid multiple tool calls:
298
320
  ```bash
299
321
  # Phase 1: Wait for port to be bound (up to 30s)
322
+ echo "Waiting for backend port ${API_PORT}..."
300
323
  for i in $(seq 1 10); do
301
- netstat.exe -ano | grep ":${API_PORT} .*LISTENING" && break
324
+ netstat.exe -ano | grep ":${API_PORT} .*LISTENING" > /dev/null 2>&1 && echo "Port bound." && break
302
325
  sleep 3
303
326
  done
304
- ```
305
- ```bash
306
- # Phase 2: Wait for API to actually respond to HTTP requests (up to 30s)
307
- # Kestrel binds the port BEFORE the app is fully initialized.
308
- # We must confirm the API actually responds before resetting the password.
327
+ # Phase 2: Wait for API to respond (up to 30s) — accept 200 or 302
328
+ echo "Waiting for API to respond..."
309
329
  for i in $(seq 1 10); do
310
- curl -s -o /dev/null -w "%{http_code}" "http://localhost:${API_PORT}/scalar" 2>/dev/null | grep -q "200" && break
330
+ HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${API_PORT}/scalar" 2>/dev/null)
331
+ [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ] && echo "API ready (HTTP $HTTP_CODE)." && break
311
332
  sleep 3
312
333
  done
313
334
  ```
314
- If `/scalar` does not return 200 after 30s, warn the user and attempt the reset anyway.
315
- b. Run: `smartstack admin reset --force --json`
335
+ If `/scalar` does not respond after 30s, warn the user and attempt the reset anyway.
336
+ b. Run admin reset using the connection string extracted from appsettings (Step 3):
337
+ ```bash
338
+ smartstack admin reset --connection "{CONN_STR}" --force --json
339
+ ```
340
+ **IMPORTANT:** Always pass `--connection` with the full connection string. Without it, the CLI shows an interactive environment picker that blocks execution.
316
341
  c. Parse JSON output to extract email and password
317
342
  d. Write `.claude/dev-session.json`:
318
343
  ```json
@@ -352,14 +377,18 @@ Use `Bash(run_in_background=true)` so it runs in background.
352
377
 
353
378
  <important_notes>
354
379
 
355
- 1. **Backend must be READY before admin reset** - First poll the port (netstat), then poll the `/health` endpoint (HTTP 200) to confirm the API is fully initialized. Kestrel binds the port before the app finishes starting, so port-only checks are insufficient.
356
- 2. **Cross-worktree support** - Detect the correct API directory regardless of which worktree we're in
357
- 3. **No MCP required** - This skill only uses bash commands and the `smartstack` CLI
358
- 4. **Config coherence is critical** - Mismatched ports between backend/frontend is the #1 cause of "it doesn't work" issues
359
- 5. **Idempotent** - Running twice should detect already-running processes and reuse stored credentials
360
- 6. **dev-session.json contains secrets** - Must be in `.gitignore`
361
- 7. **Cache** - Config detection is cached in `.claude/config/dev-run.json`. Cache is invalidated when config files change (mtime-based hash). Use `--reset` to force re-detection.
362
- 8. **SQL check is non-blocking** - A failed SQL check displays a warning but does not prevent launch. AutoMigrate may create the database on startup.
380
+ 1. **NEVER debug or fix code** - This skill ONLY starts processes. If the backend or frontend fails to start, display the error and STOP. Do NOT attempt to: clean/restore/rebuild, upgrade packages, inspect DLLs, clear NuGet cache, read .csproj files, or any other diagnostic/repair action. The user will fix the issue themselves.
381
+ 2. **Fail-fast on crash** - After launching backend in background, wait 5s then check TaskOutput. If the process already exited or shows errors, report and STOP immediately.
382
+ 3. **Backend must be READY before admin reset** - First poll the port (netstat), then poll the `/scalar` endpoint (HTTP 200/302) to confirm the API is fully initialized. Kestrel binds the port before the app finishes starting, so port-only checks are insufficient.
383
+ 4. **Admin reset requires --connection** - Always pass `--connection "{CONN_STR}"` to avoid the interactive environment picker.
384
+ 5. **Read config files in parallel** - When reading appsettings, .env, and launchSettings, use multiple Read calls in a single message.
385
+ 6. **Cross-worktree support** - Detect the correct API directory regardless of which worktree we're in.
386
+ 7. **No MCP required** - This skill only uses bash commands and the `smartstack` CLI.
387
+ 8. **Config coherence is critical** - Mismatched ports between backend/frontend is the #1 cause of "it doesn't work" issues.
388
+ 9. **Idempotent** - Running twice should detect already-running processes and reuse stored credentials.
389
+ 10. **dev-session.json contains secrets** - Must be in `.gitignore`.
390
+ 11. **Cache** - Config detection is cached in `.claude/config/dev-run.json`. Cache is invalidated when config files change (mtime-based hash). Use `--reset` to force re-detection.
391
+ 12. **SQL check is non-blocking** - A failed SQL check displays a warning but does not prevent launch. AutoMigrate may create the database on startup.
363
392
 
364
393
  </important_notes>
365
394
 
@@ -24,6 +24,7 @@ Parse `$ARGUMENTS` to determine the command:
24
24
  | `migration` | **Agent** `efcore-migration` | sonnet |
25
25
  | `squash` | **Agent** `efcore-squash` | sonnet |
26
26
  | `rebase-snapshot` | **Agent** `efcore-rebase-snapshot` | sonnet |
27
+ | (none) | **Inline** `<command-help>` | — |
27
28
 
28
29
  **Inline commands:** Execute directly following the matching `<command-*>` section below.
29
30
  **Agent commands:** Delegate via Agent tool:
@@ -31,11 +32,19 @@ Parse `$ARGUMENTS` to determine the command:
31
32
  Agent(
32
33
  subagent_type: "{agent_name}",
33
34
  model: "sonnet",
34
- prompt: "Execute /efcore {command} in {cwd}. Branch: {branch}. Flags: {flags}. Description: {description}"
35
+ prompt: "Execute /efcore {command}.
36
+ Working directory: {cwd}
37
+ Branch: {branch}
38
+ Context: {--context value if provided, else 'auto-detect'}
39
+ Force: {true if --force flag, else false}
40
+ Description: {user description if provided}
41
+ "
35
42
  )
36
43
  ```
37
44
 
38
- **Flags:** `--context <name>` (CoreDbContext/ExtensionsDbContext), `--force` (skip confirmations)
45
+ **Flags:**
46
+ - `--context <name>` — Force DbContext (`CoreDbContext` or `ExtensionsDbContext`), skip auto-detection
47
+ - `--force` — Skip all confirmations (backup still created)
39
48
 
40
49
  </routing>
41
50
 
@@ -91,6 +100,8 @@ Pattern: `{context}_v{version}_{sequence}_{Description}`
91
100
 
92
101
  **READ-ONLY: Never modify database or migrations.**
93
102
 
103
+ > **Troubleshooting:** See `references/troubleshooting.md` for common errors.
104
+
94
105
  1. Call MCP `mcp__smartstack__check_migrations` with `branch: {current_branch}`
95
106
  2. If MCP unavailable, fallback: `dotnet ef migrations list --no-build --context {Context}` per context
96
107
  3. Display compact summary
@@ -117,11 +128,14 @@ BRANCH: {branch}
117
128
 
118
129
  **ONLY runs `dotnet ef database update`. Never creates/deletes migrations.**
119
130
 
120
- 1. Verify `appsettings.Local.json` exists in the API project
121
- 2. Apply Core: `dotnet ef database update --context CoreDbContext --verbose`
122
- 3. Apply Extensions: `dotnet ef database update --context ExtensionsDbContext --verbose`
131
+ > **Reference:** See `references/database-operations.md` for connection test and migration count utilities.
132
+
133
+ 1. Detect projects: `INFRA_PROJECT=$(find src -name "*Infrastructure.csproj" | head -1)` and `STARTUP_PROJECT=$(find src -name "*.Api.csproj" | head -1)`
134
+ 2. Verify `appsettings.Local.json` exists in the API project
135
+ 3. Apply Core: `dotnet ef database update --context CoreDbContext --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT" --verbose`
136
+ 4. Apply Extensions: `dotnet ef database update --context ExtensionsDbContext --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT" --verbose`
123
137
 
124
- If connection fails: suggest verifying SQL Server, appsettings.Local.json, or `/efcore db-reset`.
138
+ If connection fails: suggest verifying SQL Server, appsettings.Local.json, or `/efcore db-reset`. See `references/troubleshooting.md`.
125
139
 
126
140
  ```
127
141
  DB DEPLOY
@@ -138,15 +152,18 @@ DB DEPLOY
138
152
 
139
153
  **ONLY operates on database. Never creates/deletes migration files.**
140
154
 
141
- 1. **Confirm** (MANDATORY unless `--force`):
155
+ > **Reference:** See `references/reset-operations.md` for drop/recreate commands.
156
+
157
+ 1. Detect projects: `INFRA_PROJECT=$(find src -name "*Infrastructure.csproj" | head -1)` and `STARTUP_PROJECT=$(find src -name "*.Api.csproj" | head -1)`
158
+ 2. **Confirm** (MANDATORY unless `--force`):
142
159
  ```
143
160
  AskUserQuestion({ question: "DELETE the database? All data will be lost.", options: ["Yes, delete", "No, cancel"] })
144
161
  ```
145
- 2. **Backup** (optional): `sqlcmd -S "$SERVER" -E -Q "BACKUP DATABASE [$DB] TO DISK='$FILE' WITH FORMAT, INIT, COMPRESSION"`
146
- 3. **Drop**: `dotnet ef database drop --force`
147
- 4. **Recreate Core**: `dotnet ef database update --context CoreDbContext`
148
- 5. **Recreate Extensions**: `dotnet ef database update --context ExtensionsDbContext`
149
- 6. **Ask** if user wants `/efcore db-seed`
162
+ 3. **Backup** (optional): `sqlcmd -S "$SERVER" -E -Q "BACKUP DATABASE [$DB] TO DISK='$FILE' WITH FORMAT, INIT, COMPRESSION"`
163
+ 4. **Drop**: `dotnet ef database drop --force --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT"`
164
+ 5. **Recreate Core**: `dotnet ef database update --context CoreDbContext --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT"`
165
+ 6. **Recreate Extensions**: `dotnet ef database update --context ExtensionsDbContext --project "$INFRA_PROJECT" --startup-project "$STARTUP_PROJECT"`
166
+ 7. **Ask** if user wants `/efcore db-seed`
150
167
 
151
168
  Block if ASPNETCORE_ENVIRONMENT=Production.
152
169
 
@@ -167,6 +184,8 @@ Next: /efcore db-status, /efcore db-seed
167
184
 
168
185
  **Never drop or recreate database.**
169
186
 
187
+ > **Reference:** See `references/seed-methods.md` for detection and execution details.
188
+
170
189
  1. **Detect** seeding method:
171
190
  - `HasData()` in EF configs → apply via `dotnet ef database update --context {Context}`
172
191
  - `IDataSeeder` / `--seed` CLI arg → `dotnet run --project {startup} -- --seed`
@@ -250,3 +269,27 @@ If conflict:
250
269
  ```
251
270
 
252
271
  </command-conflicts>
272
+
273
+ <command-help>
274
+
275
+ ## (no command) — Show available commands
276
+
277
+ Display the list of available `/efcore` commands:
278
+
279
+ ```
280
+ EF CORE COMMANDS
281
+ ────────────────
282
+ db-status Migration state check
283
+ db-deploy Apply pending migrations
284
+ db-reset Drop + recreate database
285
+ db-seed Populate test data
286
+ scan Cross-branch migration scanner
287
+ conflicts Migration conflict analyzer
288
+ migration Create/recreate migration
289
+ squash Consolidate migrations
290
+ rebase-snapshot Resync ModelSnapshot with parent
291
+
292
+ Usage: /efcore <command> [--context <name>] [--force]
293
+ ```
294
+
295
+ </command-help>
@@ -7,8 +7,7 @@
7
7
  ```bash
8
8
  # Validate required variables from step-00-init
9
9
  for VAR_NAME in DBCONTEXT DBCONTEXT_TYPE INFRA_PROJECT STARTUP_PROJECT SELECTED_ENV; do
10
- eval VAR_VALUE=\$$VAR_NAME
11
- if [ -z "$VAR_VALUE" ]; then
10
+ if [ -z "${!VAR_NAME}" ]; then
12
11
  echo "ERROR: Required variable $VAR_NAME is not set"
13
12
  echo "Run step-00-init first"
14
13
  exit 1
@@ -38,7 +37,7 @@ fi
38
37
  ## Connection Test
39
38
 
40
39
  ```bash
41
- CONNECTION_TEST=$(dotnet ef database list \
40
+ CONNECTION_TEST=$(dotnet ef migrations list \
42
41
  --context "$DBCONTEXT" \
43
42
  --project "$INFRA_PROJECT" \
44
43
  --startup-project "$STARTUP_PROJECT" 2>&1)
@@ -27,7 +27,7 @@ if grep -q "\-\-seed" ./src/*/Program.cs 2>/dev/null; then
27
27
  fi
28
28
 
29
29
  # WARNING: SQL scripts detected
30
- if [ -f "./scripts/seed.sql" ] || find . -name "seed*.sql" 2>/dev/null | grep -q .; then
30
+ if [ -f "./scripts/seed.sql" ] || find src -name "seed*.sql" -not -path "*/bin/*" -not -path "*/obj/*" 2>/dev/null | grep -q .; then
31
31
  echo ""
32
32
  echo "WARNING: SQL seed scripts detected - FORBIDDEN by conventions"
33
33
  echo "Migrate to HasData() or IDataSeeder"
@@ -58,7 +58,7 @@ the .NET SDK may not be found even if `dotnet-ef` is. Use `$USERPROFILE` first o
58
58
  ```bash
59
59
  detect_efcore_project() {
60
60
  # Find project with EF Core reference
61
- CSPROJ=$(find . -name "*.csproj" -exec grep -l "Microsoft.EntityFrameworkCore" {} \; | head -1)
61
+ CSPROJ=$(find src-name "*.csproj" -exec grep -l "Microsoft.EntityFrameworkCore" {} \; | head -1)
62
62
 
63
63
  if [ -z "$CSPROJ" ]; then
64
64
  echo "ERROR: No EF Core project found"
@@ -70,8 +70,8 @@ detect_efcore_project() {
70
70
  MIGRATIONS_DIR="$PROJECT_DIR/Persistence/Migrations"
71
71
 
72
72
  # Find startup and infrastructure projects
73
- STARTUP_PROJECT=$(find . -name "*.Api.csproj" -o -name "*Web.csproj" | head -1)
74
- INFRA_PROJECT=$(find . -name "*Infrastructure.csproj" | head -1)
73
+ STARTUP_PROJECT=$(find src-name "*.Api.csproj" -o -name "*Web.csproj" | head -1)
74
+ INFRA_PROJECT=$(find src-name "*Infrastructure.csproj" | head -1)
75
75
  [ -z "$INFRA_PROJECT" ] && INFRA_PROJECT="$CSPROJ"
76
76
 
77
77
  echo "Project: $PROJECT_NAME"
@@ -84,7 +84,7 @@ detect_efcore_project() {
84
84
  ```bash
85
85
  detect_dbcontext() {
86
86
  # Priority 1: SmartStack.Domain exists → SmartStack source project (Core only)
87
- if find . -type d -name "SmartStack.Domain" | grep -q .; then
87
+ if find src-type d -name "SmartStack.Domain" | grep -q .; then
88
88
  DBCONTEXT="CoreDbContext"
89
89
  DBCONTEXT_TYPE="core"
90
90
  SCHEMA="core"
@@ -94,7 +94,7 @@ detect_dbcontext() {
94
94
  fi
95
95
 
96
96
  # Priority 2: Client project with SmartStack NuGet → BOTH contexts
97
- if find . -name "*.csproj" -exec grep -ql "PackageReference.*SmartStack" {} \; 2>/dev/null; then
97
+ if find src-name "*.csproj" -exec grep -ql "PackageReference.*SmartStack" {} \; 2>/dev/null; then
98
98
  DBCONTEXT="CoreDbContext"
99
99
  DBCONTEXT_TYPE="both"
100
100
  SCHEMA="core"
@@ -105,8 +105,8 @@ detect_dbcontext() {
105
105
  fi
106
106
 
107
107
  # Priority 3: Scan for DbContext in code
108
- CORE_CTX=$(find . -name "*.cs" -exec grep -l "CoreDbContext" {} \; 2>/dev/null | head -1)
109
- EXT_CTX=$(find . -name "*.cs" -exec grep -l "ExtensionsDbContext" {} \; 2>/dev/null | head -1)
108
+ CORE_CTX=$(find src-name "*.cs" -exec grep -l "CoreDbContext" {} \; 2>/dev/null | head -1)
109
+ EXT_CTX=$(find src-name "*.cs" -exec grep -l "ExtensionsDbContext" {} \; 2>/dev/null | head -1)
110
110
 
111
111
  if [ -n "$CORE_CTX" ] && [ -n "$EXT_CTX" ]; then
112
112
  DBCONTEXT="CoreDbContext"
@@ -137,7 +137,7 @@ detect_dbcontext() {
137
137
 
138
138
  ```bash
139
139
  detect_environment() {
140
- API_DIR=$(find . -type d -name "*.Api" | head -1)
140
+ API_DIR=$(find src-type d -name "*.Api" | head -1)
141
141
  [ -z "$API_DIR" ] && API_DIR="src/SmartStack.Api"
142
142
 
143
143
  # Priority: --env flag > appsettings.Local.json > error
@@ -2,6 +2,8 @@
2
2
  Zero-downtime migration patterns for EF Core. Use when a migration contains destructive operations (column rename, type change, column drop) that could cause downtime during deployment.
3
3
 
4
4
  These patterns apply to production deployments where the application runs continuously. For development environments, standard migrations are sufficient.
5
+
6
+ > **Exception to "no raw SQL" rule:** `migrationBuilder.Sql(...)` is authorized ONLY for data backfill operations within zero-downtime migration patterns (e.g., `UPDATE ... SET NewCol = OldCol`). Never use it for DDL (CREATE/ALTER/DROP) — those must go through EF Core Fluent API or `SqlObjects/`.
5
7
  </overview>
6
8
 
7
9
  <when_to_use>
@@ -60,9 +60,21 @@ while read -r line; do
60
60
  continue
61
61
  fi
62
62
 
63
+ # Skip permanent branches
64
+ [[ "$WT_BRANCH" == "$GF_MAIN_BRANCH" ]] || [[ "$WT_BRANCH" == "$GF_DEVELOP_BRANCH" ]] && {
65
+ VALID+=("$WT_PATH:$WT_BRANCH")
66
+ continue
67
+ }
68
+
63
69
  # Check if branch still exists on remote
64
70
  REMOTE_EXISTS=$(git branch -r --list "origin/$WT_BRANCH" | wc -l)
65
- if [ "$REMOTE_EXISTS" -eq 0 ] && [[ "$WT_BRANCH" != "$GF_MAIN_BRANCH" ]] && [[ "$WT_BRANCH" != "$GF_DEVELOP_BRANCH" ]]; then
71
+
72
+ # Check if branch is merged into develop or main
73
+ MERGED_INTO_DEVELOP=$(git branch --merged "$GF_DEVELOP_BRANCH" | grep -c "$WT_BRANCH" 2>/dev/null || echo 0)
74
+ MERGED_INTO_MAIN=$(git branch --merged "$GF_MAIN_BRANCH" | grep -c "$WT_BRANCH" 2>/dev/null || echo 0)
75
+
76
+ # Stale if: no remote OR already merged
77
+ if [ "$REMOTE_EXISTS" -eq 0 ] || [ "$MERGED_INTO_DEVELOP" -gt 0 ] || [ "$MERGED_INTO_MAIN" -gt 0 ]; then
66
78
  STALE+=("$WT_PATH:$WT_BRANCH")
67
79
  continue
68
80
  fi
@@ -166,10 +178,14 @@ AskUserQuestion:
166
178
  # Remove orphaned
167
179
  for item in "${ORPHANED[@]}"; do
168
180
  WT_PATH=$(echo "$item" | cut -d':' -f1)
181
+ WT_BRANCH=$(echo "$item" | cut -d':' -f2)
169
182
  git worktree remove "$WT_PATH" --force 2>/dev/null || rm -rf "$WT_PATH"
183
+ # Delete remote branch if exists
184
+ git push origin --delete "$WT_BRANCH" 2>/dev/null
185
+ git branch -D "$WT_BRANCH" 2>/dev/null
170
186
  done
171
187
 
172
- # Prune
188
+ # Prune stale worktree references
173
189
  git worktree prune
174
190
 
175
191
  # Remove stale
@@ -178,15 +194,29 @@ for item in "${STALE[@]}"; do
178
194
  WT_BRANCH=$(echo "$item" | cut -d':' -f2)
179
195
 
180
196
  git worktree remove "$WT_PATH" --force 2>/dev/null || rm -rf "$WT_PATH"
197
+ # Delete remote branch if exists
198
+ git push origin --delete "$WT_BRANCH" 2>/dev/null
181
199
  git branch -D "$WT_BRANCH" 2>/dev/null
182
200
  done
201
+
202
+ # Final prune after all removals
203
+ git worktree prune
183
204
  ```
184
205
 
185
206
  **Branches:**
186
207
  ```bash
187
- # Delete merged branches
208
+ # Delete merged branches (local + remote)
188
209
  for branch in "${MERGED_BRANCHES[@]}"; do
189
210
  git branch -d "$branch"
211
+ git push origin --delete "$branch" 2>/dev/null
212
+ done
213
+ ```
214
+
215
+ **Empty parent directories:**
216
+ ```bash
217
+ # Clean up empty worktree parent directories
218
+ for dir in "$GF_FEATURES_DIR" "$GF_RELEASES_DIR" "$GF_HOTFIXES_DIR"; do
219
+ [ -d "$dir" ] && [ -z "$(ls -A "$dir" 2>/dev/null)" ] && rm -rf "$dir"
190
220
  done
191
221
  ```
192
222