@atlashub/smartstack-cli 4.37.0 → 4.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlashub/smartstack-cli",
3
- "version": "4.37.0",
3
+ "version": "4.38.0",
4
4
  "description": "SmartStack Claude Code automation toolkit - GitFlow, EF Core migrations, prompts and more",
5
5
  "author": {
6
6
  "name": "SmartStack",
@@ -114,48 +114,11 @@ rm Persistence/Migrations/*${OLD_NAME}*.cs
114
114
 
115
115
  > **Note:** `$MCP_NAME` is obtained via MCP call, never computed locally.
116
116
 
117
- ## SQL Objects Post-Injection (MANDATORY after every migration creation)
117
+ ## SQL Objects (NO injection needed)
118
118
 
119
- **This step is NON-OPTIONAL.** After EVERY `dotnet ef migrations add`, you MUST check for SQL objects and inject them into the generated migration. Skipping this step causes runtime 500 errors on ALL endpoints (TVFs like `fn_GetUserGroupHierarchy` won't exist in the database).
119
+ SQL objects (TVFs, views, stored procedures) are applied **automatically at application startup** via `SqlObjectHelper.ApplyAllAsync(dbContext)` in `SmartStackExtensions.InitializeSmartStackAsync()`, right after `MigrateAsync()`.
120
120
 
121
- ```bash
122
- # Detect the generated migration file (the main .cs, not .Designer.cs)
123
- MIGRATION_FILE=$(find "$MIGRATIONS_DIR" -name "*${MCP_NAME}.cs" ! -name "*.Designer.cs" | head -1)
124
- SQL_OBJECTS_DIR="$INFRA_PROJECT_DIR/Persistence/SqlObjects"
125
- SQL_FILES=$(find "$SQL_OBJECTS_DIR" -name "*.sql" 2>/dev/null | wc -l)
126
-
127
- if [ "$SQL_FILES" -gt 0 ]; then
128
- echo "Found $SQL_FILES SQL object(s) — injecting SqlObjectHelper.ApplyAll()..."
129
-
130
- # Add using directive if not present
131
- if ! grep -q "using SmartStack.Infrastructure.Persistence.SqlObjects;" "$MIGRATION_FILE"; then
132
- sed -i '1s/^/using SmartStack.Infrastructure.Persistence.SqlObjects;\n/' "$MIGRATION_FILE"
133
- fi
134
-
135
- # Add SqlObjectHelper.ApplyAll at the end of Up() method
136
- # Use Edit tool to insert before the closing brace of Up()
137
- # Target: the last " }" before "protected override void Down"
138
- sed -i '/protected override void Down/i\
139
- \ // Apply SQL objects (TVF, Views, SP) from embedded resources\
140
- \ SqlObjectHelper.ApplyAll(migrationBuilder);' "$MIGRATION_FILE"
141
-
142
- # VERIFY injection succeeded — FAIL if not
143
- if ! grep -q "SqlObjectHelper.ApplyAll" "$MIGRATION_FILE"; then
144
- echo "ERROR: Auto-injection failed. Use Edit tool to manually add:"
145
- echo " 1. Add 'using SmartStack.Infrastructure.Persistence.SqlObjects;' at top"
146
- echo " 2. Add 'SqlObjectHelper.ApplyAll(migrationBuilder);' at end of Up() method"
147
- echo " File: $MIGRATION_FILE"
148
- # DO NOT CONTINUE — fix this before proceeding
149
- else
150
- echo " SqlObjectHelper.ApplyAll(migrationBuilder) injected successfully"
151
- find "$SQL_OBJECTS_DIR" -name "*.sql" -exec basename {} \; | while read f; do echo " - $f"; done
152
- fi
153
- else
154
- echo "No SQL objects found in SqlObjects/ — skipping injection"
155
- fi
156
- ```
157
-
158
- > **CRITICAL:** If the bash `sed` injection fails (indentation mismatch), use the **Edit tool** to manually insert the lines. NEVER skip this step — it causes cascading 500 errors at runtime.
121
+ **You do NOT need to add `SqlObjectHelper.ApplyAll(migrationBuilder)` in migrations.** The startup process handles this with `CREATE OR ALTER` (idempotent). Modifying a `.sql` file only requires an application restart — no new migration needed.
159
122
 
160
123
  ## Context Detection
161
124
 
@@ -230,8 +193,7 @@ After rebase on develop:
230
193
  **ONLY** SQL objects defined in `src/{Project}.Infrastructure/Persistence/SqlObjects/` are authorized.
231
194
 
232
195
  - All SQL objects (functions, views, procedures, TVFs) MUST be `.sql` files in `Persistence/SqlObjects/`
233
- - Migrations reference these via `SqlObjectHelper.ApplyAll(migrationBuilder)` in the `Up()` method
234
- - If `Persistence/SqlObjects/*.sql` files exist, inject the call automatically after migration creation
196
+ - SQL objects are applied **automatically at startup** via `SqlObjectHelper.ApplyAllAsync()` no migration injection needed
235
197
  - **Never** write raw SQL directly inside migration `Up()` or `Down()` methods
236
198
 
237
199
  ## Important Notes
@@ -26,7 +26,7 @@ fi
26
26
  **FORBIDDEN:** Raw SQL inline in migrations (e.g., `migrationBuilder.Sql("CREATE FUNCTION ...")`)
27
27
 
28
28
  **ONLY** SQL objects defined in `src/{Project}.Infrastructure/Persistence/SqlObjects/` are authorized.
29
- Migrations reference these via `SqlObjectHelper.ApplyAll(migrationBuilder)` — never inline SQL.
29
+ SQL objects are applied **automatically at startup** via `SqlObjectHelper.ApplyAllAsync()` — no migration injection needed.
30
30
 
31
31
  ## Workflow
32
32
 
@@ -70,41 +70,8 @@ mcp__smartstack__suggest_migration({ description: "...", context: DBCONTEXT_TYPE
70
70
 
71
71
  dotnet ef migrations add "$MIGRATION_NAME" --context "$DBCONTEXT"
72
72
 
73
- # ═══════════════════════════════════════════════════════════════════════════
74
- # MANDATORY: SQL Objects Post-Injection
75
- # Without this, TVFs (fn_GetUserGroupHierarchy etc.) won't exist in DB
76
- # and ALL endpoints will return 500 at runtime
77
- # ═══════════════════════════════════════════════════════════════════════════
78
- MIGRATION_FILE=$(find Migrations -name "*${MIGRATION_NAME}.cs" ! -name "*.Designer.cs" | head -1)
79
- SQL_OBJECTS_DIR="$INFRA_PROJECT_DIR/Persistence/SqlObjects"
80
- SQL_FILES=$(find "$SQL_OBJECTS_DIR" -name "*.sql" 2>/dev/null | wc -l)
81
-
82
- if [ "$SQL_FILES" -gt 0 ]; then
83
- echo "Found $SQL_FILES SQL object(s) — injecting SqlObjectHelper.ApplyAll()..."
84
-
85
- # Add using directive if not present
86
- if ! grep -q "using SmartStack.Infrastructure.Persistence.SqlObjects;" "$MIGRATION_FILE"; then
87
- sed -i '1s/^/using SmartStack.Infrastructure.Persistence.SqlObjects;\n/' "$MIGRATION_FILE"
88
- fi
89
-
90
- # Inject SqlObjectHelper.ApplyAll before Down() method
91
- sed -i '/protected override void Down/i\
92
- \ // Apply SQL objects (TVF, Views, SP) from embedded resources\
93
- \ SqlObjectHelper.ApplyAll(migrationBuilder);' "$MIGRATION_FILE"
94
-
95
- # VERIFY — FAIL if injection didn't work
96
- if ! grep -q "SqlObjectHelper.ApplyAll" "$MIGRATION_FILE"; then
97
- echo "ERROR: Auto-injection failed. Use Edit tool to manually add:"
98
- echo " 1. 'using SmartStack.Infrastructure.Persistence.SqlObjects;' at top"
99
- echo " 2. 'SqlObjectHelper.ApplyAll(migrationBuilder);' at end of Up()"
100
- echo " File: $MIGRATION_FILE"
101
- else
102
- echo " SqlObjectHelper.ApplyAll(migrationBuilder) injected"
103
- find "$SQL_OBJECTS_DIR" -name "*.sql" -exec basename {} \; | while read f; do echo " - $f"; done
104
- fi
105
- else
106
- echo "No SQL objects found — skipping injection"
107
- fi
73
+ # NOTE: SQL objects (TVFs, views, SPs) are applied automatically at startup
74
+ # via SqlObjectHelper.ApplyAllAsync() no injection needed in migrations.
108
75
 
109
76
  # Validate
110
77
  dotnet build
@@ -130,7 +97,6 @@ core_v1.7.0_001_ReleaseInitial
130
97
  - [ ] Backup created
131
98
  - [ ] Build OK after rebase
132
99
  - [ ] SQL script can be generated
133
- - [ ] SQL objects injected (if `Persistence/SqlObjects/*.sql` exists)
134
100
 
135
101
  ## Error Recovery
136
102
 
@@ -140,7 +106,7 @@ core_v1.7.0_001_ReleaseInitial
140
106
  | MCP unavailable | Check `.claude/mcp-status.json`, run `/mcp healthcheck` |
141
107
  | Snapshot fetch fails | Verify `origin/$BASE_BRANCH` exists: `git fetch origin $BASE_BRANCH` |
142
108
  | Migration creation fails | Restore backup, check `dotnet build` output |
143
- | SQL objects injection fails | Manual step: add `SqlObjectHelper.ApplyAll(migrationBuilder)` in Up() |
109
+ | SQL objects missing at runtime | SQL objects are applied at startup — restart the application |
144
110
 
145
111
  **Automatic recovery:** On ANY failure after backup:
146
112
  1. Restore all files from backup directory
@@ -3,7 +3,7 @@ name: efcore-squash
3
3
  description: EF Core migration squasher - combine multiple migrations into one
4
4
  color: magenta
5
5
  model: sonnet
6
- tools: Bash, Glob, Read
6
+ tools: Bash, Glob, Read, Edit
7
7
  ---
8
8
 
9
9
  # EF Core Squash Agent
@@ -26,7 +26,7 @@ fi
26
26
  **FORBIDDEN:** Raw SQL inline in migrations (e.g., `migrationBuilder.Sql("CREATE FUNCTION ...")`)
27
27
 
28
28
  **ONLY** SQL objects defined in `src/{Project}.Infrastructure/Persistence/SqlObjects/` are authorized.
29
- Migrations reference these via `SqlObjectHelper.ApplyAll(migrationBuilder)` — never inline SQL.
29
+ SQL objects are applied **automatically at startup** via `SqlObjectHelper.ApplyAllAsync()` — no migration injection needed.
30
30
 
31
31
  ## Core Principle
32
32
 
@@ -51,8 +51,7 @@ WARNING: NEVER retrieve only the snapshot without the migrations!
51
51
  5. **Fetch**: Retrieve **ModelSnapshot AND migrations** from parent branch (CRUCIAL!)
52
52
  6. **Delete**: Remove branch-specific migrations ONLY
53
53
  7. **Create**: Create consolidated migration (MCP mandatory)
54
- 8. **Inject SQL Objects**: If `Persistence/SqlObjects/` contains `.sql` files, inject `SqlObjectHelper.ApplyAll(migrationBuilder)` in `Up()`
55
- 9. **Validate**: Build + script OK
54
+ 8. **Validate**: Build + script OK
56
55
 
57
56
  ## Key Commands
58
57
 
@@ -100,41 +99,8 @@ mcp__smartstack__suggest_migration({ description: "...", context: DBCONTEXT_TYPE
100
99
 
101
100
  dotnet ef migrations add "$MIGRATION_NAME_FROM_MCP" --context "$DBCONTEXT"
102
101
 
103
- # ═══════════════════════════════════════════════════════════════════════════
104
- # MANDATORY: SQL Objects Post-Injection
105
- # Without this, TVFs (fn_GetUserGroupHierarchy etc.) won't exist in DB
106
- # and ALL endpoints will return 500 at runtime
107
- # ═══════════════════════════════════════════════════════════════════════════
108
- MIGRATION_FILE=$(find Migrations -name "*${MIGRATION_NAME_FROM_MCP}.cs" ! -name "*.Designer.cs" | head -1)
109
- SQL_OBJECTS_DIR="$INFRA_PROJECT_DIR/Persistence/SqlObjects"
110
- SQL_FILES=$(find "$SQL_OBJECTS_DIR" -name "*.sql" 2>/dev/null | wc -l)
111
-
112
- if [ "$SQL_FILES" -gt 0 ]; then
113
- echo "Found $SQL_FILES SQL object(s) — injecting SqlObjectHelper.ApplyAll()..."
114
-
115
- # Add using directive if not present
116
- if ! grep -q "using SmartStack.Infrastructure.Persistence.SqlObjects;" "$MIGRATION_FILE"; then
117
- sed -i '1s/^/using SmartStack.Infrastructure.Persistence.SqlObjects;\n/' "$MIGRATION_FILE"
118
- fi
119
-
120
- # Inject SqlObjectHelper.ApplyAll before Down() method
121
- sed -i '/protected override void Down/i\
122
- \ // Apply SQL objects (TVF, Views, SP) from embedded resources\
123
- \ SqlObjectHelper.ApplyAll(migrationBuilder);' "$MIGRATION_FILE"
124
-
125
- # VERIFY — FAIL if injection didn't work
126
- if ! grep -q "SqlObjectHelper.ApplyAll" "$MIGRATION_FILE"; then
127
- echo "ERROR: Auto-injection failed. Use Edit tool to manually add:"
128
- echo " 1. 'using SmartStack.Infrastructure.Persistence.SqlObjects;' at top"
129
- echo " 2. 'SqlObjectHelper.ApplyAll(migrationBuilder);' at end of Up()"
130
- echo " File: $MIGRATION_FILE"
131
- else
132
- echo " SqlObjectHelper.ApplyAll(migrationBuilder) injected"
133
- find "$SQL_OBJECTS_DIR" -name "*.sql" -exec basename {} \; | while read f; do echo " - $f"; done
134
- fi
135
- else
136
- echo "No SQL objects found — skipping injection"
137
- fi
102
+ # NOTE: SQL objects (TVFs, views, SPs) are applied automatically at startup
103
+ # via SqlObjectHelper.ApplyAllAsync() no injection needed in migrations.
138
104
 
139
105
  # Validate
140
106
  dotnet build && dotnet ef migrations script --idempotent > /dev/null
@@ -149,7 +115,6 @@ dotnet build && dotnet ef migrations script --idempotent > /dev/null
149
115
  - [ ] **Parent snapshot retrieved** (origin/$BASE_BRANCH)
150
116
  - [ ] **Parent migrations retrieved** (.cs AND .Designer.cs files)
151
117
  - [ ] Branch-specific migrations deleted (not inherited ones!)
152
- - [ ] **SQL Objects injected** (if `Persistence/SqlObjects/*.sql` exists)
153
118
  - [ ] Build OK after squash
154
119
  - [ ] Script can be generated
155
120
 
@@ -189,7 +154,7 @@ Next: /efcore db-reset, /efcore db-deploy
189
154
  | MCP unavailable | Check `.claude/mcp-status.json`, run `/mcp healthcheck` |
190
155
  | Snapshot fetch fails | Verify `origin/$BASE_BRANCH` exists: `git fetch origin $BASE_BRANCH` |
191
156
  | Script generation fails | Restore backup, then investigate migration content |
192
- | SQL objects injection fails | Manual step: add `SqlObjectHelper.ApplyAll(migrationBuilder)` in Up() |
157
+ | SQL objects missing at runtime | SQL objects are applied at startup — restart the application |
193
158
 
194
159
  **Automatic recovery:** On ANY failure after backup:
195
160
  1. Restore all files from backup directory
@@ -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
 
@@ -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,22 +49,63 @@ 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
 
107
+ **Skip if CACHE_HIT=true (values loaded from cache in Step 0).**
108
+
72
109
  Find the API and Web directories:
73
110
  - API: `src/*.Api` (first match)
74
111
  - Web: `web/*-web` (first match)
@@ -77,6 +114,8 @@ If neither found, display error and exit.
77
114
 
78
115
  ## Step 2: Detect environment and ports
79
116
 
117
+ **Skip if CACHE_HIT=true (values loaded from cache in Step 0).**
118
+
80
119
  Check if `appsettings.Local.json` exists in the API directory:
81
120
 
82
121
  | Condition | Mode | Backend | Frontend | Backend command | Frontend command |
@@ -92,6 +131,8 @@ Check if `appsettings.Local.json` exists in the API directory:
92
131
 
93
132
  ## Step 3: Validate config coherence
94
133
 
134
+ **Skip if CACHE_HIT=true (values loaded from cache in Step 0).**
135
+
95
136
  **CRITICAL: Verify that backend and frontend configs are consistent.**
96
137
 
97
138
  ### Rules to check:
@@ -142,6 +183,81 @@ Config Coherence Check:
142
183
  -> Creating .env.standalone with correct values...
143
184
  ```
144
185
 
186
+ ### Rule 5: SQL Server connectivity
187
+
188
+ 1. Extract connection string from appsettings:
189
+ - **Local mode**: `appsettings.Local.json` → `ConnectionStrings.DefaultConnection`
190
+ - **Dev mode**: `appsettings.json` → `ConnectionStrings.DefaultConnection`
191
+
192
+ 2. Parse server and database:
193
+ ```bash
194
+ SQL_SERVER=$(echo "$CONN_STR" | grep -oiP '(?:Server|Data Source)=\K[^;]+')
195
+ SQL_DATABASE=$(echo "$CONN_STR" | grep -oiP '(?:Database|Initial Catalog)=\K[^;]+')
196
+ USE_WIN_AUTH=$(echo "$CONN_STR" | grep -qi 'Integrated Security=true\|Trusted_Connection=true' && echo "true" || echo "false")
197
+ ```
198
+
199
+ 3. Test connectivity (5s timeout):
200
+ ```bash
201
+ # Normalize (local) → .
202
+ SQL_SERVER_NORM=$(echo "$SQL_SERVER" | sed 's/(local)/./')
203
+
204
+ if [ "$USE_WIN_AUTH" = "true" ]; then
205
+ sqlcmd -S "$SQL_SERVER_NORM" -d master -Q "SELECT 1" -t 5 -E -C -h -1 -W 2>/dev/null
206
+ else
207
+ SQL_USER=$(echo "$CONN_STR" | grep -oiP '(?:User Id|Uid)=\K[^;]+')
208
+ SQL_PASS=$(echo "$CONN_STR" | grep -oiP '(?:Password|Pwd)=\K[^;]+')
209
+ 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
210
+ fi
211
+ ```
212
+
213
+ 4. If reachable, check database exists:
214
+ ```bash
215
+ 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
216
+ ```
217
+
218
+ 5. Output format:
219
+ | Scenario | Output | Blocking? |
220
+ |----------|--------|-----------|
221
+ | Server OK + DB exists | `[OK] SQL Server (server) — Database 'db' exists` | No |
222
+ | Server OK + DB missing | `[WARN] Database 'db' not found (AutoMigrate will create it)` | No |
223
+ | Server unreachable | `[FAIL] SQL Server (server) unreachable — Is the service running?` | No |
224
+ | sqlcmd not installed | `[SKIP] sqlcmd not found — SQL check skipped` | No |
225
+ | No connection string | `[SKIP] No connection string — SQL check skipped` | No |
226
+
227
+ ## Step 3b: Write cache
228
+
229
+ **Only execute if CACHE_HIT=false (Steps 1-3 were executed).**
230
+
231
+ 1. Ensure directory exists: `mkdir -p .claude/config`
232
+
233
+ 2. Compute config hash:
234
+ ```bash
235
+ if [ "$MODE" = "Local" ]; then
236
+ 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)
237
+ else
238
+ 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)
239
+ fi
240
+ ```
241
+
242
+ 3. Write `.claude/config/dev-run.json` using the Write tool with all detected values:
243
+ ```json
244
+ {
245
+ "version": 1,
246
+ "api_dir": "{API_DIR}",
247
+ "web_dir": "{WEB_DIR}",
248
+ "mode": "{MODE}",
249
+ "api_port": {API_PORT},
250
+ "web_port": {WEB_PORT},
251
+ "backend_cmd": "{BACKEND_CMD}",
252
+ "frontend_cmd": "{FRONTEND_CMD}",
253
+ "sql_server": "{SQL_SERVER}",
254
+ "sql_database": "{SQL_DATABASE}",
255
+ "sql_status": "{ok|unreachable|db_missing|skipped}",
256
+ "config_hash": "{CONFIG_HASH}",
257
+ "cached_at": "{ISO_TIMESTAMP}"
258
+ }
259
+ ```
260
+
145
261
  ## Step 4: Check if already running
146
262
 
147
263
  ```bash
@@ -218,15 +334,17 @@ Use `Bash(run_in_background=true)` so it runs in background.
218
334
  SMARTSTACK DEV ENVIRONMENT
219
335
  ================================================================
220
336
 
221
- Backend: http://localhost:{API_PORT} [RUNNING|STARTING]
222
- Frontend: http://localhost:{WEB_PORT} [RUNNING|STARTING]
223
- Swagger: http://localhost:{API_PORT}/scalar
337
+ Backend: http://localhost:{API_PORT} [RUNNING|STARTING]
338
+ Frontend: http://localhost:{WEB_PORT} [RUNNING|STARTING]
339
+ Swagger: http://localhost:{API_PORT}/scalar
340
+ SQL Server: {SQL_SERVER}/{SQL_DATABASE} [OK|WARN|FAIL|SKIP]
224
341
 
225
- Email: local.admin@smartstack.local
226
- Password: xxxxxxxxxxxxxx
342
+ Email: local.admin@smartstack.local
343
+ Password: xxxxxxxxxxxxxx
227
344
 
228
345
  Environment: {Development|Local}
229
346
  Config: OK | WARNING (details)
347
+ Cache: HIT (from {cached_at}) | MISS (re-detected)
230
348
  ================================================================
231
349
  ```
232
350
 
@@ -240,6 +358,8 @@ Use `Bash(run_in_background=true)` so it runs in background.
240
358
  4. **Config coherence is critical** - Mismatched ports between backend/frontend is the #1 cause of "it doesn't work" issues
241
359
  5. **Idempotent** - Running twice should detect already-running processes and reuse stored credentials
242
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.
243
363
 
244
364
  </important_notes>
245
365
 
@@ -249,4 +369,6 @@ Use `Bash(run_in_background=true)` so it runs in background.
249
369
  - Admin credentials available (reused or freshly reset)
250
370
  - Connection info displayed in clear format
251
371
  - No errors on repeated invocations (idempotent)
372
+ - Cache written on first run, reused on subsequent runs
373
+ - SQL Server connectivity reported
252
374
  </success_criteria>