agent-workflow-kit-cli 1.3.1 → 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.
Files changed (50) hide show
  1. package/dist/cli/commands/add.js +3 -1
  2. package/dist/cli/commands/doctor.js +8 -1
  3. package/dist/cli/commands/init.js +6 -0
  4. package/dist/cli/index.js +2 -2
  5. package/dist/core/analyzer.js +216 -1
  6. package/dist/core/awos/intelligence.js +30 -6
  7. package/dist/core/detector.js +54 -4
  8. package/package.json +1 -1
  9. package/templates/common/AGENTS.md.hbs +4 -0
  10. package/templates/common/GLOBAL_RULES.md +101 -0
  11. package/templates/dotnet/AGENTS.md.hbs +53 -0
  12. package/templates/dotnet/rules/api-structure.md +43 -0
  13. package/templates/dotnet/rules/csharp-style.md +33 -0
  14. package/templates/dotnet/rules/dependency-injection.md +54 -0
  15. package/templates/dotnet/rules/error-handling-validation.md +86 -0
  16. package/templates/dotnet/skills/dotnet-controller/SKILL.md +24 -0
  17. package/templates/express/AGENTS.md.hbs +41 -0
  18. package/templates/express/rules/error-handling.md +88 -0
  19. package/templates/express/rules/express-style.md +39 -0
  20. package/templates/express/rules/router-controller.md +27 -0
  21. package/templates/express/skills/express-endpoint/SKILL.md +24 -0
  22. package/templates/golang/AGENTS.md.hbs +36 -0
  23. package/templates/golang/rules/concurrency.md +71 -0
  24. package/templates/golang/rules/error-handling.md +42 -0
  25. package/templates/golang/rules/golang-style.md +24 -0
  26. package/templates/golang/rules/project-layout.md +39 -0
  27. package/templates/nestjs/AGENTS.md.hbs +38 -0
  28. package/templates/nestjs/rules/module-architecture.md +35 -0
  29. package/templates/nestjs/rules/nestjs-style.md +41 -0
  30. package/templates/nestjs/rules/validation-errors.md +46 -0
  31. package/templates/nestjs/skills/nestjs-module/SKILL.md +25 -0
  32. package/templates/next-js/AGENTS.md.hbs +41 -0
  33. package/templates/next-js/rules/data-fetching-mutations.md +36 -0
  34. package/templates/next-js/rules/next-style.md +32 -0
  35. package/templates/next-js/rules/seo-metadata.md +50 -0
  36. package/templates/next-js/rules/server-client-components.md +27 -0
  37. package/templates/next-js/skills/next-feature/SKILL.md +26 -0
  38. package/templates/react-ts/AGENTS.md.hbs +109 -32
  39. package/templates/react-ts/rules/data-fetching.md +45 -0
  40. package/templates/react-ts/rules/forms-validation.md +55 -0
  41. package/templates/react-ts/rules/premium-ui.md +45 -0
  42. package/templates/react-ts/rules/react-style.md +38 -26
  43. package/templates/react-ts/rules/routing-splitting.md +21 -0
  44. package/templates/react-ts/rules/seo-accessibility.md +34 -0
  45. package/templates/react-ts/skills/react-feature/SKILL.md +5 -5
  46. package/templates/rust/AGENTS.md.hbs +34 -0
  47. package/templates/rust/rules/error-handling.md +36 -0
  48. package/templates/rust/rules/memory-concurrency.md +47 -0
  49. package/templates/rust/rules/project-layout.md +49 -0
  50. package/templates/rust/rules/rust-style.md +26 -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", "fastapi", "python-ai"];
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") {
@@ -96,10 +96,17 @@ npx agent-workflow-kit-cli doctor || exit 1
96
96
  console.log(chalk.gray(`Running: ${cmd} ${args.join(" ")}`));
97
97
  await execa(cmd, args, { cwd, stdio: "inherit" });
98
98
  }
99
- else if (stack === "react-ts") {
99
+ else if (stack === "react-ts" ||
100
+ stack === "next-js" ||
101
+ stack === "nestjs" ||
102
+ stack === "express") {
100
103
  console.log(chalk.gray("Running: npx tsc --noEmit"));
101
104
  await execa("npx", ["tsc", "--noEmit"], { cwd, stdio: "inherit" });
102
105
  }
106
+ else if (stack === "dotnet") {
107
+ console.log(chalk.gray("Running: dotnet build"));
108
+ await execa("dotnet", ["build"], { cwd, stdio: "inherit" });
109
+ }
103
110
  console.log(chalk.green(`✔️ ${stack} validation passed!`));
104
111
  }
105
112
  catch (err) {
@@ -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/cli/index.js CHANGED
@@ -19,11 +19,11 @@ export function runCli() {
19
19
  program
20
20
  .name("agent-workflow-kit")
21
21
  .description("Generate AI coding workflows/rules/templates for Codex and Antigravity")
22
- .version("1.3.1");
22
+ .version("1.3.2");
23
23
  program
24
24
  .command("init")
25
25
  .description("Initialize agent guidelines and skills for the repository")
26
- .option("--stack <stack>", "Specify target stack: auto | spring-boot | react-ts | fastapi", "auto")
26
+ .option("--stack <stack>", "Specify target stack: auto | spring-boot | react-ts | next-js | nestjs | express | fastapi | dotnet", "auto")
27
27
  .option("--agent <agent>", "Specify target agent profile: both | codex | antigravity", "both")
28
28
  .option("--dry-run", "Output actions to console without writing any files", false)
29
29
  .action(async (options) => {
@@ -356,22 +356,36 @@ async function analyzeSpringBoot(dir) {
356
356
  async function analyzeReactTs(dir) {
357
357
  let packageManager = "npm";
358
358
  let runCommand = "npm run";
359
+ let executeCommand = "npx";
360
+ let lockFile = "package-lock.json";
359
361
  try {
360
362
  const hasYarnLock = await fs.stat(path.join(dir, "yarn.lock")).then((s) => s.isFile()).catch(() => false);
361
363
  const hasPnpmLock = await fs.stat(path.join(dir, "pnpm-lock.yaml")).then((s) => s.isFile()).catch(() => false);
364
+ const hasBunLockb = await fs.stat(path.join(dir, "bun.lockb")).then((s) => s.isFile()).catch(() => false);
365
+ const hasBunLock = await fs.stat(path.join(dir, "bun.lock")).then((s) => s.isFile()).catch(() => false);
362
366
  if (hasYarnLock) {
363
367
  packageManager = "yarn";
364
368
  runCommand = "yarn";
369
+ executeCommand = "yarn dlx";
370
+ lockFile = "yarn.lock";
365
371
  }
366
372
  else if (hasPnpmLock) {
367
373
  packageManager = "pnpm";
368
374
  runCommand = "pnpm";
375
+ executeCommand = "pnpm dlx";
376
+ lockFile = "pnpm-lock.yaml";
377
+ }
378
+ else if (hasBunLockb || hasBunLock) {
379
+ packageManager = "bun";
380
+ runCommand = "bun run";
381
+ executeCommand = "bunx";
382
+ lockFile = hasBunLockb ? "bun.lockb" : "bun.lock";
369
383
  }
370
384
  }
371
385
  catch {
372
386
  // Use default
373
387
  }
374
- return { packageManager, runCommand };
388
+ return { packageManager, runCommand, executeCommand, lockFile };
375
389
  }
376
390
  /**
377
391
  * Analyzes FastAPI project details.
@@ -439,6 +453,42 @@ async function analyzePythonAi(dir) {
439
453
  }
440
454
  return { packageManager, runCommand, hasGpuLibraries, hasHardwareLibraries };
441
455
  }
456
+ /**
457
+ * Analyzes Next.js project details.
458
+ */
459
+ async function analyzeNextJs(dir) {
460
+ const ctx = await analyzeReactTs(dir);
461
+ return {
462
+ packageManager: ctx.packageManager,
463
+ runCommand: ctx.runCommand,
464
+ executeCommand: ctx.executeCommand,
465
+ lockFile: ctx.lockFile,
466
+ };
467
+ }
468
+ /**
469
+ * Analyzes NestJS project details.
470
+ */
471
+ async function analyzeNestJs(dir) {
472
+ const ctx = await analyzeReactTs(dir);
473
+ return {
474
+ packageManager: ctx.packageManager,
475
+ runCommand: ctx.runCommand,
476
+ executeCommand: ctx.executeCommand,
477
+ lockFile: ctx.lockFile,
478
+ };
479
+ }
480
+ /**
481
+ * Analyzes Express.js project details.
482
+ */
483
+ async function analyzeExpress(dir) {
484
+ const ctx = await analyzeReactTs(dir);
485
+ return {
486
+ packageManager: ctx.packageManager,
487
+ runCommand: ctx.runCommand,
488
+ executeCommand: ctx.executeCommand,
489
+ lockFile: ctx.lockFile,
490
+ };
491
+ }
442
492
  /**
443
493
  * Entry point to analyze module configurations.
444
494
  */
@@ -451,12 +501,177 @@ export async function analyzeModule(dir, stacks) {
451
501
  else if (stack === "react-ts") {
452
502
  context["react-ts"] = await analyzeReactTs(dir);
453
503
  }
504
+ else if (stack === "next-js") {
505
+ context["next-js"] = await analyzeNextJs(dir);
506
+ }
507
+ else if (stack === "nestjs") {
508
+ context["nestjs"] = await analyzeNestJs(dir);
509
+ }
510
+ else if (stack === "express") {
511
+ context["express"] = await analyzeExpress(dir);
512
+ }
454
513
  else if (stack === "fastapi") {
455
514
  context["fastapi"] = await analyzeFastApi(dir);
456
515
  }
457
516
  else if (stack === "python-ai") {
458
517
  context["python-ai"] = await analyzePythonAi(dir);
459
518
  }
519
+ else if (stack === "dotnet") {
520
+ context["dotnet"] = await analyzeDotnet(dir);
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
+ }
460
528
  }
461
529
  return context;
462
530
  }
531
+ /**
532
+ * Analyzes dotnet project details.
533
+ */
534
+ async function analyzeDotnet(dir) {
535
+ let solutionName = path.basename(dir);
536
+ const projectNames = [];
537
+ const testProjects = [];
538
+ let dotnetVersion = "8.0";
539
+ try {
540
+ const entries = await fs.readdir(dir, { withFileTypes: true });
541
+ // Find .sln file
542
+ const slnFile = entries.find(e => e.isFile() && e.name.endsWith(".sln"));
543
+ if (slnFile) {
544
+ solutionName = path.parse(slnFile.name).name;
545
+ }
546
+ // Find all .csproj files (recursively)
547
+ const csprojFiles = await findCsprojFiles(dir);
548
+ for (const file of csprojFiles) {
549
+ const projName = path.parse(file).name;
550
+ try {
551
+ const content = await fs.readFile(file, "utf8");
552
+ if (content.includes("Microsoft.NET.Test.Sdk") || projName.toLowerCase().includes("test")) {
553
+ testProjects.push(projName);
554
+ }
555
+ else {
556
+ projectNames.push(projName);
557
+ }
558
+ // Try to parse TargetFramework
559
+ const match = content.match(/<TargetFramework>net([\d\.]+)<\/TargetFramework>/);
560
+ if (match && match[1]) {
561
+ dotnetVersion = match[1];
562
+ }
563
+ }
564
+ catch {
565
+ projectNames.push(projName);
566
+ }
567
+ }
568
+ }
569
+ catch {
570
+ // Keep defaults
571
+ }
572
+ return {
573
+ solutionName,
574
+ projectNames,
575
+ dotnetVersion,
576
+ testProjects,
577
+ };
578
+ }
579
+ async function findCsprojFiles(dir) {
580
+ const files = [];
581
+ async function traverse(currentDir) {
582
+ if (files.length >= 20)
583
+ return;
584
+ try {
585
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
586
+ for (const entry of entries) {
587
+ if (files.length >= 20)
588
+ return;
589
+ const fullPath = path.join(currentDir, entry.name);
590
+ if (entry.isDirectory()) {
591
+ if (entry.name.startsWith(".") ||
592
+ entry.name === "node_modules" ||
593
+ entry.name === "bin" ||
594
+ entry.name === "obj" ||
595
+ entry.name === "target" ||
596
+ entry.name === "build" ||
597
+ entry.name === "dist") {
598
+ continue;
599
+ }
600
+ await traverse(fullPath);
601
+ }
602
+ else if (entry.isFile() && entry.name.endsWith(".csproj")) {
603
+ files.push(fullPath);
604
+ }
605
+ }
606
+ }
607
+ catch {
608
+ // Ignore
609
+ }
610
+ }
611
+ await traverse(dir);
612
+ return files;
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
+ }
@@ -141,23 +141,41 @@ export async function buildRepositoryContext(workspaceRoot) {
141
141
  }
142
142
  // Determine default architecture style based on stack
143
143
  let architecture = "layered";
144
- if (mainStack === "spring-boot") {
144
+ if (mainStack === "spring-boot" || mainStack === "dotnet") {
145
145
  architecture = "clean-architecture";
146
146
  }
147
- else if (mainStack === "react-ts") {
147
+ else if (mainStack === "react-ts" || mainStack === "next-js") {
148
148
  architecture = "feature-first";
149
149
  }
150
+ else if (mainStack === "nestjs") {
151
+ architecture = "module-driven";
152
+ }
153
+ else if (mainStack === "express") {
154
+ architecture = "layered";
155
+ }
150
156
  else if (mainStack === "fastapi") {
151
157
  architecture = "vertical-slice";
152
158
  }
153
159
  // Testing strategy defaults
154
160
  const frameworks = [];
155
- if (mainStack === "react-ts")
161
+ if (mainStack === "react-ts") {
156
162
  frameworks.push("vitest", "testing-library");
157
- else if (mainStack === "spring-boot")
163
+ }
164
+ else if (mainStack === "next-js") {
165
+ frameworks.push("jest", "playwright");
166
+ }
167
+ else if (mainStack === "nestjs" || mainStack === "express") {
168
+ frameworks.push("jest", "supertest");
169
+ }
170
+ else if (mainStack === "spring-boot") {
158
171
  frameworks.push("junit", "mockito");
159
- else if (mainStack === "fastapi")
172
+ }
173
+ else if (mainStack === "fastapi") {
160
174
  frameworks.push("pytest");
175
+ }
176
+ else if (mainStack === "dotnet") {
177
+ frameworks.push("xunit", "moq");
178
+ }
161
179
  const testing = {
162
180
  frameworks,
163
181
  coverageGoal: 80,
@@ -167,12 +185,18 @@ export async function buildRepositoryContext(workspaceRoot) {
167
185
  if (mainStack === "spring-boot") {
168
186
  validationLibraries.push("jakarta.validation");
169
187
  }
170
- else if (mainStack === "react-ts") {
188
+ else if (mainStack === "react-ts" || mainStack === "next-js" || mainStack === "express") {
171
189
  validationLibraries.push("zod");
172
190
  }
191
+ else if (mainStack === "nestjs") {
192
+ validationLibraries.push("class-validator");
193
+ }
173
194
  else if (mainStack === "fastapi") {
174
195
  validationLibraries.push("pydantic");
175
196
  }
197
+ else if (mainStack === "dotnet") {
198
+ validationLibraries.push("fluentvalidation");
199
+ }
176
200
  const registry = await loadAWOSPlugins(workspaceRoot);
177
201
  let context = {
178
202
  stack: mainStack,
@@ -38,16 +38,30 @@ async function detectProjectStackDirect(cwd) {
38
38
  }
39
39
  }
40
40
  }
41
- // 2. Detect React + TypeScript (package.json containing react dependency)
41
+ // 2. Detect Node.js / JavaScript / TypeScript stacks (React, Next.js, NestJS, Express)
42
42
  try {
43
43
  const pkgPath = path.join(cwd, "package.json");
44
44
  const content = await fs.readFile(pkgPath, "utf8");
45
45
  const pkgJson = JSON.parse(content);
46
- const hasReact = (pkgJson.dependencies && pkgJson.dependencies.react) ||
47
- (pkgJson.devDependencies && pkgJson.devDependencies.react);
48
- if (hasReact) {
46
+ const deps = pkgJson.dependencies || {};
47
+ const devDeps = pkgJson.devDependencies || {};
48
+ const hasReact = deps.react || devDeps.react;
49
+ const hasNext = deps.next || devDeps.next;
50
+ const hasNest = deps["@nestjs/core"] || devDeps["@nestjs/core"];
51
+ const hasExpress = deps.express || devDeps.express;
52
+ if (hasNext) {
53
+ stacks.push("next-js");
54
+ }
55
+ else if (hasReact) {
49
56
  stacks.push("react-ts");
50
57
  }
58
+ const hasNestConfig = await fs.stat(path.join(cwd, "nest-cli.json")).then((s) => s.isFile()).catch(() => false);
59
+ if (hasNest || hasNestConfig) {
60
+ stacks.push("nestjs");
61
+ }
62
+ else if (hasExpress) {
63
+ stacks.push("express");
64
+ }
51
65
  }
52
66
  catch {
53
67
  // Ignore
@@ -102,6 +116,42 @@ async function detectProjectStackDirect(cwd) {
102
116
  }
103
117
  }
104
118
  }
119
+ // 4. Detect .NET (files ending in .csproj or .sln, or global.json)
120
+ try {
121
+ const entries = await fs.readdir(cwd, { withFileTypes: true });
122
+ const hasDotnet = entries.some((entry) => entry.isFile() &&
123
+ (entry.name.endsWith(".csproj") ||
124
+ entry.name.endsWith(".sln") ||
125
+ entry.name === "global.json"));
126
+ if (hasDotnet) {
127
+ stacks.push("dotnet");
128
+ }
129
+ }
130
+ catch {
131
+ // Ignore
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
+ }
105
155
  return stacks;
106
156
  }
107
157
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-workflow-kit-cli",
3
- "version": "1.3.1",
3
+ "version": "1.3.3",
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,53 @@
1
+ ## 🗺️ .NET (C#) Development Guide
2
+
3
+ This project is a .NET (C#) application configured as follows:
4
+ - **Solution Name:** `{{solutionName}}`
5
+ - **.NET Version:** `net{{dotnetVersion}}`
6
+ - **Application Projects:**
7
+ {{#each projectNames}}
8
+ - `{{this}}`
9
+ {{/each}}
10
+ - **Test Projects:**
11
+ {{#each testProjects}}
12
+ - `{{this}}`
13
+ {{/each}}
14
+
15
+ ---
16
+
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
+
26
+ ---
27
+
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
+
36
+ ---
37
+
38
+ ### 🏛️ Strict Development Rules
39
+
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
+
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
+
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.