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.
- package/dist/cli/commands/add.js +3 -1
- package/dist/cli/commands/doctor.js +8 -1
- package/dist/cli/commands/init.js +6 -0
- package/dist/cli/index.js +2 -2
- package/dist/core/analyzer.js +216 -1
- package/dist/core/awos/intelligence.js +30 -6
- package/dist/core/detector.js +54 -4
- 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 +53 -0
- package/templates/dotnet/rules/api-structure.md +43 -0
- package/templates/dotnet/rules/csharp-style.md +33 -0
- package/templates/dotnet/rules/dependency-injection.md +54 -0
- package/templates/dotnet/rules/error-handling-validation.md +86 -0
- package/templates/dotnet/skills/dotnet-controller/SKILL.md +24 -0
- package/templates/express/AGENTS.md.hbs +41 -0
- package/templates/express/rules/error-handling.md +88 -0
- package/templates/express/rules/express-style.md +39 -0
- package/templates/express/rules/router-controller.md +27 -0
- package/templates/express/skills/express-endpoint/SKILL.md +24 -0
- 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 +38 -0
- package/templates/nestjs/rules/module-architecture.md +35 -0
- package/templates/nestjs/rules/nestjs-style.md +41 -0
- package/templates/nestjs/rules/validation-errors.md +46 -0
- package/templates/nestjs/skills/nestjs-module/SKILL.md +25 -0
- package/templates/next-js/AGENTS.md.hbs +41 -0
- package/templates/next-js/rules/data-fetching-mutations.md +36 -0
- package/templates/next-js/rules/next-style.md +32 -0
- package/templates/next-js/rules/seo-metadata.md +50 -0
- package/templates/next-js/rules/server-client-components.md +27 -0
- package/templates/next-js/skills/next-feature/SKILL.md +26 -0
- package/templates/react-ts/AGENTS.md.hbs +109 -32
- package/templates/react-ts/rules/data-fetching.md +45 -0
- package/templates/react-ts/rules/forms-validation.md +55 -0
- package/templates/react-ts/rules/premium-ui.md +45 -0
- package/templates/react-ts/rules/react-style.md +38 -26
- package/templates/react-ts/rules/routing-splitting.md +21 -0
- package/templates/react-ts/rules/seo-accessibility.md +34 -0
- package/templates/react-ts/skills/react-feature/SKILL.md +5 -5
- 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", "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.
|
|
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) => {
|
package/dist/core/analyzer.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
package/dist/core/detector.js
CHANGED
|
@@ -38,16 +38,30 @@ async function detectProjectStackDirect(cwd) {
|
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
-
// 2. Detect
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
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
|
@@ -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.
|