@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.
@@ -152,7 +152,7 @@ dotnet ef migrations remove --context ExtensionsDbContext -p src/{{ProjectName}}
152
152
  3. **Repositories** implement interfaces from Application
153
153
  4. **DbContext** implements `IExtensionsDbContext` from Application
154
154
  5. **Migrations** stay in this project under `Persistence/Migrations/`
155
- 6. **SQL objects via SqlObjectHelper** - TVFs/views use `SqlObjectHelper.ApplyAll(migrationBuilder)` and embedded `.sql` files
155
+ 6. **SQL objects via SqlObjectHelper** - TVFs/views use `SqlObjectHelper.ApplyAllAsync()` at startup and embedded `.sql` files (no migration injection needed)
156
156
  7. **NO sequential/deterministic GUIDs in seed data** - All GUIDs must be generated via `[guid]::NewGuid()`
157
157
  8. **Schema prefix pattern** - Table names use format `{prefix}_{EntityName}` with schema `extensions`. Use `SchemaConstants.Extensions` constant.
158
158
 
@@ -34,24 +34,13 @@ The migration should include:
34
34
  - RolePermissions (role assignments)
35
35
  - Domain entity table
36
36
  - Indexes and constraints
37
- - SQL Objects injection (if any .sql files exist)
37
+ - SQL Objects: applied automatically at startup (no migration injection needed)
38
38
 
39
39
  ---
40
40
 
41
- ## SQL Objects Injection
41
+ ## SQL Objects
42
42
 
43
- Check if any SQL object files exist:
44
- ```
45
- Glob: **/Persistence/SqlObjects/Functions/*.sql
46
- ```
47
-
48
- If SQL files are found, the migration's `Up()` method should include a call to re-apply all SQL objects:
49
- ```csharp
50
- // At the end of the Up() method:
51
- SqlObjectHelper.ApplyAll(migrationBuilder);
52
- ```
53
-
54
- This ensures SQL functions/views are re-applied with each migration (`CREATE OR ALTER` = idempotent).
43
+ SQL objects (TVFs, views, stored procedures) are applied **automatically at application startup** via `SqlObjectHelper.ApplyAllAsync()` in `SmartStackExtensions.InitializeSmartStackAsync()`. No injection into migrations is needed — just restart the application after modifying `.sql` files.
55
44
 
56
45
  ---
57
46
 
@@ -92,7 +81,6 @@ dotnet ef database update --context CoreDbContext
92
81
  - [ ] Migration name suggested via MCP
93
82
  - [ ] Migration created with correct context
94
83
  - [ ] Migration includes navigation, permissions, and entity table
95
- - [ ] SQL objects injected (if applicable)
96
84
  - [ ] Migration applied successfully
97
85
  - [ ] Database updated without errors
98
86
  - [ ] DbSet registered in DbContext
@@ -86,10 +86,6 @@ dotnet ef migrations add {suggested_migration_name} --context ExtensionsDbContex
86
86
  > NEVER create subdirectories under `Migrations/` (e.g., `Migrations/Extensions/`).
87
87
  > All migrations for a given DbContext MUST share the same namespace to avoid EF Core errors.
88
88
 
89
- ### 2b. Inject SQL Objects (if any exist)
90
-
91
- See [references/migration-checklist-troubleshooting.md](../references/migration-checklist-troubleshooting.md) for SQL Objects injection pattern.
92
-
93
89
  ### 3. Verify Migration Content
94
90
 
95
91
  See [references/migration-checklist-troubleshooting.md](../references/migration-checklist-troubleshooting.md) for complete migration content verification checklist.
@@ -955,7 +955,7 @@ Level 4: RESOURCE (finest granularity)
955
955
  ## SQL OBJECTS INFRASTRUCTURE
956
956
 
957
957
  > **Usage:** SQL Server functions (TVFs, scalar), views, and stored procedures managed as embedded resources
958
- > **Pattern:** Same as SmartStack.app — `CREATE OR ALTER` for idempotency, applied via migrations
958
+ > **Pattern:** Same as SmartStack.app — `CREATE OR ALTER` for idempotency, applied automatically at startup
959
959
  > **Location:** `Infrastructure/Persistence/SqlObjects/`
960
960
 
961
961
  ### Directory Structure
@@ -983,7 +983,7 @@ namespace {BaseNamespace}.Infrastructure.Persistence.SqlObjects;
983
983
 
984
984
  /// <summary>
985
985
  /// Helper for applying SQL objects from embedded resources.
986
- /// SQL files use CREATE OR ALTER for idempotency — safe to re-run on any migration or squash.
986
+ /// SQL files use CREATE OR ALTER for idempotency — applied automatically at startup.
987
987
  /// </summary>
988
988
  public static class SqlObjectHelper
989
989
  {
@@ -991,8 +991,19 @@ public static class SqlObjectHelper
991
991
  private const string ResourcePrefix = "{BaseNamespace}.Infrastructure.Persistence.SqlObjects.";
992
992
 
993
993
  /// <summary>
994
- /// Applies all SQL objects (functions, views, stored procedures).
995
- /// Call in Up() method of a migration, especially after a squash.
994
+ /// Applies all SQL objects directly to the database via DbContext.
995
+ /// Called at startup after MigrateAsync() ensures SQL objects are always up-to-date
996
+ /// without requiring a new migration when a .sql file changes.
997
+ /// </summary>
998
+ public static async Task ApplyAllAsync(DbContext dbContext)
999
+ {
1000
+ await ApplyCategoryToDbAsync(dbContext, "Functions");
1001
+ // Future: await ApplyCategoryToDbAsync(dbContext, "Views");
1002
+ // Future: await ApplyCategoryToDbAsync(dbContext, "StoredProcedures");
1003
+ }
1004
+
1005
+ /// <summary>
1006
+ /// Applies all SQL objects via MigrationBuilder (legacy, kept for backward compatibility).
996
1007
  /// </summary>
997
1008
  public static void ApplyAll(MigrationBuilder migrationBuilder)
998
1009
  {
@@ -1004,12 +1015,6 @@ public static class SqlObjectHelper
1004
1015
  ApplyCategory(migrationBuilder, "Functions");
1005
1016
  }
1006
1017
 
1007
- public static void ApplyOne(MigrationBuilder migrationBuilder, string resourceName)
1008
- {
1009
- var sql = ReadSqlObject(resourceName);
1010
- migrationBuilder.Sql(sql);
1011
- }
1012
-
1013
1018
  public static string ReadSqlObject(string resourceName)
1014
1019
  {
1015
1020
  var fullName = ResourcePrefix + resourceName;
@@ -1037,6 +1042,22 @@ public static class SqlObjectHelper
1037
1042
  migrationBuilder.Sql(reader.ReadToEnd());
1038
1043
  }
1039
1044
  }
1045
+
1046
+ private static async Task ApplyCategoryToDbAsync(DbContext dbContext, string category)
1047
+ {
1048
+ var prefix = ResourcePrefix + category + ".";
1049
+ var resources = Assembly.GetManifestResourceNames()
1050
+ .Where(n => n.StartsWith(prefix, StringComparison.Ordinal)
1051
+ && n.EndsWith(".sql", StringComparison.Ordinal))
1052
+ .OrderBy(n => n, StringComparer.Ordinal);
1053
+
1054
+ foreach (var resource in resources)
1055
+ {
1056
+ using var stream = Assembly.GetManifestResourceStream(resource)!;
1057
+ using var reader = new StreamReader(stream);
1058
+ await dbContext.Database.ExecuteSqlRawAsync(reader.ReadToEnd());
1059
+ }
1060
+ }
1040
1061
  }
1041
1062
  ```
1042
1063
 
@@ -1073,24 +1094,9 @@ RETURN
1073
1094
  );
1074
1095
  ```
1075
1096
 
1076
- ### Migration Integration
1097
+ ### Startup Integration
1077
1098
 
1078
- In a migration's `Up()` method, call SqlObjectHelper to apply/re-apply SQL objects:
1079
-
1080
- ```csharp
1081
- protected override void Up(MigrationBuilder migrationBuilder)
1082
- {
1083
- // ... table creation, indexes, etc.
1084
-
1085
- // Apply all SQL objects (idempotent — CREATE OR ALTER)
1086
- SqlObjectHelper.ApplyAll(migrationBuilder);
1087
- }
1088
- ```
1089
-
1090
- Or apply a single function:
1091
- ```csharp
1092
- SqlObjectHelper.ApplyOne(migrationBuilder, "Functions.fn_GetEntityHierarchy.sql");
1093
- ```
1099
+ SQL objects are applied **automatically at application startup** via `SqlObjectHelper.ApplyAllAsync(dbContext)`, called in `SmartStackExtensions.InitializeSmartStackAsync()` right after `MigrateAsync()`. No migration injection needed — just add/modify `.sql` files and restart the application.
1094
1100
 
1095
1101
  ### When to Use SQL Objects
1096
1102
 
@@ -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
@@ -11,25 +11,21 @@ $ARGUMENTS
11
11
 
12
12
  ## Current state (auto-injected)
13
13
 
14
+ - Dev cache: !`cat .claude/config/dev-run.json 2>/dev/null || echo "no cache"`
14
15
  - Dev session: !`cat .claude/dev-session.json 2>/dev/null || echo "no session"`
15
- - Backend port check: !`netstat.exe -ano 2>/dev/null | grep -E ":(5142|5290) .*LISTENING" | head -5 || echo "no backend listening"`
16
- - Frontend port check: !`netstat.exe -ano 2>/dev/null | grep -E ":(6173|3000) .*LISTENING" | head -5 || echo "no frontend listening"`
17
- - API directory: !`ls -d src/*.Api 2>/dev/null || echo "no API found"`
18
- - Web directory: !`ls -d web/*-web 2>/dev/null || echo "no web found"`
19
- - Local config: !`cat src/*.Api/appsettings.Local.json 2>/dev/null || echo "no local config"`
20
- - Frontend env standalone: !`cat web/*-web/.env.standalone 2>/dev/null || echo "no .env.standalone"`
21
- - Frontend env development: !`cat web/*-web/.env.development 2>/dev/null || echo "no .env.development"`
16
+ - Ports running: !`netstat.exe -ano 2>/dev/null | grep -E "LISTENING" | grep -E ":(5[0-9]{3}|6173|3000) " | head -5 || echo "none"`
22
17
 
23
18
  <objective>
24
- Launch the SmartStack development environment in one command: detect configuration, validate config coherence, start backend + frontend, and provide admin credentials.
19
+ Launch the SmartStack development environment in one command: detect configuration, validate config coherence, check SQL connectivity, start backend + frontend, and provide admin credentials.
25
20
  Idempotent: safe to run multiple times. Detects already-running processes.
21
+ Uses a config cache to skip re-detection on subsequent runs when config files haven't changed.
26
22
  </objective>
27
23
 
28
24
  <quick_start>
29
25
  ```bash
30
26
  /dev-start # Launch everything (idempotent)
31
27
  /dev-start --stop # Stop backend + frontend
32
- /dev-start --reset # Force admin password reset
28
+ /dev-start --reset # Force admin password reset + re-detect config
33
29
  /dev-start --backend-only # Launch only the backend
34
30
  /dev-start --frontend-only # Launch only the frontend
35
31
  ```
@@ -41,7 +37,7 @@ Idempotent: safe to run multiple times. Detects already-running processes.
41
37
  |---------|-------------|
42
38
  | `/dev-start` | Launch all (idempotent) + validate configs |
43
39
  | `/dev-start --stop` | Stop backend + frontend via taskkill.exe |
44
- | `/dev-start --reset` | Force reset admin password |
40
+ | `/dev-start --reset` | Force reset admin password + re-detect config |
45
41
  | `/dev-start --backend-only` | Launch only backend |
46
42
  | `/dev-start --frontend-only` | Launch only frontend |
47
43
 
@@ -53,30 +49,75 @@ Idempotent: safe to run multiple times. Detects already-running processes.
53
49
 
54
50
  If `--stop` argument detected, execute the stop workflow and exit:
55
51
 
56
- 1. Find backend PID:
52
+ 1. Determine ports to stop:
53
+ - If `.claude/config/dev-run.json` exists → read `api_port` and `web_port` from cache
54
+ - Otherwise → scan default ports: 5142, 5290 (API) and 6173, 3000 (Web)
55
+
56
+ 2. Find backend PID:
57
57
  ```bash
58
58
  netstat.exe -ano | grep ":${API_PORT} .*LISTENING" | awk '{print $5}' | head -1
59
59
  ```
60
- 2. If found: `taskkill.exe /PID $PID /F`
61
- 3. Find frontend PID:
60
+ 3. If found: `taskkill.exe /PID $PID /F`
61
+ 4. Find frontend PID:
62
62
  ```bash
63
63
  netstat.exe -ano | grep ":${WEB_PORT} .*LISTENING" | awk '{print $5}' | head -1
64
64
  ```
65
- 4. If found: `taskkill.exe /PID $PID /F`
66
- 5. Display stopped status and exit.
65
+ 5. If found: `taskkill.exe /PID $PID /F`
66
+ 6. Display stopped status and exit.
67
67
 
68
68
  If no processes found, display "Nothing running" and exit.
69
69
 
70
+ ## Step 0: Load cache
71
+
72
+ If `--stop` argument → skip this step entirely (--stop uses netstat only).
73
+
74
+ 1. Read cache file:
75
+ ```bash
76
+ cat .claude/config/dev-run.json 2>/dev/null
77
+ ```
78
+
79
+ 2. **If no cache OR `--reset` argument** → CACHE_HIT=false, go to Step 1.
80
+
81
+ 3. **If cache exists**, verify config files haven't changed:
82
+ ```bash
83
+ API_DIR=$(grep -o '"api_dir": *"[^"]*"' .claude/config/dev-run.json | cut -d'"' -f4)
84
+ WEB_DIR=$(grep -o '"web_dir": *"[^"]*"' .claude/config/dev-run.json | cut -d'"' -f4)
85
+ MODE=$(grep -o '"mode": *"[^"]*"' .claude/config/dev-run.json | cut -d'"' -f4)
86
+
87
+ if [ "$MODE" = "Local" ]; then
88
+ CURRENT_HASH=$(stat -c %Y "$API_DIR/appsettings.Local.json" "$WEB_DIR/.env.standalone" "$API_DIR/Properties/launchSettings.json" 2>/dev/null | md5sum | cut -d' ' -f1)
89
+ else
90
+ CURRENT_HASH=$(stat -c %Y "$API_DIR/appsettings.json" "$WEB_DIR/.env.development" "$API_DIR/Properties/launchSettings.json" 2>/dev/null | md5sum | cut -d' ' -f1)
91
+ fi
92
+
93
+ CACHED_HASH=$(grep -o '"config_hash": *"[^"]*"' .claude/config/dev-run.json | cut -d'"' -f4)
94
+ ```
95
+
96
+ 4. **If CURRENT_HASH = CACHED_HASH** → CACHE_HIT=true :
97
+ - Load all values from cache (api_dir, web_dir, mode, ports, commands, sql_status)
98
+ - Display: `[CACHE] Using cached config (from {cached_at})`
99
+ - **Skip directly to Step 4** (check if already running)
100
+
101
+ 5. **If hash mismatch** → CACHE_HIT=false :
102
+ - Display: `[CACHE] Config changed, re-detecting...`
103
+ - Continue to Step 1
104
+
70
105
  ## Step 1: Detect project
71
106
 
72
- Find the API and Web directories:
73
- - API: `src/*.Api` (first match)
74
- - Web: `web/*-web` (first match)
107
+ **Skip if CACHE_HIT=true (values loaded from cache in Step 0).**
108
+
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
+ ```
75
114
 
76
115
  If neither found, display error and exit.
77
116
 
78
117
  ## Step 2: Detect environment and ports
79
118
 
119
+ **Skip if CACHE_HIT=true (values loaded from cache in Step 0).**
120
+
80
121
  Check if `appsettings.Local.json` exists in the API directory:
81
122
 
82
123
  | Condition | Mode | Backend | Frontend | Backend command | Frontend command |
@@ -90,8 +131,15 @@ Check if `appsettings.Local.json` exists in the API directory:
90
131
  - **Dev backend port**: Default `5142`
91
132
  - **Dev frontend port**: Default `6173`
92
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
+
93
139
  ## Step 3: Validate config coherence
94
140
 
141
+ **Skip if CACHE_HIT=true (values loaded from cache in Step 0).**
142
+
95
143
  **CRITICAL: Verify that backend and frontend configs are consistent.**
96
144
 
97
145
  ### Rules to check:
@@ -142,6 +190,82 @@ Config Coherence Check:
142
190
  -> Creating .env.standalone with correct values...
143
191
  ```
144
192
 
193
+ ### Rule 5: SQL Server connectivity
194
+
195
+ 1. Extract connection string from appsettings:
196
+ - **Local mode**: `appsettings.Local.json` → `ConnectionStrings.DefaultConnection`
197
+ - **Dev mode**: `appsettings.json` → `ConnectionStrings.DefaultConnection`
198
+
199
+ 2. Parse server and database:
200
+ ```bash
201
+ SQL_SERVER=$(echo "$CONN_STR" | grep -oiP '(?:Server|Data Source)=\K[^;]+')
202
+ SQL_DATABASE=$(echo "$CONN_STR" | grep -oiP '(?:Database|Initial Catalog)=\K[^;]+')
203
+ USE_WIN_AUTH=$(echo "$CONN_STR" | grep -qi 'Integrated Security=true\|Trusted_Connection=true' && echo "true" || echo "false")
204
+ ```
205
+
206
+ 3. Test connectivity (5s timeout):
207
+ ```bash
208
+ # Normalize (local) → .
209
+ SQL_SERVER_NORM=$(echo "$SQL_SERVER" | sed 's/(local)/./')
210
+
211
+ if [ "$USE_WIN_AUTH" = "true" ]; then
212
+ sqlcmd -S "$SQL_SERVER_NORM" -d master -Q "SELECT 1" -t 5 -E -C -h -1 -W 2>/dev/null
213
+ else
214
+ SQL_USER=$(echo "$CONN_STR" | grep -oiP '(?:User Id|Uid)=\K[^;]+')
215
+ SQL_PASS=$(echo "$CONN_STR" | grep -oiP '(?:Password|Pwd)=\K[^;]+')
216
+ sqlcmd -S "$SQL_SERVER_NORM" -d master -Q "SELECT 1" -t 5 -U "$SQL_USER" -P "$SQL_PASS" -C -h -1 -W 2>/dev/null
217
+ fi
218
+ ```
219
+
220
+ 4. If reachable, check database exists:
221
+ ```bash
222
+ sqlcmd -S "$SQL_SERVER_NORM" -Q "SET NOCOUNT ON; SELECT name FROM sys.databases WHERE name = '$SQL_DATABASE'" -t 5 -E -C -h -1 -W 2>/dev/null
223
+ ```
224
+
225
+ 5. Output format:
226
+ | Scenario | Output | Blocking? |
227
+ |----------|--------|-----------|
228
+ | Server OK + DB exists | `[OK] SQL Server (server) — Database 'db' exists` | No |
229
+ | Server OK + DB missing | `[WARN] Database 'db' not found (AutoMigrate will create it)` | No |
230
+ | Server unreachable | `[FAIL] SQL Server (server) unreachable — Is the service running?` | No |
231
+ | sqlcmd not installed | `[SKIP] sqlcmd not found — SQL check skipped` | No |
232
+ | No connection string | `[SKIP] No connection string — SQL check skipped` | No |
233
+
234
+ ## Step 3b: Write cache
235
+
236
+ **Only execute if CACHE_HIT=false (Steps 1-3 were executed).**
237
+
238
+ 1. Ensure directory exists: `mkdir -p .claude/config`
239
+
240
+ 2. Compute config hash:
241
+ ```bash
242
+ if [ "$MODE" = "Local" ]; then
243
+ CONFIG_HASH=$(stat -c %Y "$API_DIR/appsettings.Local.json" "$WEB_DIR/.env.standalone" "$API_DIR/Properties/launchSettings.json" 2>/dev/null | md5sum | cut -d' ' -f1)
244
+ else
245
+ CONFIG_HASH=$(stat -c %Y "$API_DIR/appsettings.json" "$WEB_DIR/.env.development" "$API_DIR/Properties/launchSettings.json" 2>/dev/null | md5sum | cut -d' ' -f1)
246
+ fi
247
+ ```
248
+
249
+ 3. Write `.claude/config/dev-run.json` using the Write tool with all detected values:
250
+ ```json
251
+ {
252
+ "version": 2,
253
+ "api_dir": "{API_DIR}",
254
+ "web_dir": "{WEB_DIR}",
255
+ "mode": "{MODE}",
256
+ "api_port": {API_PORT},
257
+ "web_port": {WEB_PORT},
258
+ "backend_cmd": "{BACKEND_CMD}",
259
+ "frontend_cmd": "{FRONTEND_CMD}",
260
+ "connection_string": "{CONN_STR}",
261
+ "sql_server": "{SQL_SERVER}",
262
+ "sql_database": "{SQL_DATABASE}",
263
+ "sql_status": "{ok|unreachable|db_missing|skipped}",
264
+ "config_hash": "{CONFIG_HASH}",
265
+ "cached_at": "{ISO_TIMESTAMP}"
266
+ }
267
+ ```
268
+
145
269
  ## Step 4: Check if already running
146
270
 
147
271
  ```bash
@@ -161,7 +285,20 @@ If NOT already running and NOT `--frontend-only`:
161
285
  cd {API_DIR} && dotnet run {LAUNCH_ARGS}
162
286
  ```
163
287
 
164
- 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.**
165
302
 
166
303
  ## Step 6: Launch frontend
167
304
 
@@ -178,25 +315,29 @@ Use `Bash(run_in_background=true)` so it runs in background.
178
315
 
179
316
  1. If `.claude/dev-session.json` exists and contains a password and `--reset` NOT specified -> reuse stored credentials
180
317
  2. If `--reset` OR no stored password:
181
- 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:
182
320
  ```bash
183
321
  # Phase 1: Wait for port to be bound (up to 30s)
322
+ echo "Waiting for backend port ${API_PORT}..."
184
323
  for i in $(seq 1 10); do
185
- netstat.exe -ano | grep ":${API_PORT} .*LISTENING" && break
324
+ netstat.exe -ano | grep ":${API_PORT} .*LISTENING" > /dev/null 2>&1 && echo "Port bound." && break
186
325
  sleep 3
187
326
  done
188
- ```
189
- ```bash
190
- # Phase 2: Wait for API to actually respond to HTTP requests (up to 30s)
191
- # Kestrel binds the port BEFORE the app is fully initialized.
192
- # 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..."
193
329
  for i in $(seq 1 10); do
194
- 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
195
332
  sleep 3
196
333
  done
197
334
  ```
198
- If `/scalar` does not return 200 after 30s, warn the user and attempt the reset anyway.
199
- 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.
200
341
  c. Parse JSON output to extract email and password
201
342
  d. Write `.claude/dev-session.json`:
202
343
  ```json
@@ -218,15 +359,17 @@ Use `Bash(run_in_background=true)` so it runs in background.
218
359
  SMARTSTACK DEV ENVIRONMENT
219
360
  ================================================================
220
361
 
221
- Backend: http://localhost:{API_PORT} [RUNNING|STARTING]
222
- Frontend: http://localhost:{WEB_PORT} [RUNNING|STARTING]
223
- Swagger: http://localhost:{API_PORT}/scalar
362
+ Backend: http://localhost:{API_PORT} [RUNNING|STARTING]
363
+ Frontend: http://localhost:{WEB_PORT} [RUNNING|STARTING]
364
+ Swagger: http://localhost:{API_PORT}/scalar
365
+ SQL Server: {SQL_SERVER}/{SQL_DATABASE} [OK|WARN|FAIL|SKIP]
224
366
 
225
- Email: local.admin@smartstack.local
226
- Password: xxxxxxxxxxxxxx
367
+ Email: local.admin@smartstack.local
368
+ Password: xxxxxxxxxxxxxx
227
369
 
228
370
  Environment: {Development|Local}
229
371
  Config: OK | WARNING (details)
372
+ Cache: HIT (from {cached_at}) | MISS (re-detected)
230
373
  ================================================================
231
374
  ```
232
375
 
@@ -234,12 +377,18 @@ Use `Bash(run_in_background=true)` so it runs in background.
234
377
 
235
378
  <important_notes>
236
379
 
237
- 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.
238
- 2. **Cross-worktree support** - Detect the correct API directory regardless of which worktree we're in
239
- 3. **No MCP required** - This skill only uses bash commands and the `smartstack` CLI
240
- 4. **Config coherence is critical** - Mismatched ports between backend/frontend is the #1 cause of "it doesn't work" issues
241
- 5. **Idempotent** - Running twice should detect already-running processes and reuse stored credentials
242
- 6. **dev-session.json contains secrets** - Must be in `.gitignore`
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.
243
392
 
244
393
  </important_notes>
245
394
 
@@ -249,4 +398,6 @@ Use `Bash(run_in_background=true)` so it runs in background.
249
398
  - Admin credentials available (reused or freshly reset)
250
399
  - Connection info displayed in clear format
251
400
  - No errors on repeated invocations (idempotent)
401
+ - Cache written on first run, reused on subsequent runs
402
+ - SQL Server connectivity reported
252
403
  </success_criteria>
@@ -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"