@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/dist/mcp-entry.mjs +3 -17
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/agents/efcore/migration.md +20 -59
- package/templates/agents/efcore/rebase-snapshot.md +107 -58
- package/templates/agents/efcore/squash.md +134 -71
- package/templates/agents/gitflow/cleanup.md +17 -6
- package/templates/project/claude-md/infrastructure.CLAUDE.md.template +1 -1
- package/templates/skills/application/references/migration-checklist-troubleshooting.md +3 -15
- package/templates/skills/application/steps/step-06-migration.md +0 -4
- package/templates/skills/application/templates-seed.md +33 -27
- package/templates/skills/dev-start/SKILL.md +192 -41
- package/templates/skills/efcore/SKILL.md +55 -12
- package/templates/skills/efcore/references/database-operations.md +2 -3
- package/templates/skills/efcore/references/seed-methods.md +1 -1
- package/templates/skills/efcore/references/shared-init-functions.md +8 -8
- package/templates/skills/efcore/references/sql-objects-injection.md +12 -54
- package/templates/skills/efcore/references/zero-downtime-patterns.md +2 -0
- package/templates/skills/gitflow/phases/cleanup.md +33 -3
- package/templates/skills/gitflow/references/commit-migration-validation.md +4 -0
|
@@ -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.
|
|
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
|
|
37
|
+
- SQL Objects: applied automatically at startup (no migration injection needed)
|
|
38
38
|
|
|
39
39
|
---
|
|
40
40
|
|
|
41
|
-
## SQL Objects
|
|
41
|
+
## SQL Objects
|
|
42
42
|
|
|
43
|
-
|
|
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
|
|
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 —
|
|
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
|
|
995
|
-
///
|
|
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
|
-
###
|
|
1097
|
+
### Startup Integration
|
|
1077
1098
|
|
|
1078
|
-
|
|
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
|
-
-
|
|
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.
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
199
|
-
b. Run
|
|
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:
|
|
222
|
-
Frontend:
|
|
223
|
-
Swagger:
|
|
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:
|
|
226
|
-
Password:
|
|
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. **
|
|
238
|
-
2. **
|
|
239
|
-
3. **
|
|
240
|
-
4. **
|
|
241
|
-
5. **
|
|
242
|
-
6. **
|
|
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}
|
|
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:**
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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"
|