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,95 @@
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 { WorkflowRegistry } from "../../core/awos/registry.js";
9
+ import { WorkflowRuntime } from "../../core/awos/runtime.js";
10
+ const DEFAULT_WORKFLOW_DIR = ".agents/workflows";
11
+ export async function listWorkflows() {
12
+ const cwd = process.cwd();
13
+ console.log(chalk.bold.cyan("\nšŸ“‹ Discovered AWOS Workflow Packs"));
14
+ console.log(chalk.dim("------------------------------------------"));
15
+ const dir = path.join(cwd, DEFAULT_WORKFLOW_DIR);
16
+ const list = await WorkflowRegistry.discover(dir);
17
+ if (list.length === 0) {
18
+ console.log(chalk.gray(`No workflows found under '${DEFAULT_WORKFLOW_DIR}/'.`));
19
+ return;
20
+ }
21
+ for (const wf of list) {
22
+ console.log(`${chalk.bold.green(`- ${wf.id}`)} v${wf.version} : ${wf.name}`);
23
+ console.log(chalk.gray(` ${wf.description}`));
24
+ console.log(chalk.gray(` Roles: ${wf.requiredRoles.join(", ")} | Architectures: ${wf.supportedArchitectures.join(", ")}\n`));
25
+ }
26
+ }
27
+ export async function showWorkflow(id) {
28
+ const cwd = process.cwd();
29
+ const dir = path.join(cwd, DEFAULT_WORKFLOW_DIR);
30
+ const wf = await WorkflowRegistry.load(id, dir);
31
+ console.log(chalk.bold.cyan(`\nšŸ” Workflow Details: ${wf.id}`));
32
+ console.log(chalk.dim("------------------------------------------"));
33
+ console.log(`${chalk.bold("Name:")} ${wf.name}`);
34
+ console.log(`${chalk.bold("Version:")} ${wf.version}`);
35
+ console.log(`${chalk.bold("Description:")} ${wf.description}`);
36
+ console.log(`${chalk.bold("Architectures:")} ${wf.supportedArchitectures.join(", ")}`);
37
+ console.log(`${chalk.bold("Required Roles:")} ${wf.requiredRoles.join(", ")}`);
38
+ console.log(chalk.dim("------------------------------------------"));
39
+ console.log(chalk.bold("Graph Execution Steps:"));
40
+ for (const node of wf.graph.nodes) {
41
+ const roleStr = node.role ? ` (Role: ${node.role})` : "";
42
+ const execStr = node.executor ? ` [Executor: ${node.executor}]` : "";
43
+ console.log(` - [${node.type.toUpperCase()}] ${chalk.green(node.id)}: "${node.name}"${roleStr}${execStr}`);
44
+ }
45
+ console.log(chalk.dim("------------------------------------------\n"));
46
+ }
47
+ export async function validateWorkflow(id) {
48
+ const cwd = process.cwd();
49
+ const dir = path.join(cwd, DEFAULT_WORKFLOW_DIR);
50
+ try {
51
+ const wf = await WorkflowRegistry.load(id, dir);
52
+ WorkflowRegistry.validate(wf);
53
+ console.log(chalk.bold.green(`\nāœ”ļø Workflow '${id}' is structurally valid and acyclic!`));
54
+ }
55
+ catch (err) {
56
+ console.error(chalk.bold.red(`\nāŒ Workflow '${id}' is invalid:`));
57
+ console.error(chalk.red(` ${err instanceof Error ? err.message : String(err)}`));
58
+ process.exit(1);
59
+ }
60
+ }
61
+ export async function runWorkflowById(id, options) {
62
+ const cwd = process.cwd();
63
+ const dir = path.join(cwd, DEFAULT_WORKFLOW_DIR);
64
+ const wf = await WorkflowRegistry.load(id, dir);
65
+ let inputs = {};
66
+ if (options.inputs) {
67
+ try {
68
+ const inputsPath = path.resolve(cwd, options.inputs);
69
+ const content = await fs.readFile(inputsPath, "utf8");
70
+ inputs = JSON.parse(content);
71
+ }
72
+ catch {
73
+ try {
74
+ inputs = JSON.parse(options.inputs);
75
+ }
76
+ catch (err) {
77
+ throw new Error(`Failed to parse inputs parameters: ${err instanceof Error ? err.message : String(err)}`);
78
+ }
79
+ }
80
+ }
81
+ console.log(chalk.bold.cyan(`\nšŸš€ Executing Discovered Workflow: ${wf.id}`));
82
+ console.log(chalk.dim("------------------------------------------"));
83
+ const runtime = new WorkflowRuntime(cwd);
84
+ const result = await runtime.run(wf.graph, inputs, { dryRun: options.dryRun });
85
+ console.log(chalk.dim("\n------------------------------------------"));
86
+ if (result.status === "SUCCESS") {
87
+ console.log(chalk.bold.green(`āœ”ļø Workflow execution succeeded! Run ID: ${result.runId}`));
88
+ }
89
+ else if (result.status === "PAUSED") {
90
+ console.log(chalk.bold.yellow(`āš ļø Workflow execution paused. Resume using: awk resume ${result.runId}`));
91
+ }
92
+ else {
93
+ console.log(chalk.bold.red(`āŒ Workflow execution failed. Run ID: ${result.runId}`));
94
+ }
95
+ }
package/dist/cli/index.js CHANGED
@@ -9,12 +9,17 @@ import { runAdd } from "./commands/add.js";
9
9
  import { runSync } from "./commands/sync.js";
10
10
  import { runDoctor } from "./commands/doctor.js";
11
11
  import { runExport } from "./commands/export.js";
12
+ import { runWorkflowCommand, resumeWorkflowCommand } from "./commands/run.js";
13
+ import { runProfileCheck } from "./commands/profile.js";
14
+ import { listWorkflows, showWorkflow, validateWorkflow, runWorkflowById } from "./commands/workflow.js";
15
+ import { listRoles, showRole, validateRoles } from "./commands/role.js";
16
+ import { createAdrCommand, listAdrsCommand, showAdrCommand, searchAdrsCommand } from "./commands/adr.js";
12
17
  const program = new Command();
13
18
  export function runCli() {
14
19
  program
15
20
  .name("agent-workflow-kit")
16
21
  .description("Generate AI coding workflows/rules/templates for Codex and Antigravity")
17
- .version("1.2.0");
22
+ .version("1.3.0");
18
23
  program
19
24
  .command("init")
20
25
  .description("Initialize agent guidelines and skills for the repository")
@@ -75,14 +80,200 @@ export function runCli() {
75
80
  .command("export <target>")
76
81
  .description("Export custom workflows/skills for the target agent (e.g., 'antigravity')")
77
82
  .option("--no-clipboard", "Do not copy the exported instructions to clipboard", false)
83
+ .option("-o, --output <file>", "Write the exported guidelines to a specific file")
78
84
  .action(async (target, options) => {
79
85
  try {
80
- await runExport(target, { clipboard: options.clipboard });
86
+ await runExport(target, { clipboard: options.clipboard, output: options.output });
81
87
  }
82
88
  catch (err) {
83
89
  console.error(chalk.red(`Error running export: ${err instanceof Error ? err.message : String(err)}`));
84
90
  process.exit(1);
85
91
  }
86
92
  });
93
+ program
94
+ .command("run <workflow>")
95
+ .description("Execute an AWOS graph workflow")
96
+ .option("--inputs <inputs>", "Path to input JSON file or inline JSON string")
97
+ .option("--dry-run", "Execute workflow steps in simulation mode", false)
98
+ .action(async (workflow, options) => {
99
+ try {
100
+ await runWorkflowCommand(workflow, options);
101
+ }
102
+ catch (err) {
103
+ console.error(chalk.red(`Error running workflow: ${err instanceof Error ? err.message : String(err)}`));
104
+ process.exit(1);
105
+ }
106
+ });
107
+ program
108
+ .command("resume <runId>")
109
+ .description("Resume a suspended AWOS workflow")
110
+ .action(async (runId) => {
111
+ try {
112
+ await resumeWorkflowCommand(runId);
113
+ }
114
+ catch (err) {
115
+ console.error(chalk.red(`Error resuming workflow: ${err instanceof Error ? err.message : String(err)}`));
116
+ process.exit(1);
117
+ }
118
+ });
119
+ program
120
+ .command("profile")
121
+ .description("Validate directory architecture structure and rules boundaries")
122
+ .option("--profile <profile>", "Target profile name (e.g. clean-architecture)")
123
+ .action(async (options) => {
124
+ try {
125
+ await runProfileCheck(options);
126
+ }
127
+ catch (err) {
128
+ console.error(chalk.red(`Error validation profile rules: ${err instanceof Error ? err.message : String(err)}`));
129
+ process.exit(1);
130
+ }
131
+ });
132
+ // --- Workflow Group Subcommands ---
133
+ const workflowCmd = program.command("workflow").description("Manage and execute AWOS workflow packs");
134
+ workflowCmd
135
+ .command("list")
136
+ .description("List discovered workflow packs")
137
+ .action(async () => {
138
+ try {
139
+ await listWorkflows();
140
+ }
141
+ catch (err) {
142
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
143
+ process.exit(1);
144
+ }
145
+ });
146
+ workflowCmd
147
+ .command("show <id>")
148
+ .description("Show workflow pack steps and metadata")
149
+ .action(async (id) => {
150
+ try {
151
+ await showWorkflow(id);
152
+ }
153
+ catch (err) {
154
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
155
+ process.exit(1);
156
+ }
157
+ });
158
+ workflowCmd
159
+ .command("validate <id>")
160
+ .description("Validate a workflow pack schema and graph cyclic constraints")
161
+ .action(async (id) => {
162
+ try {
163
+ await validateWorkflow(id);
164
+ }
165
+ catch (err) {
166
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
167
+ process.exit(1);
168
+ }
169
+ });
170
+ workflowCmd
171
+ .command("run <id>")
172
+ .description("Run a registered workflow pack by ID")
173
+ .option("--inputs <inputs>", "Path to inputs parameter JSON file or inline string")
174
+ .option("--dry-run", "Run nodes in dry-run mode", false)
175
+ .action(async (id, options) => {
176
+ try {
177
+ await runWorkflowById(id, options);
178
+ }
179
+ catch (err) {
180
+ console.error(chalk.red(`Error running workflow: ${err instanceof Error ? err.message : String(err)}`));
181
+ process.exit(1);
182
+ }
183
+ });
184
+ // --- Role Group Subcommands ---
185
+ const roleCmd = program.command("role").description("Manage agent role profiles");
186
+ roleCmd
187
+ .command("list")
188
+ .description("List all discovered role profiles in catalog")
189
+ .action(async () => {
190
+ try {
191
+ await listRoles();
192
+ }
193
+ catch (err) {
194
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
195
+ process.exit(1);
196
+ }
197
+ });
198
+ roleCmd
199
+ .command("show <id>")
200
+ .description("Show agent role details, checklists and inputs schema")
201
+ .action(async (id) => {
202
+ try {
203
+ await showRole(id);
204
+ }
205
+ catch (err) {
206
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
207
+ process.exit(1);
208
+ }
209
+ });
210
+ roleCmd
211
+ .command("validate")
212
+ .description("Validate all roles catalog files schema parameters")
213
+ .action(async () => {
214
+ try {
215
+ await validateRoles();
216
+ }
217
+ catch (err) {
218
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
219
+ process.exit(1);
220
+ }
221
+ });
222
+ // --- ADR Group Subcommands ---
223
+ const adrCmd = program.command("adr").description("Manage Architectural Decision Records");
224
+ adrCmd
225
+ .command("create")
226
+ .description("Create a new numbered architectural decision record (ADR)")
227
+ .option("--title <title>", "ADR decision title")
228
+ .option("--status <status>", "Initial status: proposed | accepted | rejected | superseded", "proposed")
229
+ .option("--context <context>", "Context background explanation")
230
+ .option("--decision <decision>", "Architectural decision detail statement")
231
+ .option("--consequences <consequences>", "Repercussions of decision taken")
232
+ .option("--decision-maker <maker>", "Role or name of decider", "AWOS System")
233
+ .action(async (options) => {
234
+ try {
235
+ await createAdrCommand(options);
236
+ }
237
+ catch (err) {
238
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
239
+ process.exit(1);
240
+ }
241
+ });
242
+ adrCmd
243
+ .command("list")
244
+ .description("List all saved ADR documents")
245
+ .action(async () => {
246
+ try {
247
+ await listAdrsCommand();
248
+ }
249
+ catch (err) {
250
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
251
+ process.exit(1);
252
+ }
253
+ });
254
+ adrCmd
255
+ .command("show <id>")
256
+ .description("Display details of a numbered ADR document")
257
+ .action(async (id) => {
258
+ try {
259
+ await showAdrCommand(id);
260
+ }
261
+ catch (err) {
262
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
263
+ process.exit(1);
264
+ }
265
+ });
266
+ adrCmd
267
+ .command("search <keyword>")
268
+ .description("Search all ADR documents for keyword text matches")
269
+ .action(async (keyword) => {
270
+ try {
271
+ await searchAdrsCommand(keyword);
272
+ }
273
+ catch (err) {
274
+ console.error(chalk.red(`Error: ${err instanceof Error ? err.message : String(err)}`));
275
+ process.exit(1);
276
+ }
277
+ });
87
278
  program.parse(process.argv);
88
279
  }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ import { promises as fs } from "fs";
6
+ import path from "path";
7
+ import Handlebars from "handlebars";
8
+ const ADR_DIR = "docs/adr";
9
+ const DEFAULT_TEMPLATE = `---
10
+ id: {{id}}
11
+ title: {{{title}}}
12
+ status: {{status}}
13
+ date: {{date}}
14
+ {{#if metadata.decisionMakerRole}}
15
+ decisionMakerRole: {{metadata.decisionMakerRole}}
16
+ {{/if}}
17
+ {{#if metadata.affectedModules}}
18
+ affectedModules: {{#each metadata.affectedModules}}{{#if @index}}, {{/if}}{{this}}{{/each}}
19
+ {{/if}}
20
+ ---
21
+
22
+ # {{{title}}}
23
+
24
+ ## Context
25
+ {{{context}}}
26
+
27
+ ## Decision
28
+ {{{decision}}}
29
+
30
+ ## Consequences
31
+ {{{consequences}}}
32
+ `;
33
+ export class ADRService {
34
+ /**
35
+ * Helper to parse a markdown ADR file back into a structured ADRDefinition.
36
+ */
37
+ static parseMarkdown(content, id) {
38
+ const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;
39
+ const match = content.match(frontmatterRegex);
40
+ let title = "Untitled ADR";
41
+ let status = 'proposed';
42
+ let date = new Date().toISOString().split("T")[0];
43
+ let body = content;
44
+ const metadata = {};
45
+ if (match) {
46
+ const frontmatter = match[1];
47
+ body = match[2];
48
+ const lines = frontmatter.split("\n");
49
+ for (const line of lines) {
50
+ const trimmed = line.trim();
51
+ if (!trimmed || trimmed.startsWith("#"))
52
+ continue;
53
+ const eqIdx = trimmed.indexOf(":");
54
+ if (eqIdx !== -1) {
55
+ const key = trimmed.substring(0, eqIdx).trim();
56
+ const value = trimmed.substring(eqIdx + 1).trim();
57
+ if (key === "title")
58
+ title = value.replace(/^['"]|['"]$/g, "");
59
+ else if (key === "status") {
60
+ const parsedStatus = value.replace(/^['"]|['"]$/g, "");
61
+ if (['proposed', 'accepted', 'rejected', 'superseded'].includes(parsedStatus)) {
62
+ status = parsedStatus;
63
+ }
64
+ }
65
+ else if (key === "date")
66
+ date = value;
67
+ else if (key === "decisionMakerRole")
68
+ metadata.decisionMakerRole = value;
69
+ else if (key === "affectedModules") {
70
+ metadata.affectedModules = value.split(",").map((s) => s.trim());
71
+ }
72
+ }
73
+ }
74
+ }
75
+ // Extract headers for sections (Context, Decision, Consequences)
76
+ let context = "";
77
+ let decision = "";
78
+ let consequences = "";
79
+ const contextMatch = body.match(/##\s+Context\r?\n([\s\S]*?)(?=##\s+Decision|##\s+Consequences|$)/i);
80
+ const decisionMatch = body.match(/##\s+Decision\r?\n([\s\S]*?)(?=##\s+Context|##\s+Consequences|$)/i);
81
+ const consequencesMatch = body.match(/##\s+Consequences\r?\n([\s\S]*?)(?=##\s+Context|##\s+Decision|$)/i);
82
+ if (contextMatch)
83
+ context = contextMatch[1].trim();
84
+ if (decisionMatch)
85
+ decision = decisionMatch[1].trim();
86
+ if (consequencesMatch)
87
+ consequences = consequencesMatch[1].trim();
88
+ // In case of headers missing
89
+ if (!context && !decision && !consequences) {
90
+ context = body.trim();
91
+ }
92
+ return {
93
+ id,
94
+ title,
95
+ date,
96
+ status,
97
+ context,
98
+ decision,
99
+ consequences,
100
+ metadata,
101
+ };
102
+ }
103
+ /**
104
+ * Scans docs/adr/ and calculates the next incremental number (e.g. ADR-0003).
105
+ */
106
+ static async getNextId(workspaceRoot) {
107
+ const fullDir = path.join(workspaceRoot, ADR_DIR);
108
+ try {
109
+ const files = await fs.readdir(fullDir);
110
+ let maxNum = 0;
111
+ for (const file of files) {
112
+ const match = file.match(/^ADR-(\d{4})\.md$/);
113
+ if (match) {
114
+ const num = parseInt(match[1], 10);
115
+ if (num > maxNum) {
116
+ maxNum = num;
117
+ }
118
+ }
119
+ }
120
+ const nextNum = maxNum + 1;
121
+ return `ADR-${String(nextNum).padStart(4, "0")}`;
122
+ }
123
+ catch {
124
+ return "ADR-0001";
125
+ }
126
+ }
127
+ /**
128
+ * Generates a new ADR file in the docs/adr/ directory.
129
+ */
130
+ static async create(workspaceRoot, adr, templateString) {
131
+ const id = await ADRService.getNextId(workspaceRoot);
132
+ const date = new Date().toISOString().split("T")[0];
133
+ const fullDir = path.join(workspaceRoot, ADR_DIR);
134
+ const definition = {
135
+ ...adr,
136
+ id,
137
+ date,
138
+ };
139
+ const template = Handlebars.compile(templateString || DEFAULT_TEMPLATE);
140
+ const rendered = template(definition);
141
+ await fs.mkdir(fullDir, { recursive: true });
142
+ const filePath = path.join(fullDir, `${id}.md`);
143
+ await fs.writeFile(filePath, rendered, "utf8");
144
+ return definition;
145
+ }
146
+ /**
147
+ * Loads a specific ADR by ID.
148
+ */
149
+ static async load(workspaceRoot, id) {
150
+ const fullDir = path.join(workspaceRoot, ADR_DIR);
151
+ const filePath = path.join(fullDir, `${id}.md`);
152
+ try {
153
+ const content = await fs.readFile(filePath, "utf8");
154
+ return ADRService.parseMarkdown(content, id);
155
+ }
156
+ catch (err) {
157
+ throw new Error(`Failed to load ADR '${id}': ${err instanceof Error ? err.message : String(err)}`);
158
+ }
159
+ }
160
+ /**
161
+ * Lists all ADRs sorted by ID.
162
+ */
163
+ static async list(workspaceRoot) {
164
+ const fullDir = path.join(workspaceRoot, ADR_DIR);
165
+ const adrs = [];
166
+ try {
167
+ const files = await fs.readdir(fullDir);
168
+ for (const file of files) {
169
+ if (file.match(/^ADR-\d{4}\.md$/)) {
170
+ const id = path.basename(file, ".md");
171
+ const adr = await ADRService.load(workspaceRoot, id);
172
+ adrs.push(adr);
173
+ }
174
+ }
175
+ }
176
+ catch {
177
+ // Directory may not exist yet
178
+ }
179
+ return adrs.sort((a, b) => a.id.localeCompare(b.id));
180
+ }
181
+ /**
182
+ * Performs case-insensitive search across titles, contexts, decisions, and consequences.
183
+ */
184
+ static async search(workspaceRoot, keyword) {
185
+ const all = await ADRService.list(workspaceRoot);
186
+ const searchLower = keyword.toLowerCase();
187
+ return all.filter((adr) => {
188
+ return (adr.title.toLowerCase().includes(searchLower) ||
189
+ adr.context.toLowerCase().includes(searchLower) ||
190
+ adr.decision.toLowerCase().includes(searchLower) ||
191
+ adr.consequences.toLowerCase().includes(searchLower));
192
+ });
193
+ }
194
+ /**
195
+ * Updates status metadata parameter for an ADR.
196
+ */
197
+ static async updateStatus(workspaceRoot, id, newStatus) {
198
+ const adr = await ADRService.load(workspaceRoot, id);
199
+ adr.status = newStatus;
200
+ // Render it back
201
+ const template = Handlebars.compile(DEFAULT_TEMPLATE);
202
+ const rendered = template(adr);
203
+ const fullDir = path.join(workspaceRoot, ADR_DIR);
204
+ const filePath = path.join(fullDir, `${id}.md`);
205
+ await fs.writeFile(filePath, rendered, "utf8");
206
+ return adr;
207
+ }
208
+ }