agent-workflow-kit-cli 1.3.2 → 1.3.4

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.
Files changed (51) hide show
  1. package/dist/cli/commands/add.js +3 -1
  2. package/dist/cli/commands/doctor.js +145 -47
  3. package/dist/cli/commands/init.js +6 -0
  4. package/dist/core/analyzer.js +70 -0
  5. package/dist/core/detector.js +22 -0
  6. package/package.json +1 -1
  7. package/templates/common/AGENTS.md.hbs +4 -0
  8. package/templates/common/GLOBAL_RULES.md +101 -0
  9. package/templates/devops/AGENTS.md.hbs +32 -0
  10. package/templates/devops/skills/devops/SKILL.md +477 -0
  11. package/templates/diagram/AGENTS.md.hbs +30 -0
  12. package/templates/diagram/skills/drawio-diagram/SKILL.md +427 -0
  13. package/templates/dotnet/AGENTS.md.hbs +38 -34
  14. package/templates/dotnet/rules/api-structure.md +15 -15
  15. package/templates/dotnet/rules/csharp-style.md +17 -17
  16. package/templates/dotnet/rules/dependency-injection.md +12 -12
  17. package/templates/dotnet/rules/error-handling-validation.md +15 -15
  18. package/templates/dotnet/skills/dotnet-controller/SKILL.md +16 -16
  19. package/templates/express/AGENTS.md.hbs +37 -33
  20. package/templates/express/rules/error-handling.md +18 -18
  21. package/templates/express/rules/express-style.md +19 -19
  22. package/templates/express/rules/router-controller.md +16 -16
  23. package/templates/express/skills/express-endpoint/SKILL.md +14 -14
  24. package/templates/fastapi/AGENTS.md.hbs +25 -3
  25. package/templates/fastapi/rules/api-testing.md +24 -0
  26. package/templates/fastapi/rules/database-async.md +26 -0
  27. package/templates/golang/AGENTS.md.hbs +42 -0
  28. package/templates/golang/rules/concurrency.md +71 -0
  29. package/templates/golang/rules/error-handling.md +42 -0
  30. package/templates/golang/rules/golang-style.md +24 -0
  31. package/templates/golang/rules/project-layout.md +39 -0
  32. package/templates/golang/skills/golang-db/SKILL.md +27 -0
  33. package/templates/golang/skills/golang-feature/SKILL.md +42 -0
  34. package/templates/nestjs/AGENTS.md.hbs +33 -29
  35. package/templates/nestjs/rules/module-architecture.md +14 -14
  36. package/templates/nestjs/rules/nestjs-style.md +12 -12
  37. package/templates/nestjs/rules/validation-errors.md +15 -15
  38. package/templates/nestjs/skills/nestjs-module/SKILL.md +15 -15
  39. package/templates/next-js/AGENTS.md.hbs +39 -35
  40. package/templates/next-js/rules/data-fetching-mutations.md +17 -17
  41. package/templates/next-js/rules/next-style.md +17 -17
  42. package/templates/next-js/rules/seo-metadata.md +18 -18
  43. package/templates/next-js/rules/server-client-components.md +17 -17
  44. package/templates/next-js/skills/next-feature/SKILL.md +16 -16
  45. package/templates/rust/AGENTS.md.hbs +41 -0
  46. package/templates/rust/rules/error-handling.md +36 -0
  47. package/templates/rust/rules/memory-concurrency.md +47 -0
  48. package/templates/rust/rules/project-layout.md +49 -0
  49. package/templates/rust/rules/rust-style.md +26 -0
  50. package/templates/rust/skills/rust-db/SKILL.md +27 -0
  51. package/templates/rust/skills/rust-feature/SKILL.md +34 -0
@@ -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", "diagram", "devops"];
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") {
@@ -6,7 +6,7 @@ import chalk from "chalk";
6
6
  import { promises as fs } from "fs";
7
7
  import path from "path";
8
8
  import { execa } from "execa";
9
- import { detectProjectStack } from "../../core/detector.js";
9
+ import { detectProjectModules } from "../../core/detector.js";
10
10
  export async function runDoctor(options) {
11
11
  const cwd = process.cwd();
12
12
  if (options.installHook) {
@@ -55,63 +55,161 @@ npx agent-workflow-kit-cli doctor || exit 1
55
55
  return;
56
56
  }
57
57
  console.log(chalk.blue("Running doctor checks on repository..."));
58
- // 1. Detect Stack
59
- const stacks = await detectProjectStack(cwd);
60
- console.log(chalk.gray(`Detected stacks: ${stacks.join(", ") || "None"}`));
61
- if (stacks.length === 0) {
62
- console.log(chalk.yellow("No standard stack pack detected. Nothing to validate."));
58
+ // 1. Detect Modules
59
+ const modules = await detectProjectModules(cwd);
60
+ if (modules.length === 0) {
61
+ console.log(chalk.yellow("No standard project modules detected. Nothing to validate."));
63
62
  return;
64
63
  }
64
+ console.log(chalk.gray(`Detected modules: ${modules.map(m => `${m.name} [${m.stacks.join(", ")}]`).join(", ")}`));
65
65
  // 2. Validate
66
66
  let passed = true;
67
- for (const stack of stacks) {
68
- console.log(chalk.blue(`\nValidating stack: ${stack}...`));
69
- try {
70
- if (stack === "spring-boot") {
71
- console.log(chalk.gray("Running: ./mvnw clean compile"));
72
- await execa("./mvnw", ["clean", "compile"], { cwd, stdio: "inherit" });
73
- }
74
- else if (stack === "fastapi") {
75
- console.log(chalk.gray("Running: ruff check ."));
76
- await execa("ruff", ["check", "."], { cwd, stdio: "inherit" });
77
- }
78
- else if (stack === "python-ai") {
79
- let cmd = "ruff";
80
- let args = ["check", "."];
81
- try {
82
- const hasPoetry = await fs.stat(path.join(cwd, "poetry.lock")).then(s => s.isFile()).catch(() => false);
83
- if (hasPoetry) {
84
- cmd = "poetry";
85
- args = ["run", "python", "-m", "ruff", "check", "."];
67
+ for (const mod of modules) {
68
+ console.log(chalk.blue(`\nValidating module: ${mod.name === "." ? "root" : mod.name}...`));
69
+ for (const stack of mod.stacks) {
70
+ console.log(chalk.cyan(` Validating stack: ${stack}...`));
71
+ try {
72
+ if (stack === "spring-boot") {
73
+ // Check for maven wrapper or gradle wrapper
74
+ const hasGradle = (await fs.stat(path.join(mod.dir, "build.gradle")).then((s) => s.isFile()).catch(() => false)) ||
75
+ (await fs.stat(path.join(mod.dir, "build.gradle.kts")).then((s) => s.isFile()).catch(() => false));
76
+ if (hasGradle) {
77
+ let gradlewPath = "./gradlew";
78
+ try {
79
+ await fs.access(path.join(mod.dir, "gradlew"));
80
+ }
81
+ catch {
82
+ try {
83
+ await fs.access(path.join(cwd, "gradlew"));
84
+ gradlewPath = path.relative(mod.dir, path.join(cwd, "gradlew")).replace(/\\/g, "/");
85
+ if (!gradlewPath.startsWith("."))
86
+ gradlewPath = "./" + gradlewPath;
87
+ }
88
+ catch {
89
+ gradlewPath = "gradle";
90
+ }
91
+ }
92
+ console.log(chalk.gray(`Running: ${gradlewPath} check in ${mod.dir}`));
93
+ await execa(gradlewPath, ["check"], { cwd: mod.dir, stdio: "inherit" });
86
94
  }
87
95
  else {
88
- const hasPipenv = await fs.stat(path.join(cwd, "Pipfile")).then(s => s.isFile()).catch(() => false);
89
- if (hasPipenv) {
90
- cmd = "pipenv";
91
- args = ["run", "python", "-m", "ruff", "check", "."];
96
+ let mvnwPath = "./mvnw";
97
+ try {
98
+ await fs.access(path.join(mod.dir, "mvnw"));
92
99
  }
100
+ catch {
101
+ try {
102
+ await fs.access(path.join(cwd, "mvnw"));
103
+ mvnwPath = path.relative(mod.dir, path.join(cwd, "mvnw")).replace(/\\/g, "/");
104
+ if (!mvnwPath.startsWith("."))
105
+ mvnwPath = "./" + mvnwPath;
106
+ }
107
+ catch {
108
+ mvnwPath = "mvn";
109
+ }
110
+ }
111
+ console.log(chalk.gray(`Running: ${mvnwPath} clean compile in ${mod.dir}`));
112
+ await execa(mvnwPath, ["clean", "compile"], { cwd: mod.dir, stdio: "inherit" });
93
113
  }
94
114
  }
95
- catch { }
96
- console.log(chalk.gray(`Running: ${cmd} ${args.join(" ")}`));
97
- await execa(cmd, args, { cwd, stdio: "inherit" });
98
- }
99
- else if (stack === "react-ts" ||
100
- stack === "next-js" ||
101
- stack === "nestjs" ||
102
- stack === "express") {
103
- console.log(chalk.gray("Running: npx tsc --noEmit"));
104
- await execa("npx", ["tsc", "--noEmit"], { cwd, stdio: "inherit" });
115
+ else if (stack === "fastapi") {
116
+ console.log(chalk.gray(`Running: ruff check . in ${mod.dir}`));
117
+ await execa("ruff", ["check", "."], { cwd: mod.dir, stdio: "inherit" });
118
+ }
119
+ else if (stack === "python-ai") {
120
+ let cmd = "ruff";
121
+ let args = ["check", "."];
122
+ try {
123
+ const hasPoetry = await fs.stat(path.join(mod.dir, "poetry.lock")).then(s => s.isFile()).catch(() => false);
124
+ if (hasPoetry) {
125
+ cmd = "poetry";
126
+ args = ["run", "ruff", "check", "."];
127
+ }
128
+ else {
129
+ const hasPipenv = await fs.stat(path.join(mod.dir, "Pipfile")).then(s => s.isFile()).catch(() => false);
130
+ if (hasPipenv) {
131
+ cmd = "pipenv";
132
+ args = ["run", "ruff", "check", "."];
133
+ }
134
+ }
135
+ }
136
+ catch { }
137
+ console.log(chalk.gray(`Running: ${cmd} ${args.join(" ")} in ${mod.dir}`));
138
+ await execa(cmd, args, { cwd: mod.dir, stdio: "inherit" });
139
+ }
140
+ else if (stack === "react-ts" ||
141
+ stack === "next-js" ||
142
+ stack === "nestjs" ||
143
+ stack === "express") {
144
+ let pm = "npm";
145
+ try {
146
+ const hasYarnLock = await fs.stat(path.join(mod.dir, "yarn.lock")).then((s) => s.isFile()).catch(() => false);
147
+ const hasPnpmLock = await fs.stat(path.join(mod.dir, "pnpm-lock.yaml")).then((s) => s.isFile()).catch(() => false);
148
+ const hasBunLockb = await fs.stat(path.join(mod.dir, "bun.lockb")).then((s) => s.isFile()).catch(() => false);
149
+ const hasBunLock = await fs.stat(path.join(mod.dir, "bun.lock")).then((s) => s.isFile()).catch(() => false);
150
+ if (hasYarnLock)
151
+ pm = "yarn";
152
+ else if (hasPnpmLock)
153
+ pm = "pnpm";
154
+ else if (hasBunLockb || hasBunLock)
155
+ pm = "bun";
156
+ }
157
+ catch { }
158
+ try {
159
+ const pkgContent = await fs.readFile(path.join(mod.dir, "package.json"), "utf8");
160
+ const pkg = JSON.parse(pkgContent);
161
+ const scripts = pkg.scripts || {};
162
+ if (scripts.lint) {
163
+ console.log(chalk.gray(`Running: ${pm} run lint in ${mod.dir}`));
164
+ await execa(pm, ["run", "lint"], { cwd: mod.dir, stdio: "inherit" });
165
+ }
166
+ console.log(chalk.gray(`Running: npx tsc --noEmit in ${mod.dir}`));
167
+ await execa("npx", ["tsc", "--noEmit"], { cwd: mod.dir, stdio: "inherit" });
168
+ if (scripts.test) {
169
+ const hasVitest = pkg.dependencies?.vitest || pkg.devDependencies?.vitest;
170
+ const hasJest = pkg.dependencies?.jest || pkg.devDependencies?.jest;
171
+ let args = ["run", "test"];
172
+ if (hasVitest || hasJest) {
173
+ if (pm === "npm") {
174
+ args = ["run", "test", "--", "--run"];
175
+ }
176
+ else {
177
+ args = ["run", "test", "--run"];
178
+ }
179
+ }
180
+ console.log(chalk.gray(`Running: ${pm} ${args.join(" ")} in ${mod.dir}`));
181
+ await execa(pm, args, { cwd: mod.dir, stdio: "inherit", env: { ...process.env, CI: "true" } });
182
+ }
183
+ }
184
+ catch {
185
+ console.log(chalk.gray(`Running: npx tsc --noEmit in ${mod.dir}`));
186
+ await execa("npx", ["tsc", "--noEmit"], { cwd: mod.dir, stdio: "inherit" });
187
+ }
188
+ }
189
+ else if (stack === "dotnet") {
190
+ console.log(chalk.gray(`Running: dotnet build in ${mod.dir}`));
191
+ await execa("dotnet", ["build"], { cwd: mod.dir, stdio: "inherit" });
192
+ console.log(chalk.gray(`Running: dotnet test in ${mod.dir}`));
193
+ await execa("dotnet", ["test"], { cwd: mod.dir, stdio: "inherit" });
194
+ }
195
+ else if (stack === "golang") {
196
+ console.log(chalk.gray(`Running: go build ./... in ${mod.dir}`));
197
+ await execa("go", ["build", "./..."], { cwd: mod.dir, stdio: "inherit" });
198
+ console.log(chalk.gray(`Running: go test ./... in ${mod.dir}`));
199
+ await execa("go", ["test", "./..."], { cwd: mod.dir, stdio: "inherit" });
200
+ }
201
+ else if (stack === "rust") {
202
+ console.log(chalk.gray(`Running: cargo check in ${mod.dir}`));
203
+ await execa("cargo", ["check"], { cwd: mod.dir, stdio: "inherit" });
204
+ console.log(chalk.gray(`Running: cargo test in ${mod.dir}`));
205
+ await execa("cargo", ["test"], { cwd: mod.dir, stdio: "inherit" });
206
+ }
207
+ console.log(chalk.green(` ✔️ ${stack} validation passed!`));
105
208
  }
106
- else if (stack === "dotnet") {
107
- console.log(chalk.gray("Running: dotnet build"));
108
- await execa("dotnet", ["build"], { cwd, stdio: "inherit" });
209
+ catch (err) {
210
+ console.error(chalk.red(` ❌ ${stack} validation failed: ${err instanceof Error ? err.message : String(err)}`));
211
+ passed = false;
109
212
  }
110
- console.log(chalk.green(`✔️ ${stack} validation passed!`));
111
- }
112
- catch (err) {
113
- console.error(chalk.red(`❌ ${stack} validation failed: ${err instanceof Error ? err.message : String(err)}`));
114
- passed = false;
115
213
  }
116
214
  }
117
215
  if (passed) {
@@ -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") {
@@ -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
+ }
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-workflow-kit-cli",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "AI-Ready Repository Workflow Generator & Guideline Optimizer for Codex and Antigravity",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,3 +1,7 @@
1
+ {{{globalRules}}}
2
+
3
+ ---
4
+
1
5
  # Repository Agent Guidelines
2
6
 
3
7
  This repository implements the `agent-workflow-kit-cli` standards.
@@ -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).
@@ -0,0 +1,32 @@
1
+ ## 🚀 DevOps & Deployment Automation Guide
2
+
3
+ ### 🔄 DevOps Workflow
4
+ When setting up CI/CD pipelines or containerizing applications, apply these core principles:
5
+ 1. **Analyze Project Runtime:** Identify the language ecosystem (Node.js, Python, Go, Rust, Java, .NET) and detect package manager/lockfiles (e.g. `package-lock.json`, `poetry.lock`, `Cargo.lock`, `go.sum`).
6
+ 2. **Implement Secure Multi-Stage Builds:** Separate build dependencies from runtime dependencies. The runtime image must be minimal (e.g., alpine, distroless) and must never run as `root`.
7
+ 3. **Establish CI/CD Pipelines:** Setup linting, automated testing, and secure Docker image publication workflows inside GitHub Actions.
8
+
9
+ ---
10
+
11
+ ### 🏗️ Available DevOps Templates
12
+ Refer to the detailed rules below:
13
+ - Scaffolding secure Dockerfiles and GitHub Actions workflows: `@devops`
14
+
15
+ ---
16
+
17
+ ### 🏛️ Strict Container & Pipeline Standards
18
+
19
+ #### 1. Dockerfile Guidelines
20
+ - **Multi-Stage Builds:** Always use a builder stage to download tools, compile source code, and install development headers, then copy only the compiled binaries/compiled assets to the final stage.
21
+ - **Rootless Execution:** Declare a non-privileged user (e.g., `nobody`, `appuser`, `node`) using the `USER` directive in the final stage.
22
+ - **Caching Optimization:** Copy dependency manifest files (like `package.json`, `go.mod`, `Cargo.toml`) and perform installation steps *before* copying the source code directory to maximize layer caching efficiency.
23
+ - **Base Images:** Prefer minimal, stable base images (like `node:20-alpine`, `python:3.11-slim`, `gcr.io/distroless/static`).
24
+
25
+ #### 2. GitHub Actions CI/CD Rules
26
+ - **Security:** Use secrets (`secrets.GITHUB_TOKEN` or custom registry credentials) to authenticate with registries. Never hardcode credentials.
27
+ - **Phases:** Ensure the pipeline follows a clear linear progression: `Lint` -> `Test` -> `Build Docker` -> `Publish Image` -> `Verify`.
28
+ - **Environment:** Run tests inside appropriate isolation, specifying necessary environment variables as configuration variables.
29
+
30
+ ### 🧪 Verification
31
+ - Validate the Dockerfile builds locally: `docker build -t app:local .`
32
+ - Ensure GitHub Actions workflow schema checks out correctly using visual validators or linting tools.