agentic-forge 0.0.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.
Files changed (110) hide show
  1. package/.gitattributes +24 -0
  2. package/.github/workflows/ci.yml +70 -0
  3. package/.markdownlint-cli2.jsonc +16 -0
  4. package/.prettierignore +3 -0
  5. package/.prettierrc +6 -0
  6. package/.vscode/agentic-forge.code-workspace +26 -0
  7. package/CHANGELOG.md +100 -0
  8. package/CLAUDE.md +158 -0
  9. package/CONTRIBUTING.md +152 -0
  10. package/LICENSE +21 -0
  11. package/README.md +145 -0
  12. package/agentic-forge-banner.png +0 -0
  13. package/biome.json +21 -0
  14. package/package.json +5 -0
  15. package/scripts/copy-assets.js +21 -0
  16. package/src/agents/explorer.md +97 -0
  17. package/src/agents/reviewer.md +137 -0
  18. package/src/checkpoints/manager.ts +119 -0
  19. package/src/claude/.claude/skills/analyze/SKILL.md +241 -0
  20. package/src/claude/.claude/skills/analyze/references/bug.md +62 -0
  21. package/src/claude/.claude/skills/analyze/references/debt.md +76 -0
  22. package/src/claude/.claude/skills/analyze/references/doc.md +67 -0
  23. package/src/claude/.claude/skills/analyze/references/security.md +76 -0
  24. package/src/claude/.claude/skills/analyze/references/style.md +72 -0
  25. package/src/claude/.claude/skills/create-checkpoint/SKILL.md +88 -0
  26. package/src/claude/.claude/skills/create-log/SKILL.md +75 -0
  27. package/src/claude/.claude/skills/fix-analyze/SKILL.md +102 -0
  28. package/src/claude/.claude/skills/git-branch/SKILL.md +71 -0
  29. package/src/claude/.claude/skills/git-commit/SKILL.md +107 -0
  30. package/src/claude/.claude/skills/git-pr/SKILL.md +96 -0
  31. package/src/claude/.claude/skills/orchestrate/SKILL.md +120 -0
  32. package/src/claude/.claude/skills/sdlc-plan/SKILL.md +163 -0
  33. package/src/claude/.claude/skills/sdlc-plan/references/bug.md +115 -0
  34. package/src/claude/.claude/skills/sdlc-plan/references/chore.md +105 -0
  35. package/src/claude/.claude/skills/sdlc-plan/references/feature.md +130 -0
  36. package/src/claude/.claude/skills/sdlc-review/SKILL.md +215 -0
  37. package/src/claude/.claude/skills/workflow-builder/SKILL.md +185 -0
  38. package/src/claude/.claude/skills/workflow-builder/references/REFERENCE.md +487 -0
  39. package/src/claude/.claude/skills/workflow-builder/references/workflow-example.yaml +427 -0
  40. package/src/cli.ts +182 -0
  41. package/src/commands/config-cmd.ts +28 -0
  42. package/src/commands/index.ts +21 -0
  43. package/src/commands/init.ts +96 -0
  44. package/src/commands/release-notes.ts +85 -0
  45. package/src/commands/resume.ts +103 -0
  46. package/src/commands/run.ts +234 -0
  47. package/src/commands/shortcuts.ts +11 -0
  48. package/src/commands/skills-dir.ts +11 -0
  49. package/src/commands/status.ts +112 -0
  50. package/src/commands/update.ts +64 -0
  51. package/src/commands/version.ts +27 -0
  52. package/src/commands/workflows.ts +129 -0
  53. package/src/config.ts +129 -0
  54. package/src/console.ts +790 -0
  55. package/src/executor.ts +354 -0
  56. package/src/git/worktree.ts +236 -0
  57. package/src/logging/logger.ts +95 -0
  58. package/src/orchestrator.ts +815 -0
  59. package/src/parser.ts +225 -0
  60. package/src/progress.ts +306 -0
  61. package/src/prompts/agentic-system.md +31 -0
  62. package/src/ralph-loop.ts +260 -0
  63. package/src/renderer.ts +164 -0
  64. package/src/runner.ts +634 -0
  65. package/src/signal-manager.ts +55 -0
  66. package/src/steps/base.ts +71 -0
  67. package/src/steps/conditional-step.ts +144 -0
  68. package/src/steps/index.ts +15 -0
  69. package/src/steps/parallel-step.ts +213 -0
  70. package/src/steps/prompt-step.ts +121 -0
  71. package/src/steps/ralph-loop-step.ts +186 -0
  72. package/src/steps/serial-step.ts +84 -0
  73. package/src/templates/analysis/bug.md.j2 +35 -0
  74. package/src/templates/analysis/debt.md.j2 +38 -0
  75. package/src/templates/analysis/doc.md.j2 +45 -0
  76. package/src/templates/analysis/security.md.j2 +35 -0
  77. package/src/templates/analysis/style.md.j2 +44 -0
  78. package/src/templates/analysis-summary.md.j2 +58 -0
  79. package/src/templates/checkpoint.md.j2 +27 -0
  80. package/src/templates/implementation-report.md.j2 +81 -0
  81. package/src/templates/memory.md.j2 +16 -0
  82. package/src/templates/plan-bug.md.j2 +42 -0
  83. package/src/templates/plan-chore.md.j2 +27 -0
  84. package/src/templates/plan-feature.md.j2 +41 -0
  85. package/src/templates/progress.json.j2 +16 -0
  86. package/src/templates/ralph-report.md.j2 +45 -0
  87. package/src/types.ts +141 -0
  88. package/src/workflows/analyze-codebase-merge.yaml +328 -0
  89. package/src/workflows/analyze-codebase.yaml +196 -0
  90. package/src/workflows/analyze-single.yaml +56 -0
  91. package/src/workflows/demo.yaml +180 -0
  92. package/src/workflows/one-shot.yaml +54 -0
  93. package/src/workflows/plan-build-review.yaml +160 -0
  94. package/src/workflows/ralph-loop.yaml +73 -0
  95. package/tests/config.test.ts +219 -0
  96. package/tests/console.test.ts +506 -0
  97. package/tests/executor.test.ts +339 -0
  98. package/tests/init.test.ts +86 -0
  99. package/tests/logger.test.ts +110 -0
  100. package/tests/parser.test.ts +290 -0
  101. package/tests/progress.test.ts +345 -0
  102. package/tests/ralph-loop.test.ts +418 -0
  103. package/tests/renderer.test.ts +350 -0
  104. package/tests/runner.test.ts +497 -0
  105. package/tests/setup.test.ts +7 -0
  106. package/tests/signal-manager.test.ts +26 -0
  107. package/tests/steps.test.ts +412 -0
  108. package/tests/worktree.test.ts +411 -0
  109. package/tsconfig.json +18 -0
  110. package/vitest.config.ts +8 -0
@@ -0,0 +1,96 @@
1
+ /** Init and configure command handlers. */
2
+
3
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { getDefaultConfig, loadConfig } from "../config.js";
7
+ import { getBundledWorkflowsDir } from "./run.js";
8
+
9
+ export function cmdInit(options: { force?: boolean; listOnly?: boolean }): void {
10
+ const bundledDir = getBundledWorkflowsDir();
11
+ if (!existsSync(bundledDir)) {
12
+ process.stderr.write("Error: Bundled workflows directory not found.\n");
13
+ process.exit(1);
14
+ }
15
+
16
+ const bundledWorkflows = readdirSync(bundledDir)
17
+ .filter((f) => f.endsWith(".yaml"))
18
+ .sort();
19
+
20
+ if (bundledWorkflows.length === 0) {
21
+ process.stderr.write("No bundled workflows found.\n");
22
+ process.exit(1);
23
+ }
24
+
25
+ // List only mode
26
+ if (options.listOnly) {
27
+ process.stdout.write("Available bundled workflows:\n\n");
28
+ for (const wf of bundledWorkflows) {
29
+ process.stdout.write(` ${wf}\n`);
30
+ }
31
+ process.stdout.write("\nUse 'agentic-forge init' to copy these to agentic/workflows/\n");
32
+ return;
33
+ }
34
+
35
+ // Copy workflows to local directory
36
+ const targetDir = path.join(process.cwd(), "agentic", "workflows");
37
+ mkdirSync(targetDir, { recursive: true });
38
+
39
+ const copied: string[] = [];
40
+ const skipped: string[] = [];
41
+ for (const wf of bundledWorkflows) {
42
+ const targetPath = path.join(targetDir, wf);
43
+ if (existsSync(targetPath) && !options.force) {
44
+ skipped.push(wf);
45
+ } else {
46
+ copyFileSync(path.join(bundledDir, wf), targetPath);
47
+ copied.push(wf);
48
+ }
49
+ }
50
+
51
+ if (copied.length > 0) {
52
+ process.stdout.write(`Copied ${copied.length} workflow(s) to ${targetDir}/\n`);
53
+ for (const name of copied) {
54
+ process.stdout.write(` + ${name}\n`);
55
+ }
56
+ }
57
+
58
+ if (skipped.length > 0) {
59
+ process.stdout.write(`\nSkipped ${skipped.length} existing workflow(s):\n`);
60
+ for (const name of skipped) {
61
+ process.stdout.write(` - ${name}\n`);
62
+ }
63
+ process.stdout.write("\nUse --force to overwrite existing files.\n");
64
+ }
65
+
66
+ // Create config.json next to workflows
67
+ initConfig(path.join(process.cwd(), "agentic"), options.force ?? false);
68
+
69
+ if (copied.length > 0) {
70
+ process.stdout.write("\nYou can now run workflows with:\n");
71
+ process.stdout.write(" agentic-forge run agentic/workflows/<workflow>.yaml\n");
72
+ }
73
+ }
74
+
75
+ function initConfig(agenticDir: string, force: boolean): void {
76
+ const configPath = path.join(agenticDir, "config.json");
77
+ if (existsSync(configPath) && !force) {
78
+ process.stdout.write(`\nConfig already exists: ${configPath}\n`);
79
+ return;
80
+ }
81
+
82
+ mkdirSync(agenticDir, { recursive: true });
83
+ const config = getDefaultConfig();
84
+ writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
85
+ process.stdout.write(`\nCreated config: ${configPath}\n`);
86
+ }
87
+
88
+ export function cmdConfigure(): void {
89
+ const config = loadConfig();
90
+ process.stdout.write("Agentic Workflows Configuration\n");
91
+ process.stdout.write(`${"=".repeat(40)}\n`);
92
+ process.stdout.write("\nCurrent settings:\n");
93
+ process.stdout.write(`${JSON.stringify(config, null, 2)}\n`);
94
+ process.stdout.write("\nUse 'agentic-forge config set <key> <value>' to modify settings.\n");
95
+ process.stdout.write("Example: agentic-forge config set defaults.maxRetry 5\n");
96
+ }
@@ -0,0 +1,85 @@
1
+ /** Release notes command handler. */
2
+
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+
9
+ export function cmdReleaseNotes(options: {
10
+ specificVersion?: string;
11
+ latest?: boolean;
12
+ }): void {
13
+ let changelogPath: string | null = null;
14
+
15
+ // Try to find CHANGELOG.md relative to the package
16
+ const candidate = path.join(__dirname, "..", "..", "CHANGELOG.md");
17
+ if (existsSync(candidate)) {
18
+ changelogPath = candidate;
19
+ }
20
+
21
+ // Fallback: try current directory
22
+ if (changelogPath === null) {
23
+ const cwdCandidate = path.join(process.cwd(), "CHANGELOG.md");
24
+ if (existsSync(cwdCandidate)) {
25
+ changelogPath = cwdCandidate;
26
+ }
27
+ }
28
+
29
+ if (changelogPath === null) {
30
+ process.stdout.write("CHANGELOG.md not found\n");
31
+ return;
32
+ }
33
+
34
+ const content = readFileSync(changelogPath, "utf-8");
35
+
36
+ if (options.specificVersion) {
37
+ const section = extractVersionSection(content, options.specificVersion);
38
+ if (section) {
39
+ process.stdout.write(`${section.trim()}\n`);
40
+ } else {
41
+ process.stdout.write(`Version ${options.specificVersion} not found in CHANGELOG.md\n`);
42
+ }
43
+ return;
44
+ }
45
+
46
+ if (options.latest) {
47
+ const section = extractLatestVersion(content);
48
+ if (section) {
49
+ process.stdout.write(`${section.trim()}\n`);
50
+ } else {
51
+ process.stdout.write("No version information found in CHANGELOG.md\n");
52
+ }
53
+ return;
54
+ }
55
+
56
+ process.stdout.write(content);
57
+ }
58
+
59
+ function extractVersionSection(content: string, version: string): string | null {
60
+ const escaped = version.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
61
+ const pattern = new RegExp(`^## \\[${escaped}\\].*$`, "m");
62
+ const match = pattern.exec(content);
63
+
64
+ if (!match) {
65
+ return null;
66
+ }
67
+
68
+ const start = match.index;
69
+ const nextPattern = /^## \[[\d.]+\]/m;
70
+ const nextMatch = nextPattern.exec(content.slice(match.index + match[0].length));
71
+ const end = nextMatch ? match.index + match[0].length + nextMatch.index : content.length;
72
+
73
+ return content.slice(start, end);
74
+ }
75
+
76
+ function extractLatestVersion(content: string): string | null {
77
+ const pattern = /^## \[([\d.]+)\].*$/m;
78
+ const match = pattern.exec(content);
79
+
80
+ if (!match) {
81
+ return null;
82
+ }
83
+
84
+ return extractVersionSection(content, match[1]);
85
+ }
@@ -0,0 +1,103 @@
1
+ /** Resume command handler. */
2
+
3
+ import { existsSync } from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { WORKFLOW_STATUS, loadProgress, prepareForResume, saveProgress } from "../progress.js";
7
+ import { discoverWorkflow } from "./run.js";
8
+
9
+ export async function cmdResume(options: {
10
+ workflowId: string;
11
+ terminalOutput?: string;
12
+ }): Promise<void> {
13
+ const { WorkflowExecutor } = await import("../executor.js");
14
+ const { WorkflowParser, WorkflowParseError } = await import("../parser.js");
15
+
16
+ const progress = loadProgress(options.workflowId);
17
+ if (progress === null) {
18
+ process.stderr.write(`Error: Workflow not found: ${options.workflowId}\n`);
19
+ process.exit(1);
20
+ }
21
+
22
+ // Only reject completed workflows
23
+ if (progress.status === WORKFLOW_STATUS.COMPLETED) {
24
+ process.stderr.write(
25
+ `Error: Cannot resume a completed workflow (status: '${progress.status}')\n`,
26
+ );
27
+ process.exit(1);
28
+ }
29
+
30
+ // Resolve workflow YAML file
31
+ let workflowPath: string | null = null;
32
+
33
+ // Try stored workflow_file first
34
+ if (progress.workflowFile) {
35
+ if (existsSync(progress.workflowFile)) {
36
+ workflowPath = progress.workflowFile;
37
+ }
38
+ }
39
+
40
+ // Fall back to discovery by workflow name
41
+ if (workflowPath === null) {
42
+ const [discovered] = discoverWorkflow(progress.workflowName);
43
+ if (discovered !== null) {
44
+ workflowPath = discovered;
45
+ }
46
+ }
47
+
48
+ if (workflowPath === null) {
49
+ process.stderr.write(
50
+ `Error: Cannot find workflow file for '${progress.workflowName}'.\nProvide the workflow YAML at one of the standard locations or re-run with 'agentic-forge run <path>'.\n`,
51
+ );
52
+ process.exit(1);
53
+ }
54
+
55
+ // Parse workflow
56
+ let workflow: import("../types.js").WorkflowDefinition;
57
+ try {
58
+ const parser = new WorkflowParser();
59
+ workflow = parser.parseFile(workflowPath);
60
+ } catch (e) {
61
+ if (e instanceof WorkflowParseError) {
62
+ process.stderr.write(`Error parsing workflow: ${e.message}\n`);
63
+ process.exit(1);
64
+ }
65
+ throw e;
66
+ }
67
+
68
+ // Normalize state for resume
69
+ prepareForResume(progress);
70
+ saveProgress(progress);
71
+
72
+ // Execute with resume
73
+ const executor = new WorkflowExecutor();
74
+ try {
75
+ let terminalOutput = "base";
76
+ if (options.terminalOutput != null) {
77
+ terminalOutput = options.terminalOutput;
78
+ } else if (workflow.settings?.terminalOutput) {
79
+ terminalOutput = workflow.settings.terminalOutput;
80
+ }
81
+
82
+ const result = await executor.run(
83
+ workflow,
84
+ undefined,
85
+ null,
86
+ terminalOutput,
87
+ path.resolve(workflowPath),
88
+ progress,
89
+ );
90
+ process.stdout.write(`\nWorkflow ${result.status}: ${result.workflowId}\n`);
91
+ if (result.errors && result.errors.length > 0) {
92
+ process.stdout.write("\nErrors:\n");
93
+ for (const error of result.errors) {
94
+ process.stdout.write(
95
+ ` - ${(error as Record<string, string>).step}: ${(error as Record<string, string>).error}\n`,
96
+ );
97
+ }
98
+ }
99
+ } catch (e) {
100
+ process.stderr.write(`Error running workflow: ${(e as Error).message}\n`);
101
+ process.exit(1);
102
+ }
103
+ }
@@ -0,0 +1,234 @@
1
+ /** Run and resume command handlers with workflow discovery. */
2
+
3
+ import { existsSync, readdirSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+
10
+ export function getBundledWorkflowsDir(): string {
11
+ return path.join(__dirname, "..", "workflows");
12
+ }
13
+
14
+ export function getUserWorkflowsDir(): string {
15
+ if (process.platform === "win32") {
16
+ const base = process.env.APPDATA ?? path.join(homedir(), "AppData", "Roaming");
17
+ return path.join(base, "agentic-forge", "workflows");
18
+ }
19
+ const base = process.env.XDG_CONFIG_HOME ?? path.join(homedir(), ".config");
20
+ return path.join(base, "agentic-forge", "workflows");
21
+ }
22
+
23
+ export function getProjectWorkflowsDir(): string {
24
+ return path.join(process.cwd(), "agentic", "workflows");
25
+ }
26
+
27
+ export function discoverWorkflow(name: string): [string | null, string] {
28
+ const fileName = name.endsWith(".yaml") ? name : `${name}.yaml`;
29
+
30
+ const searchLocations: [string, string][] = [
31
+ [getProjectWorkflowsDir(), "project-local"],
32
+ [getUserWorkflowsDir(), "user-global"],
33
+ [getBundledWorkflowsDir(), "bundled"],
34
+ ];
35
+
36
+ for (const [directory, locationType] of searchLocations) {
37
+ const workflowPath = path.join(directory, fileName);
38
+ if (existsSync(workflowPath)) {
39
+ return [workflowPath, locationType];
40
+ }
41
+ }
42
+
43
+ return [null, "not found"];
44
+ }
45
+
46
+ export function listAvailableWorkflows(): [string, string, string][] {
47
+ const workflows: [string, string, string][] = [];
48
+
49
+ const searchLocations: [string, string][] = [
50
+ [getProjectWorkflowsDir(), "project-local"],
51
+ [getUserWorkflowsDir(), "user-global"],
52
+ [getBundledWorkflowsDir(), "bundled"],
53
+ ];
54
+
55
+ for (const [directory, locationType] of searchLocations) {
56
+ if (existsSync(directory)) {
57
+ const files = readdirSync(directory)
58
+ .filter((f) => f.endsWith(".yaml"))
59
+ .sort();
60
+ for (const file of files) {
61
+ const name = path.basename(file, ".yaml");
62
+ workflows.push([name, path.join(directory, file), locationType]);
63
+ }
64
+ }
65
+ }
66
+
67
+ return workflows;
68
+ }
69
+
70
+ export function resolveWorkflowPath(workflowArg: string): [string, string] {
71
+ // Check if it's an absolute path
72
+ if (path.isAbsolute(workflowArg)) {
73
+ return [workflowArg, "absolute"];
74
+ }
75
+
76
+ // Check if it exists as a relative path from cwd
77
+ const localPath = path.resolve(process.cwd(), workflowArg);
78
+ if (existsSync(localPath)) {
79
+ return [localPath, "relative"];
80
+ }
81
+
82
+ // If the input looks like a bare name (no path separators), try discovery
83
+ if (!workflowArg.includes("/") && !workflowArg.includes("\\")) {
84
+ const [discovered, locationType] = discoverWorkflow(workflowArg);
85
+ if (discovered) {
86
+ return [discovered, locationType];
87
+ }
88
+ }
89
+
90
+ // Fallback: return the resolved path (will fail with appropriate error)
91
+ return [path.resolve(workflowArg), "not found"];
92
+ }
93
+
94
+ export async function cmdRun(options: {
95
+ workflow?: string;
96
+ listWorkflows?: boolean;
97
+ vars?: string[];
98
+ fromStep?: string;
99
+ terminalOutput?: string;
100
+ }): Promise<void> {
101
+ // Handle --list flag
102
+ if (options.listWorkflows) {
103
+ process.stdout.write("Available workflows:\n\n");
104
+ const workflows = listAvailableWorkflows();
105
+
106
+ if (workflows.length === 0) {
107
+ process.stdout.write("No workflows found.\n");
108
+ process.stdout.write("\nSearched locations:\n");
109
+ process.stdout.write(` - Project: ${getProjectWorkflowsDir()}\n`);
110
+ process.stdout.write(` - User: ${getUserWorkflowsDir()}\n`);
111
+ process.stdout.write(` - Bundled: ${getBundledWorkflowsDir()}\n`);
112
+ return;
113
+ }
114
+
115
+ // Group by location
116
+ const byLocation: Record<string, [string, string][]> = {};
117
+ for (const [name, wfPath, location] of workflows) {
118
+ if (!byLocation[location]) {
119
+ byLocation[location] = [];
120
+ }
121
+ byLocation[location].push([name, wfPath]);
122
+ }
123
+
124
+ for (const location of ["project-local", "user-global", "bundled"]) {
125
+ if (byLocation[location]) {
126
+ const label = location.replace("-", " ").replace(/\b\w/g, (c) => c.toUpperCase());
127
+ process.stdout.write(`${label}:\n`);
128
+ for (const [name] of byLocation[location]) {
129
+ process.stdout.write(` ${name}\n`);
130
+ }
131
+ process.stdout.write("\n");
132
+ }
133
+ }
134
+
135
+ process.stdout.write(`Total: ${workflows.length} workflow(s)\n`);
136
+ process.stdout.write("\nUsage: agentic-forge run <workflow-name>\n");
137
+ return;
138
+ }
139
+
140
+ // Validate workflow argument is provided
141
+ if (!options.workflow) {
142
+ process.stderr.write("Error: workflow name or path is required\n");
143
+ process.stderr.write("Use 'agentic-forge run --list' to see available workflows\n");
144
+ process.exit(1);
145
+ }
146
+
147
+ const { WorkflowExecutor } = await import("../executor.js");
148
+ const { WorkflowParser, WorkflowParseError } = await import("../parser.js");
149
+
150
+ const [workflowPath, locationType] = resolveWorkflowPath(options.workflow);
151
+
152
+ if (!existsSync(workflowPath)) {
153
+ process.stderr.write(`Error: Workflow not found: ${options.workflow}\n`);
154
+ process.stderr.write("\nAvailable workflows:\n");
155
+
156
+ const workflows = listAvailableWorkflows();
157
+ if (workflows.length > 0) {
158
+ for (const [name, , location] of workflows.slice(0, 10)) {
159
+ process.stderr.write(` ${name} (${location})\n`);
160
+ }
161
+ if (workflows.length > 10) {
162
+ process.stderr.write(` ... and ${workflows.length - 10} more\n`);
163
+ }
164
+ } else {
165
+ process.stderr.write(" (no workflows found)\n");
166
+ }
167
+
168
+ process.stderr.write("\nUse 'agentic-forge run --list' to see all workflows.\n");
169
+ process.stderr.write("Use 'agentic-forge init' to copy bundled workflows locally.\n");
170
+ process.exit(1);
171
+ }
172
+
173
+ // Show which workflow is being used
174
+ if (["project-local", "user-global", "bundled"].includes(locationType)) {
175
+ process.stdout.write(`Using ${locationType} workflow: ${path.basename(workflowPath)}\n`);
176
+ }
177
+
178
+ // Parse variables
179
+ const variables: Record<string, string> = {};
180
+ if (options.vars) {
181
+ for (const v of options.vars) {
182
+ if (!v.includes("=")) {
183
+ process.stderr.write(`Error: Invalid variable format: ${v}\n`);
184
+ process.stderr.write("Expected format: KEY=VALUE\n");
185
+ process.exit(1);
186
+ }
187
+ const eqIndex = v.indexOf("=");
188
+ variables[v.slice(0, eqIndex)] = v.slice(eqIndex + 1);
189
+ }
190
+ }
191
+
192
+ let workflow: import("../types.js").WorkflowDefinition;
193
+ try {
194
+ const parser = new WorkflowParser();
195
+ workflow = parser.parseFile(workflowPath);
196
+ } catch (e) {
197
+ if (e instanceof WorkflowParseError) {
198
+ process.stderr.write(`Error parsing workflow: ${e.message}\n`);
199
+ process.exit(1);
200
+ }
201
+ throw e;
202
+ }
203
+
204
+ const executor = new WorkflowExecutor();
205
+ try {
206
+ // Resolve terminal_output: CLI override > workflow settings > default "base"
207
+ let terminalOutput = "base";
208
+ if (options.terminalOutput != null) {
209
+ terminalOutput = options.terminalOutput;
210
+ } else if (workflow.settings?.terminalOutput) {
211
+ terminalOutput = workflow.settings.terminalOutput;
212
+ }
213
+
214
+ const progress = await executor.run(
215
+ workflow,
216
+ variables,
217
+ options.fromStep ?? null,
218
+ terminalOutput,
219
+ path.resolve(workflowPath),
220
+ );
221
+ process.stdout.write(`\nWorkflow ${progress.status}: ${progress.workflowId}\n`);
222
+ if (progress.errors && progress.errors.length > 0) {
223
+ process.stdout.write("\nErrors:\n");
224
+ for (const error of progress.errors) {
225
+ process.stdout.write(
226
+ ` - ${(error as Record<string, string>).step}: ${(error as Record<string, string>).error}\n`,
227
+ );
228
+ }
229
+ }
230
+ } catch (e) {
231
+ process.stderr.write(`Error running workflow: ${(e as Error).message}\n`);
232
+ process.exit(1);
233
+ }
234
+ }
@@ -0,0 +1,11 @@
1
+ /** Shortcut command handlers (input). */
2
+
3
+ import { processHumanInput } from "../orchestrator.js";
4
+
5
+ export function cmdInput(workflowId: string, response: string): void {
6
+ if (processHumanInput(workflowId, response)) {
7
+ process.stdout.write(`Input recorded for workflow: ${workflowId}\n`);
8
+ } else {
9
+ process.exit(1);
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ /** Skills-dir command handler. */
2
+
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+
8
+ export function cmdSkillsDir(): void {
9
+ const skillsDir = path.resolve(path.join(__dirname, "..", "claude"));
10
+ process.stdout.write(`${skillsDir}\n`);
11
+ }
@@ -0,0 +1,112 @@
1
+ /** Status, cancel, and list command handlers. */
2
+
3
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
4
+ import path from "node:path";
5
+
6
+ import { WORKFLOW_STATUS, loadProgress, saveProgress } from "../progress.js";
7
+
8
+ export function cmdStatus(workflowId: string): void {
9
+ const progress = loadProgress(workflowId);
10
+ if (progress === null) {
11
+ process.stderr.write(`Error: Workflow not found: ${workflowId}\n`);
12
+ process.exit(1);
13
+ }
14
+
15
+ process.stdout.write(`Workflow: ${progress.workflowName}\n`);
16
+ process.stdout.write(`ID: ${progress.workflowId}\n`);
17
+ process.stdout.write(`Status: ${progress.status}\n`);
18
+ process.stdout.write(`Started: ${progress.startedAt}\n`);
19
+ if (progress.completedAt) {
20
+ process.stdout.write(`Completed: ${progress.completedAt}\n`);
21
+ }
22
+
23
+ if (progress.currentStep) {
24
+ const step = progress.currentStep as Record<string, unknown>;
25
+ process.stdout.write(`\nCurrent Step: ${step.name}\n`);
26
+ process.stdout.write(` Retry Count: ${(step.retry_count as number) ?? 0}\n`);
27
+ }
28
+
29
+ if (progress.completedSteps && progress.completedSteps.length > 0) {
30
+ process.stdout.write("\nCompleted Steps:\n");
31
+ for (const step of progress.completedSteps) {
32
+ const icon = step.status === "completed" ? "+" : "x";
33
+ process.stdout.write(` [${icon}] ${step.name}\n`);
34
+ }
35
+ }
36
+
37
+ if (progress.pendingSteps && progress.pendingSteps.length > 0) {
38
+ process.stdout.write("\nPending Steps:\n");
39
+ for (const stepName of progress.pendingSteps) {
40
+ process.stdout.write(` [ ] ${stepName}\n`);
41
+ }
42
+ }
43
+
44
+ if (progress.errors && progress.errors.length > 0) {
45
+ process.stdout.write("\nErrors:\n");
46
+ for (const error of progress.errors) {
47
+ const err = error as Record<string, string>;
48
+ process.stdout.write(` - ${err.step}: ${err.error}\n`);
49
+ }
50
+ }
51
+ }
52
+
53
+ export function cmdCancel(workflowId: string): void {
54
+ const progress = loadProgress(workflowId);
55
+ if (progress === null) {
56
+ process.stderr.write(`Error: Workflow not found: ${workflowId}\n`);
57
+ process.exit(1);
58
+ }
59
+
60
+ if (progress.status !== WORKFLOW_STATUS.RUNNING && progress.status !== WORKFLOW_STATUS.PAUSED) {
61
+ process.stderr.write(`Error: Cannot cancel workflow in '${progress.status}' status\n`);
62
+ process.exit(1);
63
+ }
64
+
65
+ progress.status = WORKFLOW_STATUS.CANCELED;
66
+ progress.completedAt = new Date().toISOString();
67
+ saveProgress(progress);
68
+
69
+ process.stdout.write(`Workflow canceled: ${workflowId}\n`);
70
+ }
71
+
72
+ export function cmdList(statusFilter?: string): void {
73
+ const outputsDir = path.join(process.cwd(), "agentic", "outputs");
74
+ if (!existsSync(outputsDir)) {
75
+ process.stdout.write("No workflows found.\n");
76
+ return;
77
+ }
78
+
79
+ const workflows: Record<string, unknown>[] = [];
80
+ const entries = readdirSync(outputsDir, { withFileTypes: true });
81
+ for (const entry of entries) {
82
+ if (entry.isDirectory()) {
83
+ const progressFile = path.join(outputsDir, entry.name, "progress.json");
84
+ if (existsSync(progressFile)) {
85
+ const data = JSON.parse(readFileSync(progressFile, "utf-8")) as Record<string, unknown>;
86
+ if (statusFilter == null || data.status === statusFilter) {
87
+ workflows.push(data);
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ if (workflows.length === 0) {
94
+ const suffix = statusFilter ? ` (status=${statusFilter})` : "";
95
+ process.stdout.write(`No workflows found.${suffix}\n`);
96
+ return;
97
+ }
98
+
99
+ process.stdout.write(
100
+ `${"ID".padEnd(12)} ${"Name".padEnd(25)} ${"Status".padEnd(12)} ${"Started".padEnd(20)}\n`,
101
+ );
102
+ process.stdout.write(`${"-".repeat(70)}\n`);
103
+ for (const wf of workflows) {
104
+ const id = String(wf.workflow_id ?? "").padEnd(12);
105
+ const name = String(wf.workflow_name ?? "")
106
+ .slice(0, 25)
107
+ .padEnd(25);
108
+ const status = String(wf.status ?? "").padEnd(12);
109
+ const started = wf.started_at ? String(wf.started_at).slice(0, 19).padEnd(20) : "".padEnd(20);
110
+ process.stdout.write(`${id} ${name} ${status} ${started}\n`);
111
+ }
112
+ }