agent-workflow-kit-cli 1.0.0-mvp โ†’ 1.2.0

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.
@@ -0,0 +1,134 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ import chalk from "chalk";
6
+ import { promises as fs } from "fs";
7
+ import path from "path";
8
+ import { renderTemplate, readStaticTemplateFile, getStackRules, getStackSkills, } from "../../core/renderer.js";
9
+ import { updateFileWithBlock, writeRuleWithChunking, } from "../../core/emitter.js";
10
+ import { analyzeModule } from "../../core/analyzer.js";
11
+ export async function runAdd(stack, options) {
12
+ const targetStack = stack.toLowerCase();
13
+ const validStacks = ["spring-boot", "react-ts", "fastapi", "python-ai"];
14
+ if (!validStacks.includes(targetStack)) {
15
+ console.error(chalk.red(`Error: Invalid stack '${stack}'. Supported stacks are: ${validStacks.join(", ")}`));
16
+ process.exit(1);
17
+ }
18
+ const targetDir = path.resolve(options.path);
19
+ console.log(chalk.bold.cyan("\n๐Ÿš€ Agent Workflow Kit - Adding Stack Pack..."));
20
+ console.log(chalk.dim("------------------------------------------"));
21
+ console.log(`${chalk.bold("Target Folder:")} ${chalk.green(targetDir)}`);
22
+ console.log(`${chalk.bold("Stack Pack:")} ${chalk.green(targetStack)}`);
23
+ console.log(`${chalk.bold("Agent Profile:")} ${chalk.green(options.agent)}`);
24
+ console.log(`${chalk.bold("Dry Run:")} ${options.dryRun ? chalk.yellow("Enabled ๐Ÿงช") : chalk.gray("Disabled")}`);
25
+ console.log(chalk.dim("------------------------------------------\n"));
26
+ // 1. Analyze the target directory for this specific stack
27
+ const analysis = await analyzeModule(targetDir, [targetStack]);
28
+ // 2. Render stack guidelines for AGENTS.md
29
+ let stackContent = "";
30
+ try {
31
+ const stackCtx = analysis[targetStack] || {};
32
+ const rendered = await renderTemplate(`${targetStack}/AGENTS.md.hbs`, stackCtx);
33
+ stackContent = rendered.trim();
34
+ }
35
+ catch (err) {
36
+ console.error(chalk.red(`Error loading template for stack '${targetStack}': ${err instanceof Error ? err.message : String(err)}`));
37
+ process.exit(1);
38
+ }
39
+ // 3. Write or Update AGENTS.md in the target directory
40
+ const agentsPath = path.join(targetDir, "AGENTS.md");
41
+ const moduleAgentsContent = await renderTemplate("common/AGENTS.md.hbs", {
42
+ stackContent,
43
+ });
44
+ if (options.dryRun) {
45
+ console.log(chalk.gray(`[Dry Run] Would write/update AGENTS.md at ${agentsPath}`));
46
+ }
47
+ else {
48
+ try {
49
+ await fs.access(agentsPath);
50
+ await updateFileWithBlock(agentsPath, "STACK_PACK", stackContent);
51
+ console.log(chalk.green(`โœ”๏ธ Updated STACK_PACK block in ${agentsPath}`));
52
+ }
53
+ catch {
54
+ await fs.mkdir(targetDir, { recursive: true });
55
+ await fs.writeFile(agentsPath, moduleAgentsContent, "utf8");
56
+ console.log(chalk.green(`โœ”๏ธ Created ${agentsPath}`));
57
+ }
58
+ }
59
+ // 4. Copy rules and skills for the target stack
60
+ const stackCtx = analysis[targetStack] || {};
61
+ // A. Copy Rules
62
+ const rules = await getStackRules(targetStack);
63
+ for (const rule of rules) {
64
+ if (rule === "microservice-style.md" && stackCtx.isMicroservice !== true) {
65
+ continue;
66
+ }
67
+ const relativeRulePath = `${targetStack}/rules/${rule}`;
68
+ try {
69
+ const ruleContent = await readStaticTemplateFile(relativeRulePath, stackCtx);
70
+ const targetRulePath = path.join(targetDir, ".agents", "rules", rule);
71
+ if (options.dryRun) {
72
+ console.log(chalk.gray(`[Dry Run] Would write rule to ${targetRulePath}`));
73
+ }
74
+ else {
75
+ await writeRuleWithChunking(targetRulePath, ruleContent);
76
+ console.log(chalk.green(`โœ”๏ธ Wrote rule ${rule} to ${targetRulePath}`));
77
+ }
78
+ }
79
+ catch (err) {
80
+ console.error(chalk.red(`Failed to copy rule ${rule}: ${err instanceof Error ? err.message : String(err)}`));
81
+ }
82
+ }
83
+ // B. Copy Skills
84
+ const skills = await getStackSkills(targetStack);
85
+ for (const skill of skills) {
86
+ const relativeSkillPath = `${targetStack}/skills/${skill}`;
87
+ try {
88
+ const skillContent = await readStaticTemplateFile(relativeSkillPath, stackCtx);
89
+ const targetSkillPath = path.join(targetDir, ".agents", "skills", skill);
90
+ if (options.dryRun) {
91
+ console.log(chalk.gray(`[Dry Run] Would write skill to ${targetSkillPath}`));
92
+ }
93
+ else {
94
+ await fs.mkdir(path.dirname(targetSkillPath), { recursive: true });
95
+ await fs.writeFile(targetSkillPath, skillContent, "utf8");
96
+ console.log(chalk.green(`โœ”๏ธ Wrote skill ${skill} to ${targetSkillPath}`));
97
+ }
98
+ }
99
+ catch (err) {
100
+ console.error(chalk.red(`Failed to copy skill ${skill}: ${err instanceof Error ? err.message : String(err)}`));
101
+ }
102
+ }
103
+ // C. Copy Common Skills if not present
104
+ try {
105
+ const commonSkills = await getStackSkills("common");
106
+ for (const skill of commonSkills) {
107
+ const relativeSkillPath = `common/skills/${skill}`;
108
+ try {
109
+ const skillContent = await readStaticTemplateFile(relativeSkillPath, {});
110
+ const targetSkillPath = path.join(targetDir, ".agents", "skills", skill);
111
+ if (options.dryRun) {
112
+ console.log(chalk.gray(`[Dry Run] Would ensure common skill ${skill} exists`));
113
+ }
114
+ else {
115
+ try {
116
+ await fs.access(targetSkillPath);
117
+ }
118
+ catch {
119
+ await fs.mkdir(path.dirname(targetSkillPath), { recursive: true });
120
+ await fs.writeFile(targetSkillPath, skillContent, "utf8");
121
+ console.log(chalk.green(`โœ”๏ธ Wrote common skill ${skill} to ${targetSkillPath}`));
122
+ }
123
+ }
124
+ }
125
+ catch (err) {
126
+ console.error(chalk.red(`Failed to copy common skill ${skill}: ${err instanceof Error ? err.message : String(err)}`));
127
+ }
128
+ }
129
+ }
130
+ catch {
131
+ // Ignore
132
+ }
133
+ console.log(chalk.bold.green("\n๐ŸŽ‰ Add completed successfully!"));
134
+ }
@@ -75,6 +75,27 @@ npx agent-workflow-kit-cli doctor || exit 1
75
75
  console.log(chalk.gray("Running: ruff check ."));
76
76
  await execa("ruff", ["check", "."], { cwd, stdio: "inherit" });
77
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", "."];
86
+ }
87
+ 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", "."];
92
+ }
93
+ }
94
+ }
95
+ catch { }
96
+ console.log(chalk.gray(`Running: ${cmd} ${args.join(" ")}`));
97
+ await execa(cmd, args, { cwd, stdio: "inherit" });
98
+ }
78
99
  else if (stack === "react-ts") {
79
100
  console.log(chalk.gray("Running: npx tsc --noEmit"));
80
101
  await execa("npx", ["tsc", "--noEmit"], { cwd, stdio: "inherit" });
@@ -58,8 +58,9 @@ export function parseSkillFile(content, defaultName) {
58
58
  return { name, description, body: body.trim() };
59
59
  }
60
60
  export async function runExport(target, options) {
61
- if (target.toLowerCase() !== "antigravity") {
62
- console.warn(chalk.yellow(`Warning: Currently, only 'antigravity' target is officially optimized, but trying to export custom rules anyway.`));
61
+ const normTarget = target.toLowerCase();
62
+ if (normTarget !== "antigravity" && normTarget !== "codex") {
63
+ console.warn(chalk.yellow(`Warning: Currently, only 'antigravity' and 'codex' targets are officially optimized, but trying to export custom rules anyway.`));
63
64
  }
64
65
  const cwd = process.cwd();
65
66
  const skillsDir = path.join(cwd, ".agents", "skills");
@@ -9,6 +9,7 @@ import { fileURLToPath } from "url";
9
9
  import { detectProjectModules } from "../../core/detector.js";
10
10
  import { renderTemplate, readStaticTemplateFile, getStackRules, getStackSkills, } from "../../core/renderer.js";
11
11
  import { updateFileWithBlock, writeRuleWithChunking, } from "../../core/emitter.js";
12
+ import { analyzeModule } from "../../core/analyzer.js";
12
13
  const __filename = fileURLToPath(import.meta.url);
13
14
  const __dirname = path.dirname(__filename);
14
15
  function printSuccessAndNextSteps(options) {
@@ -20,9 +21,9 @@ function printSuccessAndNextSteps(options) {
20
21
  console.log(chalk.gray(` - Root: ${chalk.underline("AGENTS.md")}`));
21
22
  console.log(chalk.gray(` - Stack rules: ${chalk.underline(".agents/rules/")}`));
22
23
  console.log(chalk.white(`2. Setup automatic git pre-commit hook validation:`));
23
- console.log(chalk.cyan(` run: npx agent-workflow-kit-cli doctor --install-hook`));
24
+ console.log(chalk.cyan(` npx agent-workflow-kit-cli doctor --install-hook`));
24
25
  console.log(chalk.white(`3. Export custom skills to register with your AI agent (e.g. Antigravity):`));
25
- console.log(chalk.cyan(` run: npx agent-workflow-kit-cli export antigravity`));
26
+ console.log(chalk.cyan(` npx agent-workflow-kit-cli export antigravity`));
26
27
  console.log(chalk.dim("------------------------------------------\n"));
27
28
  }
28
29
  }
@@ -86,10 +87,12 @@ export async function runInit(options) {
86
87
  // Process each module
87
88
  for (const mod of modules) {
88
89
  console.log(chalk.cyan(`\nProcessing module: ${mod.name} (stacks: ${mod.stacks.join(", ")})`));
90
+ const analysis = await analyzeModule(mod.dir, mod.stacks);
89
91
  let stackContent = "";
90
92
  for (const stack of mod.stacks) {
91
93
  try {
92
- const rendered = await renderTemplate(`${stack}/AGENTS.md.hbs`, {});
94
+ const stackCtx = analysis[stack] || {};
95
+ const rendered = await renderTemplate(`${stack}/AGENTS.md.hbs`, stackCtx);
93
96
  stackContent += rendered + "\n\n";
94
97
  }
95
98
  catch (err) {
@@ -116,14 +119,38 @@ export async function runInit(options) {
116
119
  console.log(chalk.green(`โœ”๏ธ Created ${mod.name}/AGENTS.md`));
117
120
  }
118
121
  }
122
+ // Copy common skills for this module
123
+ try {
124
+ const commonSkills = await getStackSkills("common");
125
+ for (const skill of commonSkills) {
126
+ const relativeSkillPath = `common/skills/${skill}`;
127
+ try {
128
+ const skillContent = await readStaticTemplateFile(relativeSkillPath, {});
129
+ const targetSkillPath = path.join(mod.dir, ".agents", "skills", skill);
130
+ await fs.mkdir(path.dirname(targetSkillPath), { recursive: true });
131
+ await fs.writeFile(targetSkillPath, skillContent, "utf8");
132
+ console.log(chalk.green(`โœ”๏ธ Wrote common skill ${skill} to ${mod.name}/.agents/skills/${skill}`));
133
+ }
134
+ catch (err) {
135
+ console.error(chalk.red(`Failed to copy common skill ${skill}: ${err instanceof Error ? err.message : String(err)}`));
136
+ }
137
+ }
138
+ }
139
+ catch {
140
+ // Ignore
141
+ }
119
142
  // Copy rules and skills for each stack in this module
120
143
  for (const stack of mod.stacks) {
144
+ const stackCtx = analysis[stack] || {};
121
145
  // A. Rules
122
146
  const rules = await getStackRules(stack);
123
147
  for (const rule of rules) {
148
+ if (rule === "microservice-style.md" && stackCtx.isMicroservice !== true) {
149
+ continue;
150
+ }
124
151
  const relativeRulePath = `${stack}/rules/${rule}`;
125
152
  try {
126
- const ruleContent = await readStaticTemplateFile(relativeRulePath);
153
+ const ruleContent = await readStaticTemplateFile(relativeRulePath, stackCtx);
127
154
  const targetRulePath = path.join(mod.dir, ".agents", "rules", rule);
128
155
  if (options.dryRun) {
129
156
  console.log(chalk.gray(`[Dry Run] Would write rule to ${targetRulePath} (length: ${ruleContent.length} chars)`));
@@ -142,7 +169,7 @@ export async function runInit(options) {
142
169
  for (const skill of skills) {
143
170
  const relativeSkillPath = `${stack}/skills/${skill}`;
144
171
  try {
145
- const skillContent = await readStaticTemplateFile(relativeSkillPath);
172
+ const skillContent = await readStaticTemplateFile(relativeSkillPath, stackCtx);
146
173
  const targetSkillPath = path.join(mod.dir, ".agents", "skills", skill);
147
174
  if (options.dryRun) {
148
175
  console.log(chalk.gray(`[Dry Run] Would write skill to ${targetSkillPath}`));
package/dist/cli/index.js CHANGED
@@ -5,6 +5,7 @@
5
5
  import { Command } from "commander";
6
6
  import chalk from "chalk";
7
7
  import { runInit } from "./commands/init.js";
8
+ import { runAdd } from "./commands/add.js";
8
9
  import { runSync } from "./commands/sync.js";
9
10
  import { runDoctor } from "./commands/doctor.js";
10
11
  import { runExport } from "./commands/export.js";
@@ -13,7 +14,7 @@ export function runCli() {
13
14
  program
14
15
  .name("agent-workflow-kit")
15
16
  .description("Generate AI coding workflows/rules/templates for Codex and Antigravity")
16
- .version("1.0.0-mvp");
17
+ .version("1.2.0");
17
18
  program
18
19
  .command("init")
19
20
  .description("Initialize agent guidelines and skills for the repository")
@@ -29,6 +30,21 @@ export function runCli() {
29
30
  process.exit(1);
30
31
  }
31
32
  });
33
+ program
34
+ .command("add <stack>")
35
+ .description("Manually add/install a stack pack to a specific folder")
36
+ .option("--path <path>", "Target folder path to install the guidelines", ".")
37
+ .option("--agent <agent>", "Specify target agent profile: both | codex | antigravity", "both")
38
+ .option("--dry-run", "Output actions to console without writing any files", false)
39
+ .action(async (stack, options) => {
40
+ try {
41
+ await runAdd(stack, options);
42
+ }
43
+ catch (err) {
44
+ console.error(chalk.red(`Error running add: ${err instanceof Error ? err.message : String(err)}`));
45
+ process.exit(1);
46
+ }
47
+ });
32
48
  program
33
49
  .command("sync")
34
50
  .description("Sync generated guidelines and skills inside managed blocks")