@atlashub/smartstack-cli 1.30.0 → 1.32.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": "1.30.0",
3
+ "version": "1.32.0",
4
4
  "description": "SmartStack Claude Code automation toolkit - GitFlow, APEX, EF Core migrations, prompts and more",
5
5
  "author": {
6
6
  "name": "SmartStack",
@@ -77,6 +77,7 @@
77
77
  "cli-table3": "^0.6.3",
78
78
  "commander": "^12.0.0",
79
79
  "fs-extra": "^11.2.0",
80
+ "glob": "^11.0.0",
80
81
  "inquirer": "^9.2.12",
81
82
  "jsonwebtoken": "^9.0.3",
82
83
  "mssql": "^11.0.1",
@@ -0,0 +1,31 @@
1
+ import axios from 'axios';
2
+
3
+ const api = axios.create({
4
+ baseURL: '/api',
5
+ headers: {
6
+ 'Content-Type': 'application/json',
7
+ },
8
+ });
9
+
10
+ // Request interceptor for auth token
11
+ api.interceptors.request.use((config) => {
12
+ const token = localStorage.getItem('token');
13
+ if (token) {
14
+ config.headers.Authorization = `Bearer ${token}`;
15
+ }
16
+ return config;
17
+ });
18
+
19
+ // Response interceptor for error handling
20
+ api.interceptors.response.use(
21
+ (response) => response,
22
+ (error) => {
23
+ if (error.response?.status === 401) {
24
+ localStorage.removeItem('token');
25
+ window.location.href = '/login';
26
+ }
27
+ return Promise.reject(error);
28
+ }
29
+ );
30
+
31
+ export default api;
@@ -0,0 +1,153 @@
1
+ {
2
+ "ConnectionStrings": {
3
+ "DefaultConnection": "Server=(local);Database={{ProjectName}};Integrated Security=true;TrustServerCertificate=true;Connection Timeout=60;Pooling=true;Min Pool Size=0;Max Pool Size=50;Load Balance Timeout=30"
4
+ },
5
+ "SmartStack": {
6
+ "AutoMigrate": true,
7
+ "FailOnMigrationError": true,
8
+ "EnableDevSeeding": false
9
+ },
10
+ "Jwt": {
11
+ "Secret": "{{GenerateRandomSecret}}",
12
+ "Issuer": "{{ProjectName}}",
13
+ "Audience": "{{ProjectName}}",
14
+ "AccessTokenExpirationMinutes": 60,
15
+ "RefreshTokenExpirationDays": 7
16
+ },
17
+ "Authentication": {
18
+ "Microsoft": {
19
+ "ClientId": "",
20
+ "ClientSecret": "",
21
+ "CallbackPath": "/api/auth/microsoft/callback"
22
+ },
23
+ "Google": {
24
+ "ClientId": "",
25
+ "ClientSecret": "",
26
+ "CallbackPath": "/api/auth/google/callback"
27
+ },
28
+ "FrontendUrl": "http://localhost:5173"
29
+ },
30
+ "Session": {
31
+ "IdleTimeoutMinutes": 20,
32
+ "AbsoluteTimeoutHours": 8,
33
+ "WarningThresholdMinutes": 5,
34
+ "EnableValidation": true,
35
+ "RefreshTokenExpirationDays": 7,
36
+ "CleanupBatchSize": 100,
37
+ "MaxConcurrentSessions": 1
38
+ },
39
+ "Serilog": {
40
+ "MinimumLevel": {
41
+ "Default": "Information",
42
+ "Override": {
43
+ "Microsoft": "Warning",
44
+ "Microsoft.EntityFrameworkCore": "Warning",
45
+ "Microsoft.AspNetCore": "Warning",
46
+ "System": "Warning"
47
+ }
48
+ },
49
+ "Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"],
50
+ "Properties": {
51
+ "Application": "{{ProjectName}}"
52
+ }
53
+ },
54
+ "Logging": {
55
+ "Sinks": {
56
+ "Console": {
57
+ "Enabled": true
58
+ },
59
+ "File": {
60
+ "Enabled": true,
61
+ "Path": "logs/{{ProjectNameLower}}-.log",
62
+ "RollingInterval": "Day",
63
+ "RetainedFileCountLimit": 21
64
+ },
65
+ "Seq": {
66
+ "Enabled": false,
67
+ "ServerUrl": "http://localhost:5341"
68
+ },
69
+ "ApplicationInsights": {
70
+ "Enabled": false,
71
+ "ConnectionString": ""
72
+ }
73
+ }
74
+ },
75
+ "AzureStorage": {
76
+ "UseAzure": false,
77
+ "Normal": {
78
+ "ConnectionString": "",
79
+ "ContainerName": "files",
80
+ "MaxFileSizeMB": 50,
81
+ "RetentionDays": 0,
82
+ "AllowedExtensions": []
83
+ },
84
+ "Legal": {
85
+ "ConnectionString": "",
86
+ "ContainerName": "legal-files",
87
+ "MaxFileSizeMB": 50,
88
+ "RetentionDays": 3653,
89
+ "EnableLegalHold": true,
90
+ "EnableRetentionPolicy": true,
91
+ "AllowedExtensions": []
92
+ }
93
+ },
94
+ "FileStorage": {
95
+ "BasePath": "d:\\{{ProjectName}}Files",
96
+ "Normal": {
97
+ "FolderName": "normal",
98
+ "MaxFileSizeMB": 50,
99
+ "RetentionDays": 0,
100
+ "AllowedExtensions": []
101
+ },
102
+ "Legal": {
103
+ "FolderName": "legal",
104
+ "MaxFileSizeMB": 50,
105
+ "RetentionDays": 3653,
106
+ "AllowedExtensions": []
107
+ }
108
+ },
109
+ "Email": {
110
+ "Enabled": true,
111
+ "Provider": "Development",
112
+ "FromEmail": "noreply@{{ProjectDomain}}",
113
+ "FromName": "{{ProjectName}}",
114
+ "BaseUrl": "http://localhost:5173",
115
+ "TokenExpiration": {
116
+ "EmailConfirmation": "24:00:00",
117
+ "PasswordReset": "01:00:00"
118
+ },
119
+ "Development": {
120
+ "SmtpHost": "localhost",
121
+ "SmtpPort": 1025,
122
+ "UseSsl": false,
123
+ "WebInterfaceUrl": "http://localhost:8025"
124
+ },
125
+ "Mailgun": {
126
+ "ApiKey": "",
127
+ "Domain": "",
128
+ "Region": "EU"
129
+ },
130
+ "AzureAcs": {
131
+ "ConnectionString": "",
132
+ "SenderAddress": ""
133
+ }
134
+ },
135
+ "Entra": {
136
+ "TenantId": "",
137
+ "ClientId": "",
138
+ "ClientSecret": "",
139
+ "SyncEnabled": false,
140
+ "SyncIntervalMinutes": 60,
141
+ "UseDeltaSync": true,
142
+ "DefaultConflictResolution": "ManualReview",
143
+ "AutoCreateReferences": true
144
+ },
145
+ "MultiTenant": {
146
+ "Enabled": true,
147
+ "EnableB2C": true,
148
+ "SystemTenantSlug": "default",
149
+ "SystemTenantName": "Default Workspace",
150
+ "AutoAssignUsersToSystemTenant": true
151
+ },
152
+ "AllowedHosts": "*"
153
+ }
@@ -28,6 +28,42 @@ Parameter limits:
28
28
  - Maximum 3 parameters
29
29
  - Avoid flag arguments (split into separate methods)
30
30
  - Use objects for related parameters
31
+
32
+ **Parameter Object Pattern (for 4+ parameters):**
33
+
34
+ ```csharp
35
+ // ❌ BAD - Too many parameters (S107)
36
+ public void CreateUser(
37
+ string name,
38
+ string email,
39
+ string phone,
40
+ string address,
41
+ bool isActive,
42
+ Guid tenantId)
43
+ {
44
+ // ...
45
+ }
46
+
47
+ // ✅ GOOD - Use parameter object
48
+ public record CreateUserRequest(
49
+ string Name,
50
+ string Email,
51
+ string Phone,
52
+ string Address,
53
+ bool IsActive,
54
+ Guid TenantId);
55
+
56
+ public void CreateUser(CreateUserRequest request)
57
+ {
58
+ // Access via request.Name, request.Email, etc.
59
+ }
60
+ ```
61
+
62
+ **Benefits:**
63
+ - Easier to extend (add new parameters without changing signature)
64
+ - Self-documenting (grouped related data)
65
+ - Enables validation logic on the object itself
66
+ - Reduces method signature complexity
31
67
  </parameters>
32
68
 
33
69
  <naming>
@@ -37,6 +73,45 @@ Function naming patterns:
37
73
  - Reveals intent without reading body
38
74
  - No abbreviations: `getTransaction()` not `getTx()`
39
75
  </naming>
76
+
77
+ <string_comparison>
78
+ **C# String Comparison Best Practices:**
79
+
80
+ ALWAYS use `StringComparison` overloads to avoid culture-specific issues (CA1873).
81
+
82
+ ```csharp
83
+ // ❌ BAD - Culture-dependent, performance issue
84
+ if (str1.ToLower() == str2.ToLower()) { ... }
85
+ if (str1.ToUpper() == str2.ToUpper()) { ... }
86
+
87
+ // ✅ GOOD - Explicit comparison
88
+ if (string.Equals(str1, str2, StringComparison.OrdinalIgnoreCase)) { ... }
89
+ if (str1.Equals(str2, StringComparison.OrdinalIgnoreCase)) { ... }
90
+
91
+ // ❌ BAD - Culture-dependent Contains
92
+ if (str.ToLower().Contains("keyword")) { ... }
93
+
94
+ // ✅ GOOD - Explicit comparison (C# 5.0+)
95
+ if (str.Contains("keyword", StringComparison.OrdinalIgnoreCase)) { ... }
96
+
97
+ // ❌ BAD - Culture-dependent StartsWith/EndsWith
98
+ if (str.ToLower().StartsWith("prefix")) { ... }
99
+
100
+ // ✅ GOOD - Explicit comparison
101
+ if (str.StartsWith("prefix", StringComparison.OrdinalIgnoreCase)) { ... }
102
+ ```
103
+
104
+ **StringComparison options:**
105
+ - `Ordinal` - Binary comparison (fastest, case-sensitive)
106
+ - `OrdinalIgnoreCase` - Binary comparison (case-insensitive)
107
+ - `CurrentCulture` - Culture-aware (rarely needed)
108
+ - `InvariantCulture` - Culture-invariant (for data storage)
109
+
110
+ **Rule of thumb:**
111
+ - Use `Ordinal` for identifiers, file paths, keys
112
+ - Use `OrdinalIgnoreCase` for user input comparison
113
+ - Avoid `ToLower()`/`ToUpper()` for comparison
114
+ </string_comparison>
40
115
  </function_guidelines>
41
116
 
42
117
  <naming_conventions>
@@ -73,7 +148,84 @@ Suggest these fixes:
73
148
  | **Magic Numbers** | Unexplained literals | Named constants |
74
149
  | **Dead Code** | Commented-out or unreachable code | Delete it |
75
150
  | **Shotgun Surgery** | Small change touches many files | Consolidate related code |
151
+ | **Nested Ternary** | `? ( ? : ) :` patterns | Extract to function/if-else |
152
+ | **TODO/FIXME** | Untracked technical debt | Create ticket or fix immediately |
76
153
  </medium_smells>
154
+
155
+ <nested_ternary_detection>
156
+ **Nested Ternary Operators (S3358):**
157
+
158
+ Nested ternaries are hard to read and maintain.
159
+
160
+ ```typescript
161
+ // ❌ BAD - Nested ternary
162
+ const status = isActive
163
+ ? (isPremium ? 'premium-active' : 'standard-active')
164
+ : 'inactive';
165
+
166
+ const color = type === 'error'
167
+ ? 'red'
168
+ : type === 'warning' ? 'yellow' : 'blue';
169
+
170
+ // ✅ GOOD - Early returns in function
171
+ function getStatus(isActive: boolean, isPremium: boolean): string {
172
+ if (!isActive) return 'inactive';
173
+ if (isPremium) return 'premium-active';
174
+ return 'standard-active';
175
+ }
176
+
177
+ // ✅ GOOD - If-else chain
178
+ let color: string;
179
+ if (type === 'error') {
180
+ color = 'red';
181
+ } else if (type === 'warning') {
182
+ color = 'yellow';
183
+ } else {
184
+ color = 'blue';
185
+ }
186
+
187
+ // ✅ GOOD - Map/lookup for simple cases
188
+ const colorMap: Record<string, string> = {
189
+ error: 'red',
190
+ warning: 'yellow',
191
+ info: 'blue'
192
+ };
193
+ const color = colorMap[type] ?? 'blue';
194
+ ```
195
+
196
+ **Detection pattern:** Look for `? ... ? ... : ... :` in code.
197
+ </nested_ternary_detection>
198
+
199
+ <todo_fixme_tracking>
200
+ **TODO/FIXME Comments (S1135):**
201
+
202
+ Untracked TODOs become technical debt. Either fix immediately or create a tracked ticket.
203
+
204
+ ```typescript
205
+ // ❌ BAD - Untracked TODO
206
+ // TODO: handle edge case
207
+
208
+ // ❌ BAD - Vague FIXME
209
+ // FIXME: this is broken
210
+
211
+ // ✅ ACCEPTABLE - Linked to ticket
212
+ // TODO(#1234): Implement retry logic for transient failures
213
+
214
+ // ✅ ACCEPTABLE - With deadline
215
+ // FIXME(2026-02-15): Remove legacy API call after migration
216
+
217
+ // ✅ BEST - No comment, just fix it
218
+ // (Actually implement the fix instead of leaving a comment)
219
+ ```
220
+
221
+ **Code review action:**
222
+ 1. Search for `TODO`, `FIXME`, `HACK`, `XXX`, `REFACTOR` comments
223
+ 2. For each:
224
+ - Is it linked to a ticket? If not, create one or fix now
225
+ - Is it still relevant? If not, delete
226
+ - Does it have context? If not, add details
227
+ 3. Report untracked TODOs as `[SUGGESTION]` in review
228
+ </todo_fixme_tracking>
77
229
  </code_smells>
78
230
 
79
231
  <complexity_reduction>
@@ -154,12 +154,36 @@ builder.ToTable("auth_users", SchemaConstants.Core);
154
154
  ```csharp
155
155
  [ApiController]
156
156
  [NavRoute("platform.administration.users")]
157
- public class UsersController : ControllerBase
157
+ public class UsersController : ControllerBase // MUST be ControllerBase, NOT Controller
158
158
  {
159
159
  // Routes generated from NavRoute: /api/platform/administration/users
160
160
  }
161
161
  ```
162
162
 
163
+ **CRITICAL: Controller Inheritance (S6934)**
164
+
165
+ ```csharp
166
+ // ❌ BAD - Inheriting from Controller
167
+ public class UsersController : Controller
168
+ {
169
+ // Brings in MVC View support (Razor, ViewData, ViewBag)
170
+ // Unnecessary for API controllers
171
+ }
172
+
173
+ // ✅ GOOD - Inheriting from ControllerBase
174
+ public class UsersController : ControllerBase
175
+ {
176
+ // Lightweight, API-only base class
177
+ // No MVC View support (cleaner, more appropriate)
178
+ }
179
+ ```
180
+
181
+ **Why ControllerBase?**
182
+ - API controllers don't need MVC View support
183
+ - `Controller` adds unnecessary dependencies (Razor, ViewData, etc.)
184
+ - `ControllerBase` is lighter and more focused on APIs
185
+ - Better performance and smaller footprint
186
+
163
187
  **NavRoute format:**
164
188
  - Lowercase only
165
189
  - Dot-separated hierarchy
@@ -318,6 +318,175 @@ const STATUS_CONFIG = {
318
318
  )}
319
319
  ```
320
320
 
321
+ ## TYPESCRIPT INTERFACES (MANDATORY)
322
+
323
+ **ALWAYS define explicit TypeScript interfaces for component props.**
324
+
325
+ ```typescript
326
+ // ✅ CORRECT - Explicit interface with all props typed
327
+ interface UserCardProps {
328
+ user: User;
329
+ onSelect?: (user: User) => void;
330
+ isSelected?: boolean;
331
+ variant?: 'default' | 'compact';
332
+ }
333
+
334
+ export const UserCard: React.FC<UserCardProps> = ({
335
+ user,
336
+ onSelect,
337
+ isSelected = false,
338
+ variant = 'default'
339
+ }) => {
340
+ // ...
341
+ };
342
+
343
+ // ❌ WRONG - Missing interface, inline types, or any
344
+ export const UserCard = ({ user, onSelect }: any) => { ... }
345
+ export const UserCard = (props: { user: any }) => { ... }
346
+ ```
347
+
348
+ ### State Typing
349
+ ```typescript
350
+ // ✅ CORRECT - Explicit state types
351
+ const [users, setUsers] = useState<User[]>([]);
352
+ const [selectedId, setSelectedId] = useState<string | null>(null);
353
+ const [status, setStatus] = useState<'idle' | 'loading' | 'error'>('idle');
354
+
355
+ // ❌ WRONG - Implicit or missing types
356
+ const [users, setUsers] = useState([]);
357
+ const [data, setData] = useState();
358
+ ```
359
+
360
+ ## SEMANTIC HTML (ACCESSIBILITY)
361
+
362
+ **ALWAYS use semantic HTML elements instead of divs with roles.**
363
+
364
+ ```tsx
365
+ // ✅ CORRECT - Native button element
366
+ <button type="button" onClick={handleClick} disabled={isDisabled}>
367
+ Click me
368
+ </button>
369
+
370
+ // ❌ WRONG - div with role (S6819)
371
+ <div role="button" onClick={handleClick} tabIndex={0}>
372
+ Click me
373
+ </div>
374
+ ```
375
+
376
+ ### Interactive Elements Mapping
377
+
378
+ | Use Case | Correct Element | Wrong Pattern |
379
+ |----------|----------------|---------------|
380
+ | Clickable action | `<button type="button">` | `<div role="button">` |
381
+ | Navigation link | `<a href="...">` or `<Link>` | `<div onClick={navigate}>` |
382
+ | Form submission | `<button type="submit">` | `<div onClick={submit}>` |
383
+ | Expandable section | `<details><summary>` | `<div onClick={toggle}>` |
384
+ | List of items | `<ul><li>` with `<button>` inside | `<div role="listitem">` |
385
+
386
+ ### Clickable Cards Pattern
387
+ ```tsx
388
+ // ✅ CORRECT - Card with button inside
389
+ <div className="rounded-[var(--radius-card)] border ...">
390
+ <div className="p-4">
391
+ <h3>{title}</h3>
392
+ <p>{description}</p>
393
+ </div>
394
+ <button
395
+ type="button"
396
+ onClick={() => onSelect(item)}
397
+ className="w-full px-4 py-2 border-t ..."
398
+ >
399
+ Select
400
+ </button>
401
+ </div>
402
+
403
+ // ✅ CORRECT - Entire card as link
404
+ <Link to={`/items/${item.id}`} className="block rounded-[var(--radius-card)] ...">
405
+ <div className="p-4">...</div>
406
+ </Link>
407
+
408
+ // ❌ WRONG - div with click handler
409
+ <div onClick={() => onSelect(item)} className="cursor-pointer ...">
410
+ ...
411
+ </div>
412
+ ```
413
+
414
+ ## PERFORMANCE OPTIMIZATION
415
+
416
+ ### React.memo for Pure Components
417
+ ```tsx
418
+ import { memo, useCallback, useMemo } from 'react';
419
+
420
+ // ✅ CORRECT - Memoized component for lists
421
+ export const UserCard = memo(({ user, onSelect }: UserCardProps) => {
422
+ return (
423
+ <div className="...">
424
+ <h3>{user.name}</h3>
425
+ <button onClick={() => onSelect(user)}>Select</button>
426
+ </div>
427
+ );
428
+ });
429
+ UserCard.displayName = 'UserCard';
430
+ ```
431
+
432
+ ### useCallback for Event Handlers
433
+ ```tsx
434
+ // ✅ CORRECT - Stable callback reference
435
+ const handleSelect = useCallback((item: Item) => {
436
+ onSelect?.(item);
437
+ setSelectedId(item.id);
438
+ }, [onSelect]);
439
+
440
+ // ❌ WRONG - New function on every render
441
+ const handleSelect = (item: Item) => {
442
+ onSelect?.(item);
443
+ };
444
+ ```
445
+
446
+ ### useMemo for Expensive Computations
447
+ ```tsx
448
+ // ✅ CORRECT - Memoized filtered list
449
+ const filteredItems = useMemo(() =>
450
+ items.filter(item => item.status === filter),
451
+ [items, filter]
452
+ );
453
+
454
+ // ❌ WRONG - Recomputed on every render
455
+ const filteredItems = items.filter(item => item.status === filter);
456
+ ```
457
+
458
+ ## LIST RENDERING (UNIQUE KEYS)
459
+
460
+ **NEVER use array index as React key. ALWAYS use unique identifiers.**
461
+
462
+ ```tsx
463
+ // ✅ CORRECT - Unique ID from data
464
+ {items.map(item => (
465
+ <EntityCard key={item.id} {...mapToCardProps(item)} />
466
+ ))}
467
+
468
+ // ✅ CORRECT - Composite key when needed
469
+ {items.map(item => (
470
+ <Row key={`${item.tenantId}-${item.id}`} data={item} />
471
+ ))}
472
+
473
+ // ❌ WRONG - Array index as key (S6479)
474
+ {items.map((item, index) => (
475
+ <EntityCard key={index} {...mapToCardProps(item)} />
476
+ ))}
477
+
478
+ // ❌ WRONG - Random key
479
+ {items.map(item => (
480
+ <EntityCard key={Math.random()} {...mapToCardProps(item)} />
481
+ ))}
482
+ ```
483
+
484
+ ### Why Index Keys Are Problematic
485
+ - Causes unnecessary re-renders when list order changes
486
+ - Breaks component state (inputs, animations)
487
+ - Causes visual glitches with drag-and-drop
488
+ - Makes reconciliation inefficient
489
+
321
490
  ## ABSOLUTE RULES
322
491
 
323
492
  | DO | DON'T |
@@ -330,3 +499,8 @@ const STATUS_CONFIG = {
330
499
  | Responsive grid 1→2→3→4 | Fixed non-responsive grid |
331
500
  | h-full + flex-1 + mt-auto | Unaligned buttons in grid |
332
501
  | Empty and loading states | Native HTML tooltip |
502
+ | Explicit TypeScript interfaces | `any` or implicit types |
503
+ | `<button>` for click actions | `<div role="button">` |
504
+ | `memo()` for list item components | Unmemoized components in loops |
505
+ | `useCallback` for event handlers | Inline arrow functions in JSX |
506
+ | Unique `id` for list keys | Array index as key |