agent-workflow-kit-cli 1.3.2 → 1.3.3
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/cli/commands/add.js +3 -1
- package/dist/cli/commands/init.js +6 -0
- package/dist/core/analyzer.js +70 -0
- package/dist/core/detector.js +22 -0
- package/package.json +1 -1
- package/templates/common/AGENTS.md.hbs +4 -0
- package/templates/common/GLOBAL_RULES.md +101 -0
- package/templates/dotnet/AGENTS.md.hbs +34 -34
- package/templates/dotnet/rules/api-structure.md +15 -15
- package/templates/dotnet/rules/csharp-style.md +17 -17
- package/templates/dotnet/rules/dependency-injection.md +12 -12
- package/templates/dotnet/rules/error-handling-validation.md +15 -15
- package/templates/dotnet/skills/dotnet-controller/SKILL.md +16 -16
- package/templates/express/AGENTS.md.hbs +33 -33
- package/templates/express/rules/error-handling.md +18 -18
- package/templates/express/rules/express-style.md +19 -19
- package/templates/express/rules/router-controller.md +16 -16
- package/templates/express/skills/express-endpoint/SKILL.md +14 -14
- package/templates/golang/AGENTS.md.hbs +36 -0
- package/templates/golang/rules/concurrency.md +71 -0
- package/templates/golang/rules/error-handling.md +42 -0
- package/templates/golang/rules/golang-style.md +24 -0
- package/templates/golang/rules/project-layout.md +39 -0
- package/templates/nestjs/AGENTS.md.hbs +29 -29
- package/templates/nestjs/rules/module-architecture.md +14 -14
- package/templates/nestjs/rules/nestjs-style.md +12 -12
- package/templates/nestjs/rules/validation-errors.md +15 -15
- package/templates/nestjs/skills/nestjs-module/SKILL.md +15 -15
- package/templates/next-js/AGENTS.md.hbs +32 -32
- package/templates/next-js/rules/data-fetching-mutations.md +17 -17
- package/templates/next-js/rules/next-style.md +17 -17
- package/templates/next-js/rules/seo-metadata.md +18 -18
- package/templates/next-js/rules/server-client-components.md +17 -17
- package/templates/next-js/skills/next-feature/SKILL.md +16 -16
- package/templates/rust/AGENTS.md.hbs +34 -0
- package/templates/rust/rules/error-handling.md +36 -0
- package/templates/rust/rules/memory-concurrency.md +47 -0
- package/templates/rust/rules/project-layout.md +49 -0
- package/templates/rust/rules/rust-style.md +26 -0
package/dist/cli/commands/add.js
CHANGED
|
@@ -11,7 +11,7 @@ import { analyzeModule } from "../../core/analyzer.js";
|
|
|
11
11
|
import { updateGitignore } from "./init.js";
|
|
12
12
|
export async function runAdd(stack, options) {
|
|
13
13
|
const targetStack = stack.toLowerCase();
|
|
14
|
-
const validStacks = ["spring-boot", "react-ts", "next-js", "nestjs", "express", "fastapi", "python-ai", "dotnet"];
|
|
14
|
+
const validStacks = ["spring-boot", "react-ts", "next-js", "nestjs", "express", "fastapi", "python-ai", "dotnet", "golang", "rust"];
|
|
15
15
|
if (!validStacks.includes(targetStack)) {
|
|
16
16
|
console.error(chalk.red(`Error: Invalid stack '${stack}'. Supported stacks are: ${validStacks.join(", ")}`));
|
|
17
17
|
process.exit(1);
|
|
@@ -40,7 +40,9 @@ export async function runAdd(stack, options) {
|
|
|
40
40
|
// 3. Write or Update AGENTS.md and/or GEMINI.md in the target directory
|
|
41
41
|
const geminiPath = path.join(targetDir, "GEMINI.md");
|
|
42
42
|
const agentsPath = path.join(targetDir, "AGENTS.md");
|
|
43
|
+
const globalRules = await readStaticTemplateFile("common/GLOBAL_RULES.md").catch(() => "");
|
|
43
44
|
const moduleAgentsContent = await renderTemplate("common/AGENTS.md.hbs", {
|
|
45
|
+
globalRules,
|
|
44
46
|
stackContent,
|
|
45
47
|
});
|
|
46
48
|
if (options.agent === "antigravity" || options.agent === "both") {
|
|
@@ -183,7 +183,9 @@ export async function runInit(options) {
|
|
|
183
183
|
}
|
|
184
184
|
if (modules.length === 0) {
|
|
185
185
|
console.log(chalk.yellow("No standard stacks detected automatically. Creating general agent guidelines at root."));
|
|
186
|
+
const globalRules = await readStaticTemplateFile("common/GLOBAL_RULES.md").catch(() => "");
|
|
186
187
|
const finalAgentsContent = await renderTemplate("common/AGENTS.md.hbs", {
|
|
188
|
+
globalRules,
|
|
187
189
|
stackContent: "",
|
|
188
190
|
});
|
|
189
191
|
if (options.agent === "antigravity" || options.agent === "both") {
|
|
@@ -219,7 +221,9 @@ export async function runInit(options) {
|
|
|
219
221
|
const targetFileName = isAntigravity ? "GEMINI.md" : "AGENTS.md";
|
|
220
222
|
monorepoContent += `- **${mod.name}** (${mod.stacks.join(", ")}): Stack rules and guidelines are located at [${mod.name}/${targetFileName}](file:///${mod.dir.replace(/\\/g, "/")}/${targetFileName})\n`;
|
|
221
223
|
}
|
|
224
|
+
const globalRules = await readStaticTemplateFile("common/GLOBAL_RULES.md").catch(() => "");
|
|
222
225
|
const rootAgentsContent = await renderTemplate("common/AGENTS.md.hbs", {
|
|
226
|
+
globalRules,
|
|
223
227
|
stackContent: monorepoContent.trim(),
|
|
224
228
|
});
|
|
225
229
|
const rootGeminiPath = path.join(cwd, "GEMINI.md");
|
|
@@ -276,7 +280,9 @@ export async function runInit(options) {
|
|
|
276
280
|
// Render agent files for this module
|
|
277
281
|
const geminiPath = path.join(mod.dir, "GEMINI.md");
|
|
278
282
|
const agentsPath = path.join(mod.dir, "AGENTS.md");
|
|
283
|
+
const globalRules = await readStaticTemplateFile("common/GLOBAL_RULES.md").catch(() => "");
|
|
279
284
|
const moduleAgentsContent = await renderTemplate("common/AGENTS.md.hbs", {
|
|
285
|
+
globalRules,
|
|
280
286
|
stackContent,
|
|
281
287
|
});
|
|
282
288
|
if (options.agent === "antigravity" || options.agent === "both") {
|
package/dist/core/analyzer.js
CHANGED
|
@@ -519,6 +519,12 @@ export async function analyzeModule(dir, stacks) {
|
|
|
519
519
|
else if (stack === "dotnet") {
|
|
520
520
|
context["dotnet"] = await analyzeDotnet(dir);
|
|
521
521
|
}
|
|
522
|
+
else if (stack === "golang") {
|
|
523
|
+
context["golang"] = await analyzeGolang(dir);
|
|
524
|
+
}
|
|
525
|
+
else if (stack === "rust") {
|
|
526
|
+
context["rust"] = await analyzeRust(dir);
|
|
527
|
+
}
|
|
522
528
|
}
|
|
523
529
|
return context;
|
|
524
530
|
}
|
|
@@ -605,3 +611,67 @@ async function findCsprojFiles(dir) {
|
|
|
605
611
|
await traverse(dir);
|
|
606
612
|
return files;
|
|
607
613
|
}
|
|
614
|
+
async function analyzeGolang(dir) {
|
|
615
|
+
let goVersion = "1.22";
|
|
616
|
+
let moduleName = path.basename(dir);
|
|
617
|
+
try {
|
|
618
|
+
const goModPath = path.join(dir, "go.mod");
|
|
619
|
+
const content = await fs.readFile(goModPath, "utf8");
|
|
620
|
+
const lines = content.split("\n");
|
|
621
|
+
for (const line of lines) {
|
|
622
|
+
const trimmed = line.trim();
|
|
623
|
+
if (trimmed.startsWith("module ")) {
|
|
624
|
+
moduleName = trimmed.replace("module ", "").trim();
|
|
625
|
+
}
|
|
626
|
+
else if (trimmed.startsWith("go ")) {
|
|
627
|
+
goVersion = trimmed.replace("go ", "").trim();
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
catch {
|
|
632
|
+
// Keep default
|
|
633
|
+
}
|
|
634
|
+
return { goVersion, moduleName };
|
|
635
|
+
}
|
|
636
|
+
async function analyzeRust(dir) {
|
|
637
|
+
let projectName = path.basename(dir);
|
|
638
|
+
let rustEdition = "2021";
|
|
639
|
+
let isWorkspace = false;
|
|
640
|
+
try {
|
|
641
|
+
const cargoTomlPath = path.join(dir, "Cargo.toml");
|
|
642
|
+
const content = await fs.readFile(cargoTomlPath, "utf8");
|
|
643
|
+
if (content.includes("[workspace]")) {
|
|
644
|
+
isWorkspace = true;
|
|
645
|
+
}
|
|
646
|
+
// Very basic parsing for name and edition
|
|
647
|
+
const lines = content.split("\n");
|
|
648
|
+
let inPackageSection = false;
|
|
649
|
+
for (const line of lines) {
|
|
650
|
+
const trimmed = line.trim();
|
|
651
|
+
if (trimmed.startsWith("[package]")) {
|
|
652
|
+
inPackageSection = true;
|
|
653
|
+
}
|
|
654
|
+
else if (trimmed.startsWith("[")) {
|
|
655
|
+
inPackageSection = false;
|
|
656
|
+
}
|
|
657
|
+
if (inPackageSection) {
|
|
658
|
+
if (trimmed.startsWith("name")) {
|
|
659
|
+
const match = trimmed.match(/name\s*=\s*"(.*)"/);
|
|
660
|
+
if (match && match[1]) {
|
|
661
|
+
projectName = match[1];
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
else if (trimmed.startsWith("edition")) {
|
|
665
|
+
const match = trimmed.match(/edition\s*=\s*"(.*)"/);
|
|
666
|
+
if (match && match[1]) {
|
|
667
|
+
rustEdition = match[1];
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
catch {
|
|
674
|
+
// Keep default
|
|
675
|
+
}
|
|
676
|
+
return { projectName, rustEdition, isWorkspace };
|
|
677
|
+
}
|
package/dist/core/detector.js
CHANGED
|
@@ -130,6 +130,28 @@ async function detectProjectStackDirect(cwd) {
|
|
|
130
130
|
catch {
|
|
131
131
|
// Ignore
|
|
132
132
|
}
|
|
133
|
+
// 5. Detect Golang (go.mod)
|
|
134
|
+
try {
|
|
135
|
+
const goModPath = path.join(cwd, "go.mod");
|
|
136
|
+
const stat = await fs.stat(goModPath);
|
|
137
|
+
if (stat.isFile()) {
|
|
138
|
+
stacks.push("golang");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Ignore
|
|
143
|
+
}
|
|
144
|
+
// 6. Detect Rust (Cargo.toml)
|
|
145
|
+
try {
|
|
146
|
+
const cargoTomlPath = path.join(cwd, "Cargo.toml");
|
|
147
|
+
const stat = await fs.stat(cargoTomlPath);
|
|
148
|
+
if (stat.isFile()) {
|
|
149
|
+
stacks.push("rust");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Ignore
|
|
154
|
+
}
|
|
133
155
|
return stacks;
|
|
134
156
|
}
|
|
135
157
|
/**
|
package/package.json
CHANGED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# UNIVERSAL GLOBAL RULES (GLOBAL_RULES.md)
|
|
2
|
+
|
|
3
|
+
NOTICE TO AI AGENT: All source code generated in this project – regardless of language, framework, or architecture (Frontend, Backend, AI Agent) – must strictly adhere to the following 4 pillars of engineering rules. Any violations will result in code rejection.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🏛️ PILLAR 1: DATABASE & MIGRATION GOVERNANCE
|
|
8
|
+
The main goal is to protect data integrity, prevent data loss in Production environments, and optimize core query performance.
|
|
9
|
+
|
|
10
|
+
### 1.1 Strict No Auto-Sync in Production
|
|
11
|
+
- **Rule:** Never use properties or commands that automatically synchronize schema changes directly to the database in a Production environment (e.g., `synchronize: true` in TypeORM/Hibernate, `prisma db push`, `sequelize.sync()`, or GORM's `db.AutoMigrate()` running automatically at startup).
|
|
12
|
+
- **Mandatory Solution:** All structural changes (adding columns, modifying types, dropping tables, creating indices) must be written in independent, timestamped migration files (e.g., `YYYYMMDDHHMMSS_migration_name`).
|
|
13
|
+
- **Workflow:** Migration files must be verified locally and executed only via the ORM/Driver CLI tool prior to production deployment.
|
|
14
|
+
|
|
15
|
+
### 1.2 Strict Indexing Strategy
|
|
16
|
+
- **Prerequisite:** Any column frequently appearing in `WHERE` clauses, `JOIN` conditions, or `ORDER BY` statements in large-scale growing tables (e.g., `user_id`, `status`, `created_at`) must be indexed.
|
|
17
|
+
- **Write Prevention:** Avoid indexing columns indiscriminately. For columns with high-frequency updates (`INSERT`/`UPDATE`/`DELETE`), redundant indices will severely degrade write performance.
|
|
18
|
+
- **Unique Constraints:** Identity fields requiring uniqueness (e.g., `email`, `username`, `phone_number`) must be configured with a `UNIQUE` index at the Database level, not just validated in the Application code.
|
|
19
|
+
|
|
20
|
+
### 1.3 Eliminate N+1 Query Issues
|
|
21
|
+
- **Issue:** Never query child relations inside loops (`for`, `while`, `forEach`) for a list of records (e.g., fetching 100 courses and looping 100 times to execute SQL to get the instructor for each course).
|
|
22
|
+
- **Solution:** Use Eager Loading (direct SQL `JOIN`s, or pre-fetching methods like `.Include()` in EF Core, `Preload` in GORM, `select_related` / `prefetch_related` in Python) to retrieve all relational data in a single query.
|
|
23
|
+
|
|
24
|
+
### 1.4 Connection Pool Management
|
|
25
|
+
- All database connections must go through a Connection Pool. The AI Agent must not manually open/close connections per request.
|
|
26
|
+
- You must configure explicit limits: `max_connections` (maximum connections), `idle_timeout` (unused connection release time), and `max_lifetime` to prevent database server resource exhaustion.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 🛡️ PILLAR 2: SECURITY & AUTH HARDENING
|
|
31
|
+
Ensure the system is immune to vulnerabilities listed in the OWASP Top 10.
|
|
32
|
+
|
|
33
|
+
### 2.1 Zero Hardcoded Secrets
|
|
34
|
+
- **Strict Prohibition:** Never write database connection strings, third-party API Keys (e.g., OpenAI, Stripe), JWT secrets, or system passwords directly into the codebase.
|
|
35
|
+
- **Implementation:** All sensitive values must be loaded into the application via Environment Variables or System Env.
|
|
36
|
+
- **Documentation:** Every project must include a `.env.example` file listing all required environment keys with empty values, guiding others on how to configure the project.
|
|
37
|
+
|
|
38
|
+
### 2.2 JWT Lifecycle & Storage Standards (JSON Web Token)
|
|
39
|
+
- **Access Token:** Configure a short lifespan (minimum 15 minutes, maximum 1 hour) to reduce the window of vulnerability if the token is compromised.
|
|
40
|
+
- **Refresh Token:** Must have a longer expiration (7 days to 30 days) and must be stored in an `HttpOnly`, `Secure`, `SameSite=Strict` cookie on the client side. Storing Refresh Tokens in `localStorage` or `sessionStorage` is strictly prohibited to prevent account takeover via XSS.
|
|
41
|
+
|
|
42
|
+
### 2.3 Input Validation & Injection Defense
|
|
43
|
+
- **Mass Assignment Defense (Overposting):** Never map client request objects (e.g., `req.body`) directly to database update statements without filtering. Data must be validated and filtered using DTOs (Data Transfer Objects), Pydantic Schemas, or Struct validations.
|
|
44
|
+
- **SQL Injection Defense:** All database queries must use Parameterized Queries (Prepared Statements). String concatenation to construct dynamic SQL/NoSQL queries is strictly prohibited.
|
|
45
|
+
|
|
46
|
+
### 2.4 CORS & Rate Limiting
|
|
47
|
+
- **CORS:** Using `Origin: '*'` is strictly prohibited in Production. A whitelist of permitted frontend domains must be explicitly defined.
|
|
48
|
+
- **Rate Limiting:** Sensitive or resource-intensive endpoints (e.g., Login, Register, Forgot Password, Send OTP, LLM AI generation APIs) must be protected by rate-limiting middleware (e.g., maximum 5 requests/minute/IP) to defend against DoS and brute-force attacks.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 🚦 PILLAR 3: OBSERVABILITY & STRUCTURED LOGS
|
|
53
|
+
Transition the system from blind debugging to proactive monitoring using structured log data.
|
|
54
|
+
|
|
55
|
+
### 3.1 Structured Logging Standards
|
|
56
|
+
- Printing raw text directly to the console (e.g., `console.log()`, `fmt.Println()`, `print()`, or `println!()`) is prohibited in Production.
|
|
57
|
+
- **Mandatory Format:** All logs written to stdout/stderr must use JSON format so that Log Aggregators (ELK Stack, Grafana Loki, Datadog) can automatically parse and index them.
|
|
58
|
+
- **Required fields in every JSON log line:**
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"timestamp": "2026-06-13T16:00:00.000Z",
|
|
62
|
+
"level": "ERROR",
|
|
63
|
+
"trace_id": "c4b37d89-9821-47d3-b12a-7e61a4b9812f",
|
|
64
|
+
"context": "PaymentService",
|
|
65
|
+
"message": "Failed to connect to Stripe payment gateway",
|
|
66
|
+
"error_details": {
|
|
67
|
+
"code": "STRIPE_TIMEOUT",
|
|
68
|
+
"stack": "Error: Timeout after 5000ms at StripeClient.request..."
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3.2 Strict Log Levels
|
|
74
|
+
The AI Agent must select the appropriate log level for every event:
|
|
75
|
+
- **DEBUG:** Detailed execution flow tracing during development (must be automatically disabled or hidden in Production).
|
|
76
|
+
- **INFO:** Normal but important system events (e.g., Server started on port 8080, cache cleanup completed).
|
|
77
|
+
- **WARN:** Abnormal situations that do not fail the request (e.g., user entered incorrect password, third-party API responded slowly but succeeded).
|
|
78
|
+
- **ERROR:** Severe errors that terminate request execution and require developer intervention (e.g., database connection loss, invoice file write failure, logic errors).
|
|
79
|
+
|
|
80
|
+
### 3.3 Masking PII (Personally Identifiable Information)
|
|
81
|
+
- Never print sensitive PII into public log systems.
|
|
82
|
+
- Fields containing passwords, credit card numbers, national IDs/passports, and access/refresh tokens must be masked (e.g., converted to `*`) or omitted from the logged objects.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 📦 PILLAR 4: CONTAINER & CI/CD GUARDRAILS
|
|
87
|
+
Ensure applications can be deployed automatically onto Cloud environments (Kubernetes, AWS, Docker Swarm) efficiently and securely.
|
|
88
|
+
|
|
89
|
+
### 4.1 Mandatory Multi-Stage Builds
|
|
90
|
+
Every Dockerfile must consist of at least 2 stages:
|
|
91
|
+
- **Stage 1 (Build Stage):** Use a base image containing all necessary tools (SDKs, Compilers, devDependencies) to compile the source code and install packages.
|
|
92
|
+
- **Stage 2 (Production Runtime Stage):** Copy only compiled assets and production dependencies (e.g., `dist` folder, compiled binary, node production modules) from Stage 1. This final image must not contain raw source code or development utilities, minimizing bundle size.
|
|
93
|
+
|
|
94
|
+
### 4.2 Principle of Least Privilege
|
|
95
|
+
- **No Root User:** Never run container processes using the default `root` user. Running containers as root introduces severe security risks in case of container escape vulnerabilities.
|
|
96
|
+
- **Solution:** Initialize a non-privileged user in the Dockerfile (e.g., `USER node` in Node.js, or `useradd -u 1001 appuser` in Go/Rust) and grant appropriate execution permissions to this user.
|
|
97
|
+
|
|
98
|
+
### 4.3 Health Check API
|
|
99
|
+
- All backend services must expose a dedicated health check endpoint (e.g., `GET /health` or `GET /ready`).
|
|
100
|
+
- This endpoint must check downstream connectivity (e.g., Database, Redis) and return HTTP Status `503 Service Unavailable` if unhealthy.
|
|
101
|
+
- Use the `HEALTHCHECK` directive in the Dockerfile so orchestrators like Kubernetes can detect failures and automatically restart unhealthy containers (Self-healing).
|
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
## 🗺️
|
|
1
|
+
## 🗺️ .NET (C#) Development Guide
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
- **
|
|
5
|
-
-
|
|
6
|
-
- **
|
|
3
|
+
This project is a .NET (C#) application configured as follows:
|
|
4
|
+
- **Solution Name:** `{{solutionName}}`
|
|
5
|
+
- **.NET Version:** `net{{dotnetVersion}}`
|
|
6
|
+
- **Application Projects:**
|
|
7
7
|
{{#each projectNames}}
|
|
8
8
|
- `{{this}}`
|
|
9
9
|
{{/each}}
|
|
10
|
-
- **
|
|
10
|
+
- **Test Projects:**
|
|
11
11
|
{{#each testProjects}}
|
|
12
12
|
- `{{this}}`
|
|
13
13
|
{{/each}}
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
### 🔄
|
|
18
|
-
|
|
19
|
-
1. **
|
|
20
|
-
2. **
|
|
21
|
-
3. **
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
4. **
|
|
17
|
+
### 🔄 Agent Development Lifecycle
|
|
18
|
+
The AI Agent must execute all .NET tasks following this 5-step workflow:
|
|
19
|
+
1. **Design & Implementation:** Implement business logic adhering to Clean Architecture / Onion Architecture principles. Maintain Core Logic in decoupled Application/Domain layers.
|
|
20
|
+
2. **Comprehensive Testing:** Write unit tests for Services using xUnit and Moq.
|
|
21
|
+
3. **Code Quality & Verification:** Run build and test execution commands:
|
|
22
|
+
- Build Check: `dotnet build`
|
|
23
|
+
- Test Execution: `dotnet test`
|
|
24
|
+
4. **Package Management:** Add new NuGet dependencies using the command: `dotnet add package [package_name]`.
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
-
### 🏗️
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
28
|
+
### 🏗️ Template Blueprint
|
|
29
|
+
Refer to the detailed rules below:
|
|
30
|
+
- C# coding style, PascalCase, camelCase, and Interface prefixes: `@csharp-style.md`
|
|
31
|
+
- Dependency Injection lifetimes (Transient, Scoped, Singleton) registration: `@dependency-injection.md`
|
|
32
|
+
- API Controller responsibilities and execution dispatch: `@api-structure.md`
|
|
33
|
+
- FluentValidation setups and centralized exception handling middleware: `@error-handling-validation.md`
|
|
34
|
+
- Scaffolding a new API Controller, DTO, and Request Validator: `@SKILL.md`
|
|
35
35
|
|
|
36
36
|
---
|
|
37
37
|
|
|
38
|
-
### 🏛️
|
|
38
|
+
### 🏛️ Strict Development Rules
|
|
39
39
|
|
|
40
|
-
#### 1.
|
|
41
|
-
|
|
42
|
-
- **Domain:**
|
|
43
|
-
- **Application:**
|
|
44
|
-
- **Infrastructure:**
|
|
45
|
-
- **Presentation (API):**
|
|
40
|
+
#### 1. Clean Architecture Layers
|
|
41
|
+
Strictly separate concerns across application boundaries:
|
|
42
|
+
- **Domain:** Houses Entities, Value Objects, and core Domain Logic. Must remain free of external package references.
|
|
43
|
+
- **Application:** Houses Interfaces, DTOs, and Use Cases (Services/Queries/Commands). Depends only on the Domain layer.
|
|
44
|
+
- **Infrastructure:** Houses Data Access (EF Core DbContext) and External Integrations. Depends on the Application layer.
|
|
45
|
+
- **Presentation (API):** Houses Controllers, Filters, and Middlewares. The entrypoint of the execution runtime.
|
|
46
46
|
|
|
47
|
-
#### 2.
|
|
48
|
-
-
|
|
49
|
-
-
|
|
47
|
+
#### 2. Dependency Injection (DI) Registration
|
|
48
|
+
- Inject all downstream services using constructor parameters via interfaces.
|
|
49
|
+
- Never use the Service Locator pattern (e.g., manually resolving dependencies at runtime using `IServiceProvider`).
|
|
50
50
|
|
|
51
|
-
#### 3.
|
|
52
|
-
-
|
|
53
|
-
-
|
|
51
|
+
#### 3. Validation with FluentValidation
|
|
52
|
+
- Validate all incoming request DTOs using validators inheriting from FluentValidation's `AbstractValidator<T>`.
|
|
53
|
+
- Do not write manual validation statements scattered across Controller methods.
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Presentation Layer Structure (API Controllers)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This document defines the structure and responsibilities of API Controllers inside the Presentation layer of a .NET application.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## 🏛️
|
|
8
|
-
API Controller
|
|
9
|
-
1.
|
|
10
|
-
2.
|
|
11
|
-
3.
|
|
12
|
-
4.
|
|
7
|
+
## 🏛️ API Controller Responsibilities
|
|
8
|
+
An API Controller is responsible for:
|
|
9
|
+
1. Receiving incoming HTTP requests from clients.
|
|
10
|
+
2. Mapping URL parameters, query strings, or request bodies into target request DTOs.
|
|
11
|
+
3. Delegating request processing to the Application layer (Services/Queries/Commands).
|
|
12
|
+
4. Receiving results and responding to the client using appropriate HTTP status codes (e.g., `200 OK`, `201 Created`, `400 Bad Request`, `404 Not Found`).
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
## 🚦
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
- **
|
|
16
|
+
## 🚦 Development Rules
|
|
17
|
+
- **Thin Controllers:** Controllers must not contain business logic, algorithmic calculations, or direct database queries. 100% of this logic must reside in the Application layer.
|
|
18
|
+
- **Do Not Inject DbContext into Controllers:** Keep `DbContext` encapsulated within the Infrastructure / Repository layers. The Presentation layer must never access `DbContext` directly.
|
|
19
|
+
- **Use DTOs Instead of Domain Entities:**
|
|
20
|
+
- Never accept raw Domain Entities as Controller input parameters.
|
|
21
|
+
- Never return raw Domain Entities directly to the client to prevent exposing the database schema or triggering cyclic reference errors. Always map records to DTO (Data Transfer Object) classes.
|
|
22
|
+
- **Explicit Route Declarations:** Use attribute routing to clearly declare HTTP methods and endpoints:
|
|
23
23
|
```csharp
|
|
24
24
|
[ApiController]
|
|
25
25
|
[Route("api/v1/[controller]")]
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# C# Coding Style & Conventions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This document specifies standard coding styles, naming patterns, and source file organization conventions in .NET projects.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## 🏷️
|
|
7
|
+
## 🏷️ Naming Conventions
|
|
8
8
|
|
|
9
|
-
###
|
|
10
|
-
- **PascalCase:**
|
|
11
|
-
- *
|
|
12
|
-
- **Interface Prefix:**
|
|
13
|
-
- *
|
|
14
|
-
- **camelCase:**
|
|
15
|
-
- *
|
|
16
|
-
- **Private Fields Prefix:**
|
|
17
|
-
- *
|
|
9
|
+
### Class & Component Naming Rules
|
|
10
|
+
- **PascalCase:** Use for Classes, Structs, Records, Enums, Interfaces, Methods, and Public Properties.
|
|
11
|
+
- *Examples:* `UserService`, `GetActiveUsers()`, `CreatedDate`.
|
|
12
|
+
- **Interface Prefix:** Interface names must start with the uppercase letter `I`.
|
|
13
|
+
- *Examples:* `IUserService`, `IUserRepository`.
|
|
14
|
+
- **camelCase:** Use for method arguments and local variables.
|
|
15
|
+
- *Examples:* `userId`, `requestPayload`.
|
|
16
|
+
- **Private Fields Prefix:** Private fields inside classes must use `camelCase` and start with an underscore `_`.
|
|
17
|
+
- *Example:* `private readonly IUserRepository _userRepository;`.
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
-
## 📦
|
|
22
|
-
- **Namespaces:**
|
|
23
|
-
- *
|
|
24
|
-
- **File
|
|
21
|
+
## 📦 File Structure & Formatting
|
|
22
|
+
- **Namespaces:** Match namespaces with the physical directory structure to ensure easy discovery.
|
|
23
|
+
- *Example:* `ProjectName.Application.Services`.
|
|
24
|
+
- **File-Scoped Namespaces:** Use C# 10+ file-scoped namespaces to reduce unnecessary indentation levels:
|
|
25
25
|
```csharp
|
|
26
26
|
namespace ProjectName.Application.Services;
|
|
27
27
|
|
|
@@ -30,4 +30,4 @@ Tài liệu này đặc tả quy chuẩn coding style, đặt tên biến, tên
|
|
|
30
30
|
// ...
|
|
31
31
|
}
|
|
32
32
|
```
|
|
33
|
-
- **
|
|
33
|
+
- **Sorting Usings:** Organize `using` directives by placing system namespaces (`System.*`) first, followed by third-party packages, and internal project dependencies last.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Dependency Injection (DI) Configurations
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This document details guidelines for configuring and consuming dependency injection in .NET Core applications.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## 💉 Constructor Injection
|
|
8
|
-
- **
|
|
9
|
-
-
|
|
7
|
+
## 💉 Constructor Injection
|
|
8
|
+
- **Mandatory Rule:** Always resolve class dependencies using constructor parameters. Assign them to private readonly fields prefixed with an underscore `_`.
|
|
9
|
+
- Never use the Service Locator pattern (do not call `app.Services.GetService<T>()` or inject `IServiceProvider` to dynamically resolve services at runtime).
|
|
10
10
|
|
|
11
|
-
* **
|
|
11
|
+
* **Invalid (❌ Prohibited):**
|
|
12
12
|
```csharp
|
|
13
13
|
public class OrderService
|
|
14
14
|
{
|
|
@@ -26,7 +26,7 @@ Tài liệu này hướng dẫn cách cấu hình và tiêm phụ thuộc trong
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
```
|
|
29
|
-
* **
|
|
29
|
+
* **Valid (✔️ Recommended):**
|
|
30
30
|
```csharp
|
|
31
31
|
public class OrderService : IOrderService
|
|
32
32
|
{
|
|
@@ -47,8 +47,8 @@ Tài liệu này hướng dẫn cách cấu hình và tiêm phụ thuộc trong
|
|
|
47
47
|
|
|
48
48
|
---
|
|
49
49
|
|
|
50
|
-
## ⚙️
|
|
51
|
-
|
|
52
|
-
1. **Transient (`AddTransient<I, T>()`):**
|
|
53
|
-
2. **Scoped (`AddScoped<I, T>()`):**
|
|
54
|
-
3. **Singleton (`AddSingleton<I, T>()`):**
|
|
50
|
+
## ⚙️ Service Lifetimes Registration
|
|
51
|
+
Register dependencies with the appropriate lifetime scopes inside `Program.cs`:
|
|
52
|
+
1. **Transient (`AddTransient<I, T>()`):** Instantiated every time they are requested. Use for lightweight, stateless services.
|
|
53
|
+
2. **Scoped (`AddScoped<I, T>()`):** Instantiated once per HTTP request context. Recommended for repositories, database contexts (`DbContext`), and business services.
|
|
54
|
+
3. **Singleton (`AddSingleton<I, T>()`):** Instantiated once and shared across the entire application lifetime. Use for in-memory caching, system configurations, and thread-safe helper services.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Unified Exception Handling & Automated Validation (FluentValidation)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This document defines configurations for centralized exception handling using Middleware and incoming request validation using the FluentValidation library in .NET applications.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
## 🛡️
|
|
8
|
-
- **
|
|
9
|
-
-
|
|
7
|
+
## 🛡️ Automated Validation with FluentValidation
|
|
8
|
+
- **Rule:** Every API request representing complex input data payloads must have a corresponding validator class inheriting from `AbstractValidator<T>` to declare validation rules.
|
|
9
|
+
- Configure FluentValidation to automatically intercept invalid requests and return an HTTP `400 Bad Request` payload to the client, preventing repetitive manual validation checks inside Controllers.
|
|
10
10
|
|
|
11
11
|
```csharp
|
|
12
|
-
//
|
|
12
|
+
// Example of a Validator class
|
|
13
13
|
using FluentValidation;
|
|
14
14
|
|
|
15
15
|
public class CreateCourseRequestValidator : AbstractValidator<CreateCourseRequestDto>
|
|
@@ -17,32 +17,32 @@ Tài liệu này quy định cấu hình xử lý ngoại lệ tập trung qua M
|
|
|
17
17
|
public CreateCourseRequestValidator()
|
|
18
18
|
{
|
|
19
19
|
RuleFor(x => x.Title)
|
|
20
|
-
.NotEmpty().WithMessage("
|
|
21
|
-
.MaximumLength(150).WithMessage("
|
|
20
|
+
.NotEmpty().WithMessage("Course title cannot be empty.")
|
|
21
|
+
.MaximumLength(150).WithMessage("Title must not exceed 150 characters.");
|
|
22
22
|
|
|
23
23
|
RuleFor(x => x.Price)
|
|
24
|
-
.GreaterThanOrEqualTo(0).WithMessage("
|
|
24
|
+
.GreaterThanOrEqualTo(0).WithMessage("Course price cannot be less than 0.");
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
31
|
-
## 🚨
|
|
32
|
-
- **
|
|
33
|
-
-
|
|
31
|
+
## 🚨 Centralized Exception Handling (Global Exception Middleware)
|
|
32
|
+
- **Rule:** Never expose raw system exceptions (such as database faults, file system errors, or stack traces) directly to clients as HTTP 500 errors.
|
|
33
|
+
- Build a global middleware (`ExceptionHandlingMiddleware`) or implement `IExceptionHandler` in .NET 8+ to intercept all unhandled exceptions and format them into a standard JSON payload:
|
|
34
34
|
|
|
35
35
|
```json
|
|
36
36
|
{
|
|
37
37
|
"success": false,
|
|
38
38
|
"statusCode": 500,
|
|
39
39
|
"timestamp": "2026-06-13T07:42:00.000Z",
|
|
40
|
-
"message": "
|
|
40
|
+
"message": "An internal server error occurred. Please contact the administrator."
|
|
41
41
|
}
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
```csharp
|
|
45
|
-
//
|
|
45
|
+
// Standard Global Exception Handling Middleware
|
|
46
46
|
public class ExceptionHandlingMiddleware
|
|
47
47
|
{
|
|
48
48
|
private readonly RequestDelegate _next;
|
|
@@ -77,7 +77,7 @@ Tài liệu này quy định cấu hình xử lý ngoại lệ tập trung qua M
|
|
|
77
77
|
success = false,
|
|
78
78
|
statusCode = context.Response.StatusCode,
|
|
79
79
|
timestamp = DateTime.UtcNow,
|
|
80
|
-
message = "
|
|
80
|
+
message = "An internal server error occurred. Please contact the administrator."
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
return context.Response.WriteAsJsonAsync(response);
|