@atlashub/smartstack-cli 4.32.0 → 4.34.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/.documentation/index.html +2 -2
- package/.documentation/init.html +358 -174
- package/dist/index.js +45 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-entry.mjs +271 -44
- package/dist/mcp-entry.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/mcp-scaffolding/controller.cs.hbs +54 -128
- package/templates/project/README.md +19 -0
- package/templates/project/claude-md/api.CLAUDE.md.template +315 -0
- package/templates/project/claude-md/application.CLAUDE.md.template +181 -0
- package/templates/project/claude-md/domain.CLAUDE.md.template +125 -0
- package/templates/project/claude-md/infrastructure.CLAUDE.md.template +168 -0
- package/templates/project/claude-md/root.CLAUDE.md.template +339 -0
- package/templates/project/claude-md/web.CLAUDE.md.template +339 -0
- package/templates/skills/apex/SKILL.md +16 -10
- package/templates/skills/apex/_shared.md +1 -1
- package/templates/skills/apex/references/checks/architecture-checks.sh +154 -0
- package/templates/skills/apex/references/checks/backend-checks.sh +194 -0
- package/templates/skills/apex/references/checks/frontend-checks.sh +448 -0
- package/templates/skills/apex/references/checks/infrastructure-checks.sh +255 -0
- package/templates/skills/apex/references/checks/security-checks.sh +153 -0
- package/templates/skills/apex/references/checks/seed-checks.sh +536 -0
- package/templates/skills/apex/references/frontend-route-wiring-app-tsx.md +49 -192
- package/templates/skills/apex/references/post-checks.md +124 -2156
- package/templates/skills/apex/references/smartstack-api.md +160 -957
- package/templates/skills/apex/references/smartstack-frontend.md +134 -1022
- package/templates/skills/apex/references/smartstack-layers.md +12 -6
- package/templates/skills/apex/steps/step-00-init.md +81 -238
- package/templates/skills/apex/steps/step-03-execute.md +25 -752
- package/templates/skills/apex/steps/step-03a-layer0-domain.md +118 -0
- package/templates/skills/apex/steps/step-03b-layer1-seed.md +91 -0
- package/templates/skills/apex/steps/step-03c-layer2-backend.md +240 -0
- package/templates/skills/apex/steps/step-03d-layer3-frontend.md +300 -0
- package/templates/skills/apex/steps/step-03e-layer4-devdata.md +44 -0
- package/templates/skills/apex/steps/step-04-examine.md +70 -150
- package/templates/skills/application/references/frontend-i18n-and-output.md +2 -2
- package/templates/skills/application/references/frontend-route-naming.md +5 -1
- package/templates/skills/application/references/frontend-route-wiring-app-tsx.md +49 -198
- package/templates/skills/application/references/frontend-verification.md +11 -11
- package/templates/skills/application/steps/step-05-frontend.md +26 -15
- package/templates/skills/application/templates-frontend.md +4 -0
- package/templates/skills/cli-app-sync/SKILL.md +2 -2
- package/templates/skills/cli-app-sync/references/comparison-map.md +1 -1
- package/templates/skills/controller/references/controller-code-templates.md +70 -67
- package/templates/skills/controller/references/mcp-scaffold-workflow.md +5 -1
|
@@ -91,14 +91,19 @@ Execute incremental SmartStack development using the APEX methodology. This skil
|
|
|
91
91
|
</entry_point>
|
|
92
92
|
|
|
93
93
|
<step_files>
|
|
94
|
-
**Progressive loading
|
|
94
|
+
**Progressive loading — only load current step. Step 03 dispatches to layer sub-steps.**
|
|
95
95
|
|
|
96
96
|
| Step | File | Model | Purpose |
|
|
97
97
|
|------|------|-------|---------|
|
|
98
98
|
| 00 | `steps/step-00-init.md` | Sonnet | Parse flags, detect application, verify MCP, define hierarchy (4 levels), scope validation |
|
|
99
99
|
| 01 | `steps/step-01-analyze.md` | Opus | Explore existing code (parallel Agent tool or sequential) |
|
|
100
100
|
| 02 | `steps/step-02-plan.md` | Opus | Layer-by-layer plan with skill/MCP mapping |
|
|
101
|
-
| 03 | `steps/step-03-execute.md` | Opus |
|
|
101
|
+
| 03 | `steps/step-03-execute.md` | Opus | **Orchestrator** — dispatches to layer sub-steps below |
|
|
102
|
+
| 03a | `steps/step-03a-layer0-domain.md` | Opus | Layer 0: Domain entities, EF configs, migration |
|
|
103
|
+
| 03b | `steps/step-03b-layer1-seed.md` | Opus | Layer 1: Seed data (navigation, permissions, roles) |
|
|
104
|
+
| 03c | `steps/step-03c-layer2-backend.md` | Opus | Layer 2: Services, controllers, backend tests |
|
|
105
|
+
| 03d | `steps/step-03d-layer3-frontend.md` | Opus | Layer 3: Pages, i18n, routes, frontend tests |
|
|
106
|
+
| 03e | `steps/step-03e-layer4-devdata.md` | Opus | Layer 4: Development test data (optional) |
|
|
102
107
|
| 04 | `steps/step-04-examine.md` | Opus | eXamine: MCP validation, build, POST-CHECKs, acceptance criteria |
|
|
103
108
|
| 05 | `steps/step-05-deep-review.md` | Opus | Deep Review: adversarial code review (if -x) |
|
|
104
109
|
| 06 | `steps/step-06-resolve.md` | Opus | Fix BLOCKING findings (if any) |
|
|
@@ -126,15 +131,16 @@ Execute incremental SmartStack development using the APEX methodology. This skil
|
|
|
126
131
|
|
|
127
132
|
| File | Purpose | Loaded by | Stays in context for |
|
|
128
133
|
|------|---------|-----------|---------------------|
|
|
129
|
-
| `references/smartstack-api.md` | BaseEntity, interfaces, entity/config/controller patterns | step-01 | step-
|
|
130
|
-
| `references/smartstack-layers.md` | Layer rules, skill/MCP mapping, planning templates, delegate fast path | step-02 | step-
|
|
131
|
-
| `references/core-seed-data.md` | Comprehensive seed data templates (navigation, permissions, roles) | step-
|
|
132
|
-
| `references/smartstack-frontend.md` | Frontend patterns, EntityLookup, i18n (sections 1-6) | step-
|
|
133
|
-
| `references/smartstack-frontend-compliance.md` | Documentation, form testing, compliance gates (sections 7-9) | step-
|
|
134
|
+
| `references/smartstack-api.md` | BaseEntity, interfaces, entity/config/controller patterns | step-01 | step-03a/03b/03c (released after L2) |
|
|
135
|
+
| `references/smartstack-layers.md` | Layer rules, skill/MCP mapping, planning templates, delegate fast path | step-02 | step-03a/03b/03c (released after L2) |
|
|
136
|
+
| `references/core-seed-data.md` | Comprehensive seed data templates (navigation, permissions, roles) | step-03b (Layer 1) | released after Layer 1 |
|
|
137
|
+
| `references/smartstack-frontend.md` | Frontend patterns, EntityLookup, i18n (sections 1-6) | step-03d (Layer 3, deferred) | step-04 |
|
|
138
|
+
| `references/smartstack-frontend-compliance.md` | Documentation, form testing, compliance gates (sections 7-9) | step-03d (Layer 3, deferred) | step-04 |
|
|
134
139
|
| `references/challenge-questions.md` | Hierarchy rules, challenge questions, delegate mode skip | step-00 | — |
|
|
135
140
|
| `references/error-classification.md` | Build error diagnosis categories A-F | step-03 (build failure), step-04 | — |
|
|
136
|
-
| `references/parallel-execution.md` | Agent tool launch patterns, task coordination, decision matrix | step-01, step-
|
|
137
|
-
| `references/post-checks.md` |
|
|
141
|
+
| `references/parallel-execution.md` | Agent tool launch patterns, task coordination, decision matrix | step-01, step-03c/03d (if NOT economy_mode) | — |
|
|
142
|
+
| `references/post-checks.md` | Compact checklist — indexes checks in `references/checks/*.sh` | step-04 | — |
|
|
143
|
+
| `references/checks/*.sh` | Bash check scripts (security, backend, frontend, seed, architecture, infrastructure) | step-04 (executed via bash) | — |
|
|
138
144
|
|
|
139
145
|
**Context propagation rule:** Files loaded in step N remain in conversation context for step N+1. Steps mark "do NOT re-read" to avoid duplicate reads.
|
|
140
146
|
</reference_files>
|
|
@@ -148,7 +154,7 @@ Execute incremental SmartStack development using the APEX methodology. This skil
|
|
|
148
154
|
- **Layer order** - Layer 0 (domain+infra+migration) → Layer 1 (seed data) → Layer 2 (backend+tests) → Layer 3 (frontend+tests) → Layer 4 (devdata)
|
|
149
155
|
- **Parallel Agent tool** - Parallel execution for scan (step-01) and within Layer 2/3 (step-03) for multi-entity, unless economy_mode
|
|
150
156
|
- **Tests inline** - Backend tests run after Layer 2, frontend tests run after Layer 3 (max 3 fix iterations each). Step-07 = final sweep (security + coverage).
|
|
151
|
-
- **Exception: seed data** — The templates in core-seed-data.md and person-extension-pattern.md are generated directly because no MCP tool covers seed data creation. This is a documented exception to the "orchestrate, never generate" rule.
|
|
157
|
+
- **Exception: seed data** — The templates in core-seed-data.md and person-extension-pattern.md are generated directly because no MCP tool covers seed data creation yet. This is a documented exception to the "orchestrate, never generate" rule. <!-- TODO: Remove exception when MCP scaffold_seed_data (B1) is ready -->
|
|
152
158
|
- **Frontend pages: ALWAYS via Skill("ui-components")** — economy_mode affects parallelization only, NOT whether /ui-components is called. NEVER generate .tsx pages directly, even in delegate or economy mode.
|
|
153
159
|
- **Save outputs** if `{save_mode}` = true
|
|
154
160
|
- **Commits per layer** - Atomic commits after each execution layer
|
|
@@ -156,7 +156,7 @@ Write back to {delegate_prd_path}
|
|
|
156
156
|
| `scaffold_extension` | Create files manually following `smartstack-api.md` entity/service/controller patterns |
|
|
157
157
|
| `suggest_migration` | Name format: `{context}_v{version}_{sequence}_{Description}` (see existing migrations for version) |
|
|
158
158
|
| `generate_permissions` | Write `HasData()` code manually following `core-seed-data.md` permission section |
|
|
159
|
-
| `scaffold_routes` | Create `
|
|
159
|
+
| `scaffold_routes` | Create `componentRegistry.generated.ts` with `PageRegistry.register()` calls manually following `smartstack-frontend.md` §1 (or legacy `applicationRoutes` array for pre-v3.7 projects) |
|
|
160
160
|
| `validate_frontend_routes` | Run POST-CHECK bash scripts from `post-checks.md` |
|
|
161
161
|
| `validate_security` | Run security POST-CHECKs S1-S6 from `post-checks.md` |
|
|
162
162
|
| `check_migrations` | Run `dotnet ef migrations has-pending-model-changes` manually |
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# POST-CHECK: Architecture — Clean Architecture Layer Isolation
|
|
5
|
+
# A1-A8: Layer boundary enforcement, DTO usage, handoff compliance
|
|
6
|
+
|
|
7
|
+
FAIL=false
|
|
8
|
+
|
|
9
|
+
# POST-CHECK A1: Domain must not import other layers (BLOCKING)
|
|
10
|
+
DOMAIN_FILES=$(find src/ -path "*/Domain/*" -name "*.cs" 2>/dev/null)
|
|
11
|
+
if [ -n "$DOMAIN_FILES" ]; then
|
|
12
|
+
BAD_IMPORTS=$(grep -Pn 'using\s+[\w.]*\.(Application|Infrastructure|Api)[\w.]*;' $DOMAIN_FILES 2>/dev/null || true)
|
|
13
|
+
if [ -n "$BAD_IMPORTS" ]; then
|
|
14
|
+
echo "BLOCKING: Domain layer imports Application/Infrastructure/Api — violates Clean Architecture"
|
|
15
|
+
echo "Domain is the core, it must not depend on any other layer"
|
|
16
|
+
echo "$BAD_IMPORTS"
|
|
17
|
+
echo "Fix: Move shared types to Domain or remove the dependency"
|
|
18
|
+
FAIL=true
|
|
19
|
+
fi
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# POST-CHECK A2: Application must not import Infrastructure or Api (BLOCKING)
|
|
23
|
+
APP_FILES=$(find src/ -path "*/Application/*" -name "*.cs" 2>/dev/null)
|
|
24
|
+
if [ -n "$APP_FILES" ]; then
|
|
25
|
+
BAD_IMPORTS=$(grep -Pn 'using\s+[\w.]*\.(Infrastructure|Api)[\w.]*;' $APP_FILES 2>/dev/null || true)
|
|
26
|
+
if [ -n "$BAD_IMPORTS" ]; then
|
|
27
|
+
echo "BLOCKING: Application layer imports Infrastructure/Api — violates Clean Architecture"
|
|
28
|
+
echo "Application defines interfaces, Infrastructure implements them"
|
|
29
|
+
echo "$BAD_IMPORTS"
|
|
30
|
+
echo "Fix: Define an interface in Application and implement it in Infrastructure"
|
|
31
|
+
FAIL=true
|
|
32
|
+
fi
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# POST-CHECK A3: Controllers must not inject DbContext (BLOCKING)
|
|
36
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
37
|
+
if [ -n "$CTRL_FILES" ]; then
|
|
38
|
+
BAD_DBCONTEXT=$(grep -Pn 'private\s+readonly\s+\w*DbContext|DbContext\s+\w+[,)]' $CTRL_FILES 2>/dev/null || true)
|
|
39
|
+
if [ -n "$BAD_DBCONTEXT" ]; then
|
|
40
|
+
echo "BLOCKING: Controller injects DbContext directly — violates Clean Architecture"
|
|
41
|
+
echo "Controllers must use Application services, not access the database directly"
|
|
42
|
+
echo "$BAD_DBCONTEXT"
|
|
43
|
+
echo "Fix: Create an Application service with the required business logic and inject it instead"
|
|
44
|
+
FAIL=true
|
|
45
|
+
fi
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
# POST-CHECK A4: API must return DTOs, not Domain entities (WARNING)
|
|
49
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
50
|
+
ENTITY_FILES=$(find src/ -path "*/Domain/Entities/*" -name "*.cs" 2>/dev/null)
|
|
51
|
+
if [ -n "$CTRL_FILES" ] && [ -n "$ENTITY_FILES" ]; then
|
|
52
|
+
ENTITY_NAMES=$(grep -ohP 'public\s+class\s+(\w+)\s*:' $ENTITY_FILES 2>/dev/null | grep -oP '\w+(?=\s*:)' | grep -v '^public$' | sort -u || true)
|
|
53
|
+
for ENTITY in $ENTITY_NAMES; do
|
|
54
|
+
BAD_RETURN=$(grep -Pn "ActionResult<$ENTITY>|ActionResult<IEnumerable<$ENTITY>>|ActionResult<List<$ENTITY>>" $CTRL_FILES 2>/dev/null || true)
|
|
55
|
+
if [ -n "$BAD_RETURN" ]; then
|
|
56
|
+
echo "WARNING: Controller returns Domain entity '$ENTITY' instead of a DTO"
|
|
57
|
+
echo "$BAD_RETURN"
|
|
58
|
+
echo "Fix: Return ${ENTITY}ResponseDto instead of $ENTITY"
|
|
59
|
+
fi
|
|
60
|
+
done
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# POST-CHECK A5: Service interfaces in Application, implementations in Infrastructure (WARNING)
|
|
64
|
+
APP_SERVICES=$(find src/ -path "*/Application/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
65
|
+
if [ -n "$APP_SERVICES" ]; then
|
|
66
|
+
for f in $APP_SERVICES; do
|
|
67
|
+
if grep -q 'public class.*Service' "$f" 2>/dev/null; then
|
|
68
|
+
echo "WARNING: Service implementation found in Application layer: $f"
|
|
69
|
+
echo "Fix: Move implementation to Infrastructure/Services/. Application should only contain interfaces."
|
|
70
|
+
fi
|
|
71
|
+
done
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
DOMAIN_INTERFACES=$(find src/ -path "*/Domain/*" -name "I*Service.cs" 2>/dev/null)
|
|
75
|
+
API_INTERFACES=$(find src/ -path "*/Api/*" -name "I*Service.cs" 2>/dev/null)
|
|
76
|
+
for f in $DOMAIN_INTERFACES $API_INTERFACES; do
|
|
77
|
+
if [ -n "$f" ] && grep -q 'public interface.*Service' "$f" 2>/dev/null; then
|
|
78
|
+
echo "WARNING: Service interface found outside Application layer: $f"
|
|
79
|
+
echo "Fix: Move to Application/Interfaces/"
|
|
80
|
+
fi
|
|
81
|
+
done
|
|
82
|
+
|
|
83
|
+
# POST-CHECK A6: No EF Core attributes in Domain entities (BLOCKING)
|
|
84
|
+
DOMAIN_FILES=$(find src/ -path "*/Domain/*" -name "*.cs" 2>/dev/null)
|
|
85
|
+
if [ -n "$DOMAIN_FILES" ]; then
|
|
86
|
+
BAD_EF=$(grep -Pn '\[Table\(|\[Column\(|\[Index\(|using\s+Microsoft\.EntityFrameworkCore' $DOMAIN_FILES 2>/dev/null || true)
|
|
87
|
+
if [ -n "$BAD_EF" ]; then
|
|
88
|
+
echo "BLOCKING: EF Core attributes or using directives found in Domain layer"
|
|
89
|
+
echo "Domain entities must be persistence-ignorant — EF configuration belongs in Infrastructure"
|
|
90
|
+
echo "$BAD_EF"
|
|
91
|
+
echo "Fix: Move [Table], [Column], [Index] to IEntityTypeConfiguration<T> in Infrastructure/Persistence/Configurations/"
|
|
92
|
+
FAIL=true
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# POST-CHECK A7: No direct repository usage in controllers (WARNING)
|
|
97
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
98
|
+
if [ -n "$CTRL_FILES" ]; then
|
|
99
|
+
BAD_REPO=$(grep -Pn 'IRepository<|IGenericRepository<|private\s+readonly\s+IRepository|private\s+readonly\s+IGenericRepository' $CTRL_FILES 2>/dev/null || true)
|
|
100
|
+
if [ -n "$BAD_REPO" ]; then
|
|
101
|
+
echo "WARNING: Controller injects repository directly — should use Application services"
|
|
102
|
+
echo "$BAD_REPO"
|
|
103
|
+
echo "Fix: Controllers should depend on Application services (I*Service), not repositories"
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# POST-CHECK A8: API endpoints must match handoff apiEndpointSummary (BLOCKING)
|
|
108
|
+
PRD_FILE=".ralph/prd.json"
|
|
109
|
+
if [ ! -f "$PRD_FILE" ]; then
|
|
110
|
+
if [ -f ".ralph/modules-queue.json" ]; then
|
|
111
|
+
PRD_FILE=$(cat .ralph/modules-queue.json | grep -o '"prdFile":"[^"]*"' | tail -1 | cut -d'"' -f4)
|
|
112
|
+
fi
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
if [ -f "$PRD_FILE" ]; then
|
|
116
|
+
OPERATIONS=$(cat "$PRD_FILE" | grep -o '"operation"\s*:\s*"[^"]*"' | cut -d'"' -f4 2>/dev/null || true)
|
|
117
|
+
|
|
118
|
+
if [ -n "$OPERATIONS" ]; then
|
|
119
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
120
|
+
MISSING_OPS=""
|
|
121
|
+
TOTAL_OPS=0
|
|
122
|
+
FOUND_OPS=0
|
|
123
|
+
|
|
124
|
+
for op in $OPERATIONS; do
|
|
125
|
+
TOTAL_OPS=$((TOTAL_OPS + 1))
|
|
126
|
+
FOUND=false
|
|
127
|
+
if [ -n "$CTRL_FILES" ]; then
|
|
128
|
+
for f in $CTRL_FILES; do
|
|
129
|
+
if grep -q "$op" "$f" 2>/dev/null; then
|
|
130
|
+
FOUND=true
|
|
131
|
+
break
|
|
132
|
+
fi
|
|
133
|
+
done
|
|
134
|
+
fi
|
|
135
|
+
if [ "$FOUND" = true ]; then
|
|
136
|
+
FOUND_OPS=$((FOUND_OPS + 1))
|
|
137
|
+
else
|
|
138
|
+
MISSING_OPS="$MISSING_OPS $op"
|
|
139
|
+
fi
|
|
140
|
+
done
|
|
141
|
+
|
|
142
|
+
if [ -n "$MISSING_OPS" ]; then
|
|
143
|
+
echo "BLOCKING: API endpoints missing from controllers (handoff contract violation)"
|
|
144
|
+
echo "Found: $FOUND_OPS/$TOTAL_OPS operations"
|
|
145
|
+
echo "Missing operations:$MISSING_OPS"
|
|
146
|
+
echo "Fix: Implement missing endpoints in the appropriate Controller"
|
|
147
|
+
FAIL=true
|
|
148
|
+
fi
|
|
149
|
+
fi
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
if [ "$FAIL" = true ]; then
|
|
153
|
+
exit 1
|
|
154
|
+
fi
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# POST-CHECK: Backend — Entity, Service & Controller Checks
|
|
5
|
+
# V1-V2, C8, C12, C14, C28-C31, C54: Entity validation, API contracts, pagination, code generation
|
|
6
|
+
|
|
7
|
+
FAIL=false
|
|
8
|
+
|
|
9
|
+
# POST-CHECK V1: Controllers with POST/PUT must have corresponding Validators (BLOCKING)
|
|
10
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
11
|
+
if [ -n "$CTRL_FILES" ]; then
|
|
12
|
+
for f in $CTRL_FILES; do
|
|
13
|
+
HAS_WRITE=$(grep -cE "\[Http(Post|Put)\]" "$f")
|
|
14
|
+
if [ "$HAS_WRITE" -gt 0 ]; then
|
|
15
|
+
DTOS=$(grep -oP '(?:Create|Update)\w+Dto' "$f" | sort -u)
|
|
16
|
+
for DTO in $DTOS; do
|
|
17
|
+
VALIDATOR_NAME=$(echo "$DTO" | sed 's/Dto$/Validator/')
|
|
18
|
+
VALIDATOR_FILE=$(find src/ -path "*/Validators/*" -name "${VALIDATOR_NAME}.cs" 2>/dev/null)
|
|
19
|
+
if [ -z "$VALIDATOR_FILE" ]; then
|
|
20
|
+
echo "BLOCKING: Controller $f uses $DTO but no ${VALIDATOR_NAME}.cs found"
|
|
21
|
+
echo "Fix: Create Validator with FluentValidation rules from business rules"
|
|
22
|
+
FAIL=true
|
|
23
|
+
fi
|
|
24
|
+
done
|
|
25
|
+
fi
|
|
26
|
+
done
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# POST-CHECK V2: Validators must be registered in DI (WARNING)
|
|
30
|
+
VALIDATOR_FILES=$(find src/ -path "*/Validators/*" -name "*Validator.cs" 2>/dev/null)
|
|
31
|
+
if [ -n "$VALIDATOR_FILES" ]; then
|
|
32
|
+
DI_FILE=$(find src/ -name "DependencyInjection.cs" -o -name "ServiceCollectionExtensions.cs" 2>/dev/null | head -1)
|
|
33
|
+
if [ -n "$DI_FILE" ]; then
|
|
34
|
+
for f in $VALIDATOR_FILES; do
|
|
35
|
+
VALIDATOR_NAME=$(basename "$f" .cs)
|
|
36
|
+
if ! grep -q "$VALIDATOR_NAME" "$DI_FILE"; then
|
|
37
|
+
echo "WARNING: Validator $VALIDATOR_NAME not registered in DI: $DI_FILE"
|
|
38
|
+
fi
|
|
39
|
+
done
|
|
40
|
+
fi
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
# POST-CHECK C8: Backend APIs must support search parameter for EntityLookup (BLOCKING)
|
|
44
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
45
|
+
if [ -n "$CTRL_FILES" ]; then
|
|
46
|
+
for f in $CTRL_FILES; do
|
|
47
|
+
if grep -q "\[HttpGet\]" "$f" && grep -q "GetAll" "$f"; then
|
|
48
|
+
if ! grep -q "search" "$f"; then
|
|
49
|
+
echo "BLOCKING: Controller missing search parameter on GetAll: $f"
|
|
50
|
+
echo "GetAll endpoints must accept ?search= to enable EntityLookup on frontend"
|
|
51
|
+
echo "Fix: Add [FromQuery] string? search parameter to GetAll action"
|
|
52
|
+
FAIL=true
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
done
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
WEB_DIR=$(find . -name "vitest.config.ts" -o -name "vite.config.ts" 2>/dev/null | head -1 | xargs dirname 2>/dev/null)
|
|
59
|
+
if [ -n "$WEB_DIR" ]; then
|
|
60
|
+
LOOKUP_FILES=$(grep -rl "EntityLookup" "$WEB_DIR/src/pages/" "$WEB_DIR/src/components/" 2>/dev/null | grep -v node_modules | grep -v "\.test\." || true)
|
|
61
|
+
if [ -n "$LOOKUP_FILES" ]; then
|
|
62
|
+
for f in $LOOKUP_FILES; do
|
|
63
|
+
ENDPOINTS=$(grep -oP "apiEndpoint=['\"]([^'\"]+)['\"]" "$f" 2>/dev/null | grep -oP "['\"]([^'\"]+)['\"]" | tr -d "'" | tr -d '"' || true)
|
|
64
|
+
for ep in $ENDPOINTS; do
|
|
65
|
+
ENTITY=$(echo "$ep" | sed 's|.*/||' | sed 's/.*/\u&/')
|
|
66
|
+
CTRL=$(find src/ -path "*/Controllers/*${ENTITY}*Controller.cs" 2>/dev/null | head -1)
|
|
67
|
+
if [ -n "$CTRL" ] && ! grep -q "search" "$CTRL"; then
|
|
68
|
+
echo "BLOCKING: EntityLookup in $f calls $ep, but controller $CTRL does not support ?search="
|
|
69
|
+
echo "Fix: Add [FromQuery] string? search parameter to GetAll in $CTRL"
|
|
70
|
+
FAIL=true
|
|
71
|
+
fi
|
|
72
|
+
done
|
|
73
|
+
done
|
|
74
|
+
fi
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# POST-CHECK C12: GetAll methods must return PaginatedResult<T>
|
|
78
|
+
SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
79
|
+
if [ -n "$SERVICE_FILES" ]; then
|
|
80
|
+
BAD_RETURNS=$(grep -Pn '(Task<\s*(?:List|IEnumerable|IList|ICollection|IReadOnlyList|IReadOnlyCollection)<).*GetAll' $SERVICE_FILES 2>/dev/null)
|
|
81
|
+
if [ -n "$BAD_RETURNS" ]; then
|
|
82
|
+
echo "WARNING: GetAll methods must return PaginatedResult<T>, not List/IEnumerable"
|
|
83
|
+
echo "$BAD_RETURNS"
|
|
84
|
+
echo "Fix: Change return type to Task<PaginatedResult<{Entity}ResponseDto>>"
|
|
85
|
+
fi
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# POST-CHECK C14: Entities must implement IAuditableEntity + Validators must have Create/Update pairs
|
|
89
|
+
ENTITY_FILES=$(find src/ -path "*/Domain/Entities/*" -name "*.cs" 2>/dev/null)
|
|
90
|
+
if [ -n "$ENTITY_FILES" ]; then
|
|
91
|
+
for f in $ENTITY_FILES; do
|
|
92
|
+
if grep -q "ITenantEntity" "$f" && ! grep -q "IAuditableEntity" "$f"; then
|
|
93
|
+
echo "WARNING: Entity implements ITenantEntity but NOT IAuditableEntity: $f"
|
|
94
|
+
echo "Pattern: public class Entity : BaseEntity, ITenantEntity, IAuditableEntity"
|
|
95
|
+
fi
|
|
96
|
+
done
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
CREATE_VALIDATORS=$(find src/ -path "*/Validators/*" -name "Create*Validator.cs" 2>/dev/null)
|
|
100
|
+
if [ -n "$CREATE_VALIDATORS" ]; then
|
|
101
|
+
for f in $CREATE_VALIDATORS; do
|
|
102
|
+
VALIDATOR_DIR=$(dirname "$f")
|
|
103
|
+
ENTITY_NAME=$(basename "$f" | sed 's/^Create\(.*\)Validator\.cs$/\1/')
|
|
104
|
+
if [ ! -f "$VALIDATOR_DIR/Update${ENTITY_NAME}Validator.cs" ]; then
|
|
105
|
+
echo "WARNING: Create${ENTITY_NAME}Validator exists but Update${ENTITY_NAME}Validator is missing"
|
|
106
|
+
echo " Found: $f"
|
|
107
|
+
echo " Expected: $VALIDATOR_DIR/Update${ENTITY_NAME}Validator.cs"
|
|
108
|
+
fi
|
|
109
|
+
done
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# POST-CHECK C28: Pagination type must be PaginatedResult<T> — no aliases (WARNING)
|
|
113
|
+
SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
114
|
+
CTRL_FILES=$(find src/ -path "*/Controllers/*" -name "*Controller.cs" 2>/dev/null)
|
|
115
|
+
ALL_FILES="$SERVICE_FILES $CTRL_FILES"
|
|
116
|
+
if [ -n "$(echo $ALL_FILES | tr -d ' ')" ]; then
|
|
117
|
+
BAD_NAMES=$(grep -Pn 'PagedResult<|PaginatedResultDto<|PaginatedResponse<|PageResultDto<' $ALL_FILES 2>/dev/null || true)
|
|
118
|
+
if [ -n "$BAD_NAMES" ]; then
|
|
119
|
+
echo "WARNING: Pagination type must be PaginatedResult<T> — found non-canonical names"
|
|
120
|
+
echo "$BAD_NAMES"
|
|
121
|
+
echo "FORBIDDEN type names: PagedResult, PaginatedResultDto, PaginatedResponse, PageResultDto"
|
|
122
|
+
echo "Fix: Use PaginatedResult<T> from SmartStack.Application.Common.Models everywhere"
|
|
123
|
+
fi
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
# POST-CHECK C29: Code generation — ICodeGenerator must be registered for auto-generated entities (BLOCKING)
|
|
127
|
+
FEATURE_FILES=$(find docs/ -name "feature.json" 2>/dev/null)
|
|
128
|
+
DI_FILE=$(find src/ -name "DependencyInjection.cs" -path "*/Infrastructure/*" 2>/dev/null | head -1)
|
|
129
|
+
if [ -n "$FEATURE_FILES" ] && [ -n "$DI_FILE" ]; then
|
|
130
|
+
for FEATURE in $FEATURE_FILES; do
|
|
131
|
+
ENTITIES_WITH_CODE=$(python3 -c "
|
|
132
|
+
import json, sys
|
|
133
|
+
try:
|
|
134
|
+
with open('$FEATURE') as f:
|
|
135
|
+
data = json.load(f)
|
|
136
|
+
for e in data.get('analysis', {}).get('entities', []):
|
|
137
|
+
cp = e.get('codePattern', {})
|
|
138
|
+
if cp.get('strategy', 'manual') != 'manual':
|
|
139
|
+
print(e['name'])
|
|
140
|
+
except: pass
|
|
141
|
+
" 2>/dev/null || true)
|
|
142
|
+
for ENTITY in $ENTITIES_WITH_CODE; do
|
|
143
|
+
if ! grep -q "ICodeGenerator<$ENTITY>" "$DI_FILE" 2>/dev/null; then
|
|
144
|
+
echo "BLOCKING: Entity $ENTITY has auto-generated code pattern but ICodeGenerator<$ENTITY> is not registered in DI"
|
|
145
|
+
echo "Fix: Add CodeGenerator<$ENTITY> registration in DependencyInjection.cs — see references/code-generation.md"
|
|
146
|
+
FAIL=true
|
|
147
|
+
fi
|
|
148
|
+
done
|
|
149
|
+
done
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# POST-CHECK C30: Code regex must support hyphens (BLOCKING)
|
|
153
|
+
VALIDATOR_FILES=$(find src/ -path "*/Validators/*" -name "*Validator.cs" 2>/dev/null)
|
|
154
|
+
if [ -n "$VALIDATOR_FILES" ]; then
|
|
155
|
+
OLD_REGEX=$(grep -rn '\^\\[a-z0-9_\\]+\$' $VALIDATOR_FILES 2>/dev/null | grep -v '\-' || true)
|
|
156
|
+
if [ -n "$OLD_REGEX" ]; then
|
|
157
|
+
echo "BLOCKING: Code validator uses old regex without hyphen support"
|
|
158
|
+
echo "$OLD_REGEX"
|
|
159
|
+
echo "Fix: Update regex to ^[a-z0-9_-]+$ to support auto-generated codes with hyphens"
|
|
160
|
+
FAIL=true
|
|
161
|
+
fi
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# POST-CHECK C31: CreateDto must NOT have Code field when service uses ICodeGenerator (WARNING)
|
|
165
|
+
SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
166
|
+
if [ -n "$SERVICE_FILES" ]; then
|
|
167
|
+
for f in $SERVICE_FILES; do
|
|
168
|
+
if grep -q "ICodeGenerator" "$f"; then
|
|
169
|
+
ENTITY=$(basename "$f" | sed 's/Service\.cs$//')
|
|
170
|
+
DTO_FILE=$(find src/ -path "*/DTOs/*" -name "Create${ENTITY}Dto.cs" 2>/dev/null | head -1)
|
|
171
|
+
if [ -n "$DTO_FILE" ] && grep -q "public string Code" "$DTO_FILE"; then
|
|
172
|
+
echo "WARNING: Create${ENTITY}Dto has Code field but service uses ICodeGenerator (code is auto-generated)"
|
|
173
|
+
echo "Fix: Remove Code from Create${ENTITY}Dto — it is auto-generated by ICodeGenerator<${ENTITY}>"
|
|
174
|
+
fi
|
|
175
|
+
fi
|
|
176
|
+
done
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
# POST-CHECK C54: No helper method calls inside .Select() on IQueryable (BLOCKING)
|
|
180
|
+
SERVICE_FILES=$(find src/ -path "*/Services/*" -name "*Service.cs" ! -name "I*Service.cs" 2>/dev/null)
|
|
181
|
+
if [ -n "$SERVICE_FILES" ]; then
|
|
182
|
+
BAD_SELECT=$(grep -Pn '\.Select\(\s*\w+\s*=>\s*(?!new\s)[A-Z]\w+\(|\.Select\(\s*(?!x\s*=>)[A-Z]\w+\s*\)' $SERVICE_FILES 2>/dev/null || true)
|
|
183
|
+
if [ -n "$BAD_SELECT" ]; then
|
|
184
|
+
echo "BLOCKING: Helper method call inside .Select() on IQueryable — EF Core cannot translate to SQL"
|
|
185
|
+
echo "$BAD_SELECT"
|
|
186
|
+
echo "Fix: Use inline DTO construction: .Select(x => new ResponseDto(x.Id, x.Name, x.FK.Code))"
|
|
187
|
+
echo " Helper methods (MapToDto, ToDto) are only safe AFTER materialization (ToListAsync, FirstAsync)"
|
|
188
|
+
FAIL=true
|
|
189
|
+
fi
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
if [ "$FAIL" = true ]; then
|
|
193
|
+
exit 1
|
|
194
|
+
fi
|