agent-workflow-kit-cli 1.3.3 → 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.
@@ -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", "golang", "rust"];
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);
@@ -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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-workflow-kit-cli",
3
- "version": "1.3.3",
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",
@@ -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.