agent-workflow-kit-cli 1.2.0 → 1.3.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,143 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ import ts from "typescript";
6
+ import { execa } from "execa";
7
+ function parseTsImports(content) {
8
+ const sourceFile = ts.createSourceFile("file.ts", content, ts.ScriptTarget.Latest, true);
9
+ const imports = [];
10
+ function visit(node) {
11
+ if (ts.isImportDeclaration(node)) {
12
+ if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
13
+ imports.push(node.moduleSpecifier.text);
14
+ }
15
+ }
16
+ else if (ts.isExportDeclaration(node)) {
17
+ if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
18
+ imports.push(node.moduleSpecifier.text);
19
+ }
20
+ }
21
+ else if (ts.isCallExpression(node) &&
22
+ node.expression.kind === ts.SyntaxKind.ImportKeyword &&
23
+ node.arguments.length > 0) {
24
+ const arg = node.arguments[0];
25
+ if (ts.isStringLiteral(arg)) {
26
+ imports.push(arg.text);
27
+ }
28
+ }
29
+ ts.forEachChild(node, visit);
30
+ }
31
+ visit(sourceFile);
32
+ return imports;
33
+ }
34
+ function fallbackPyImports(content) {
35
+ const lines = content.split("\n");
36
+ const imports = [];
37
+ let inMultiline = false;
38
+ let multilineQuote = "";
39
+ for (let line of lines) {
40
+ line = line.trim();
41
+ if (!line)
42
+ continue;
43
+ if (!inMultiline) {
44
+ if (line.startsWith('"""')) {
45
+ inMultiline = true;
46
+ multilineQuote = '"""';
47
+ if (line.endsWith('"""') && line.length > 3) {
48
+ inMultiline = false;
49
+ }
50
+ continue;
51
+ }
52
+ if (line.startsWith("'''")) {
53
+ inMultiline = true;
54
+ multilineQuote = "'''";
55
+ if (line.endsWith("'''") && line.length > 3) {
56
+ inMultiline = false;
57
+ }
58
+ continue;
59
+ }
60
+ const hashIdx = line.indexOf("#");
61
+ if (hashIdx !== -1) {
62
+ line = line.substring(0, hashIdx).trim();
63
+ }
64
+ const importFromMatch = line.match(/^from\s+([a-zA-Z0-9._]+)\s+import/);
65
+ if (importFromMatch) {
66
+ imports.push(importFromMatch[1]);
67
+ }
68
+ else {
69
+ const importMatch = line.match(/^import\s+([a-zA-Z0-9._\s,]+)/);
70
+ if (importMatch) {
71
+ const modules = importMatch[1].split(",").map(m => m.trim());
72
+ imports.push(...modules);
73
+ }
74
+ }
75
+ }
76
+ else {
77
+ if (line.endsWith(multilineQuote)) {
78
+ inMultiline = false;
79
+ }
80
+ }
81
+ }
82
+ return imports;
83
+ }
84
+ async function parsePyImports(content) {
85
+ const pyScript = `
86
+ import ast, sys, json
87
+ try:
88
+ tree = ast.parse(sys.stdin.read())
89
+ imports = []
90
+ for node in ast.walk(tree):
91
+ if isinstance(node, ast.Import):
92
+ for name in node.names:
93
+ imports.append(name.name)
94
+ elif isinstance(node, ast.ImportFrom):
95
+ if node.module:
96
+ imports.append(node.module)
97
+ print(json.dumps(imports))
98
+ except Exception:
99
+ sys.exit(1)
100
+ `.trim();
101
+ try {
102
+ const { stdout } = await execa("python", ["-c", pyScript], {
103
+ input: content,
104
+ timeout: 2000,
105
+ });
106
+ return JSON.parse(stdout);
107
+ }
108
+ catch {
109
+ return fallbackPyImports(content);
110
+ }
111
+ }
112
+ function parseJavaImports(content) {
113
+ const cleaned = content.replace(/\/\*[\s\S]*?\*\//g, "");
114
+ const lines = cleaned.split("\n");
115
+ const imports = [];
116
+ for (let line of lines) {
117
+ line = line.trim();
118
+ const doubleSlashIdx = line.indexOf("//");
119
+ if (doubleSlashIdx !== -1) {
120
+ line = line.substring(0, doubleSlashIdx).trim();
121
+ }
122
+ if (line.startsWith("import ")) {
123
+ const match = line.match(/^import\s+(?:static\s+)?([a-zA-Z0-9._*]+)\s*;/);
124
+ if (match) {
125
+ imports.push(match[1]);
126
+ }
127
+ }
128
+ }
129
+ return imports;
130
+ }
131
+ export async function parseImports(filePath, content) {
132
+ const ext = filePath.split(".").pop()?.toLowerCase();
133
+ if (ext === "ts" || ext === "tsx") {
134
+ return parseTsImports(content);
135
+ }
136
+ if (ext === "py") {
137
+ return parsePyImports(content);
138
+ }
139
+ if (ext === "java") {
140
+ return parseJavaImports(content);
141
+ }
142
+ return [];
143
+ }
@@ -6,6 +6,7 @@ import handlebars from "handlebars";
6
6
  import { promises as fs } from "fs";
7
7
  import path from "path";
8
8
  import { fileURLToPath } from "url";
9
+ import { loadConfig } from "./config.js";
9
10
  // Register custom Handlebars helpers
10
11
  handlebars.registerHelper("eq", function (a, b) {
11
12
  return a === b;
@@ -14,13 +15,30 @@ const __filename = fileURLToPath(import.meta.url);
14
15
  const __dirname = path.dirname(__filename);
15
16
  // The templates directory is located at '../../templates' relative to 'dist/core/renderer.js'
16
17
  const TEMPLATES_DIR = path.resolve(__dirname, "../../templates");
18
+ /**
19
+ * Resolves the path of a template file, prioritizing the customTemplatesPath if configured.
20
+ */
21
+ async function resolveTemplatePath(relativePath) {
22
+ const config = await loadConfig();
23
+ if (config.customTemplatesPath) {
24
+ const customFullPath = path.resolve(process.cwd(), config.customTemplatesPath, relativePath);
25
+ try {
26
+ await fs.access(customFullPath);
27
+ return customFullPath;
28
+ }
29
+ catch {
30
+ // Fallback to default
31
+ }
32
+ }
33
+ return path.join(TEMPLATES_DIR, relativePath);
34
+ }
17
35
  /**
18
36
  * Loads a Handlebars template, compiles it, and renders it with the given context.
19
37
  * @param templatePath Relative path to the template inside the templates directory (e.g. 'common/AGENTS.md.hbs')
20
38
  * @param context Key-value context data for compilation
21
39
  */
22
40
  export async function renderTemplate(templatePath, context) {
23
- const fullPath = path.join(TEMPLATES_DIR, templatePath);
41
+ const fullPath = await resolveTemplatePath(templatePath);
24
42
  const fileContent = await fs.readFile(fullPath, "utf8");
25
43
  const compiled = handlebars.compile(fileContent);
26
44
  return compiled(context);
@@ -31,7 +49,7 @@ export async function renderTemplate(templatePath, context) {
31
49
  * @param context Optional key-value data for compilation
32
50
  */
33
51
  export async function readStaticTemplateFile(filePath, context) {
34
- const fullPath = path.join(TEMPLATES_DIR, filePath);
52
+ const fullPath = await resolveTemplatePath(filePath);
35
53
  const fileContent = await fs.readFile(fullPath, "utf8");
36
54
  if (context) {
37
55
  const compiled = handlebars.compile(fileContent);
@@ -43,38 +61,71 @@ export async function readStaticTemplateFile(filePath, context) {
43
61
  * Gets all rules files for a stack.
44
62
  */
45
63
  export async function getStackRules(stack) {
46
- const rulesDir = path.join(TEMPLATES_DIR, stack, "rules");
64
+ const relativeRulesDir = path.join(stack, "rules");
65
+ const rules = new Set();
66
+ // Try custom first
67
+ const config = await loadConfig();
68
+ if (config.customTemplatesPath) {
69
+ const customRulesDir = path.resolve(process.cwd(), config.customTemplatesPath, relativeRulesDir);
70
+ try {
71
+ const files = await fs.readdir(customRulesDir);
72
+ for (const f of files) {
73
+ if (f.endsWith(".md"))
74
+ rules.add(f);
75
+ }
76
+ }
77
+ catch {
78
+ // Ignore
79
+ }
80
+ }
81
+ // Fallback default
82
+ const defaultRulesDir = path.join(TEMPLATES_DIR, stack, "rules");
47
83
  try {
48
- const files = await fs.readdir(rulesDir);
49
- return files.filter((f) => f.endsWith(".md"));
84
+ const files = await fs.readdir(defaultRulesDir);
85
+ for (const f of files) {
86
+ if (f.endsWith(".md"))
87
+ rules.add(f);
88
+ }
50
89
  }
51
90
  catch {
52
- return [];
91
+ // Ignore
53
92
  }
93
+ return Array.from(rules);
54
94
  }
55
95
  /**
56
96
  * Gets all skills for a stack.
57
97
  */
58
98
  export async function getStackSkills(stack) {
59
- const skillsDir = path.join(TEMPLATES_DIR, stack, "skills");
60
- try {
61
- const entries = await fs.readdir(skillsDir, { withFileTypes: true });
62
- const skills = [];
63
- for (const entry of entries) {
64
- if (entry.isDirectory()) {
65
- const skillFilePath = path.join(skillsDir, entry.name, "SKILL.md");
66
- try {
67
- await fs.access(skillFilePath);
68
- skills.push(`${entry.name}/SKILL.md`);
69
- }
70
- catch {
71
- // Ignore if SKILL.md doesn't exist
99
+ const relativeSkillsDir = path.join(stack, "skills");
100
+ const skills = new Set();
101
+ const checkDir = async (dirPath) => {
102
+ try {
103
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
104
+ for (const entry of entries) {
105
+ if (entry.isDirectory()) {
106
+ const skillFilePath = path.join(dirPath, entry.name, "SKILL.md");
107
+ try {
108
+ await fs.access(skillFilePath);
109
+ skills.add(`${entry.name}/SKILL.md`);
110
+ }
111
+ catch {
112
+ // Ignore
113
+ }
72
114
  }
73
115
  }
74
116
  }
75
- return skills;
76
- }
77
- catch {
78
- return [];
117
+ catch {
118
+ // Ignore
119
+ }
120
+ };
121
+ // Check custom
122
+ const config = await loadConfig();
123
+ if (config.customTemplatesPath) {
124
+ const customSkillsDir = path.resolve(process.cwd(), config.customTemplatesPath, relativeSkillsDir);
125
+ await checkDir(customSkillsDir);
79
126
  }
127
+ // Check default
128
+ const defaultSkillsDir = path.join(TEMPLATES_DIR, stack, "skills");
129
+ await checkDir(defaultSkillsDir);
130
+ return Array.from(skills);
80
131
  }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "agent-workflow-kit-cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "AI-Ready Repository Workflow Generator & Guideline Optimizer for Codex and Antigravity",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "bin": {
8
- "agent-workflow-kit": "./dist/index.js"
8
+ "agent-workflow-kit": "./dist/index.js",
9
+ "agent-workflow-kit-cli": "./dist/index.js"
9
10
  },
10
11
  "files": [
11
12
  "dist",
@@ -0,0 +1,12 @@
1
+ # Agent Workflow Kit - IDE System Instructions
2
+
3
+ You are an AI assistant ({{#if (eq agent "antigravity")}}Antigravity{{else}}{{#if (eq agent "codex")}}Codex{{else}}Antigravity / Codex{{/if}}{{/if}}) coding inside this repository. You MUST follow these repository standards for every query:
4
+
5
+ 1. Always check for and follow {{#if (eq agent "antigravity")}}[GEMINI.md](file:///GEMINI.md){{else}}{{#if (eq agent "codex")}}[AGENTS.md](file:///AGENTS.md){{else}}[GEMINI.md](file:///GEMINI.md) (if you are Antigravity) or [AGENTS.md](file:///AGENTS.md) (if you are Codex){{/if}}{{/if}} at the workspace root (or current module workspace) before executing any task.
6
+ 2. Follow the strict 5-Step Execution Workflow:
7
+ - Step 1: Research & Context Analysis (scan workspace, read relevant rules in `.agents/rules/` matching the active stack, e.g. `react-style.md` for React. Do not scan irrelevant files).
8
+ - Step 2: Formulate Implementation Plan (list files to create, modify, or delete).
9
+ - Step 3: Implementation & Clean Coding (maintain type-safety, styles, and layering).
10
+ - Step 4: Verification (run validation commands: compile, lint, test, build).
11
+ - Step 5: Self-Review & QA (review diff, clean logs, summarize edits).
12
+ 3. Check and execute custom skills defined inside `.agents/skills/` (like `/spring-feature` or `/react-feature`) by reading their `SKILL.md` instruction file.