@atlashub/smartstack-cli 1.29.0 → 1.31.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/index.js +18 -87
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/project/api.ts.template +31 -0
- package/templates/skills/review-code/references/clean-code-principles.md +152 -0
- package/templates/skills/review-code/references/smartstack-conventions.md +25 -1
- package/templates/skills/ui-components/SKILL.md +174 -0
package/package.json
CHANGED
|
@@ -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;
|
|
@@ -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 |
|