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.
- package/.gitattributes +24 -0
- package/.github/workflows/ci.yml +70 -0
- package/.markdownlint-cli2.jsonc +16 -0
- package/.prettierignore +3 -0
- package/.prettierrc +6 -0
- package/.vscode/agentic-forge.code-workspace +26 -0
- package/CHANGELOG.md +100 -0
- package/CLAUDE.md +158 -0
- package/CONTRIBUTING.md +152 -0
- package/LICENSE +21 -0
- package/README.md +145 -0
- package/agentic-forge-banner.png +0 -0
- package/biome.json +21 -0
- package/package.json +5 -0
- package/scripts/copy-assets.js +21 -0
- package/src/agents/explorer.md +97 -0
- package/src/agents/reviewer.md +137 -0
- package/src/checkpoints/manager.ts +119 -0
- package/src/claude/.claude/skills/analyze/SKILL.md +241 -0
- package/src/claude/.claude/skills/analyze/references/bug.md +62 -0
- package/src/claude/.claude/skills/analyze/references/debt.md +76 -0
- package/src/claude/.claude/skills/analyze/references/doc.md +67 -0
- package/src/claude/.claude/skills/analyze/references/security.md +76 -0
- package/src/claude/.claude/skills/analyze/references/style.md +72 -0
- package/src/claude/.claude/skills/create-checkpoint/SKILL.md +88 -0
- package/src/claude/.claude/skills/create-log/SKILL.md +75 -0
- package/src/claude/.claude/skills/fix-analyze/SKILL.md +102 -0
- package/src/claude/.claude/skills/git-branch/SKILL.md +71 -0
- package/src/claude/.claude/skills/git-commit/SKILL.md +107 -0
- package/src/claude/.claude/skills/git-pr/SKILL.md +96 -0
- package/src/claude/.claude/skills/orchestrate/SKILL.md +120 -0
- package/src/claude/.claude/skills/sdlc-plan/SKILL.md +163 -0
- package/src/claude/.claude/skills/sdlc-plan/references/bug.md +115 -0
- package/src/claude/.claude/skills/sdlc-plan/references/chore.md +105 -0
- package/src/claude/.claude/skills/sdlc-plan/references/feature.md +130 -0
- package/src/claude/.claude/skills/sdlc-review/SKILL.md +215 -0
- package/src/claude/.claude/skills/workflow-builder/SKILL.md +185 -0
- package/src/claude/.claude/skills/workflow-builder/references/REFERENCE.md +487 -0
- package/src/claude/.claude/skills/workflow-builder/references/workflow-example.yaml +427 -0
- package/src/cli.ts +182 -0
- package/src/commands/config-cmd.ts +28 -0
- package/src/commands/index.ts +21 -0
- package/src/commands/init.ts +96 -0
- package/src/commands/release-notes.ts +85 -0
- package/src/commands/resume.ts +103 -0
- package/src/commands/run.ts +234 -0
- package/src/commands/shortcuts.ts +11 -0
- package/src/commands/skills-dir.ts +11 -0
- package/src/commands/status.ts +112 -0
- package/src/commands/update.ts +64 -0
- package/src/commands/version.ts +27 -0
- package/src/commands/workflows.ts +129 -0
- package/src/config.ts +129 -0
- package/src/console.ts +790 -0
- package/src/executor.ts +354 -0
- package/src/git/worktree.ts +236 -0
- package/src/logging/logger.ts +95 -0
- package/src/orchestrator.ts +815 -0
- package/src/parser.ts +225 -0
- package/src/progress.ts +306 -0
- package/src/prompts/agentic-system.md +31 -0
- package/src/ralph-loop.ts +260 -0
- package/src/renderer.ts +164 -0
- package/src/runner.ts +634 -0
- package/src/signal-manager.ts +55 -0
- package/src/steps/base.ts +71 -0
- package/src/steps/conditional-step.ts +144 -0
- package/src/steps/index.ts +15 -0
- package/src/steps/parallel-step.ts +213 -0
- package/src/steps/prompt-step.ts +121 -0
- package/src/steps/ralph-loop-step.ts +186 -0
- package/src/steps/serial-step.ts +84 -0
- package/src/templates/analysis/bug.md.j2 +35 -0
- package/src/templates/analysis/debt.md.j2 +38 -0
- package/src/templates/analysis/doc.md.j2 +45 -0
- package/src/templates/analysis/security.md.j2 +35 -0
- package/src/templates/analysis/style.md.j2 +44 -0
- package/src/templates/analysis-summary.md.j2 +58 -0
- package/src/templates/checkpoint.md.j2 +27 -0
- package/src/templates/implementation-report.md.j2 +81 -0
- package/src/templates/memory.md.j2 +16 -0
- package/src/templates/plan-bug.md.j2 +42 -0
- package/src/templates/plan-chore.md.j2 +27 -0
- package/src/templates/plan-feature.md.j2 +41 -0
- package/src/templates/progress.json.j2 +16 -0
- package/src/templates/ralph-report.md.j2 +45 -0
- package/src/types.ts +141 -0
- package/src/workflows/analyze-codebase-merge.yaml +328 -0
- package/src/workflows/analyze-codebase.yaml +196 -0
- package/src/workflows/analyze-single.yaml +56 -0
- package/src/workflows/demo.yaml +180 -0
- package/src/workflows/one-shot.yaml +54 -0
- package/src/workflows/plan-build-review.yaml +160 -0
- package/src/workflows/ralph-loop.yaml +73 -0
- package/tests/config.test.ts +219 -0
- package/tests/console.test.ts +506 -0
- package/tests/executor.test.ts +339 -0
- package/tests/init.test.ts +86 -0
- package/tests/logger.test.ts +110 -0
- package/tests/parser.test.ts +290 -0
- package/tests/progress.test.ts +345 -0
- package/tests/ralph-loop.test.ts +418 -0
- package/tests/renderer.test.ts +350 -0
- package/tests/runner.test.ts +497 -0
- package/tests/setup.test.ts +7 -0
- package/tests/signal-manager.test.ts +26 -0
- package/tests/steps.test.ts +412 -0
- package/tests/worktree.test.ts +411 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +8 -0
package/src/parser.ts
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/** YAML workflow parser. */
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import yaml from "js-yaml";
|
|
6
|
+
import type {
|
|
7
|
+
GitSettings,
|
|
8
|
+
OutputDefinition,
|
|
9
|
+
StepDefinition,
|
|
10
|
+
StepGitSettings,
|
|
11
|
+
StepType,
|
|
12
|
+
Variable,
|
|
13
|
+
WorkflowDefinition,
|
|
14
|
+
WorkflowSettings,
|
|
15
|
+
} from "./types.js";
|
|
16
|
+
import { VALID_STEP_TYPES } from "./types.js";
|
|
17
|
+
|
|
18
|
+
export class WorkflowParseError extends Error {
|
|
19
|
+
constructor(message: string) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "WorkflowParseError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class WorkflowParser {
|
|
26
|
+
basePath: string;
|
|
27
|
+
|
|
28
|
+
constructor(basePath?: string) {
|
|
29
|
+
this.basePath = basePath ?? process.cwd();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
parseFile(filePath: string): WorkflowDefinition {
|
|
33
|
+
if (!existsSync(filePath)) {
|
|
34
|
+
throw new WorkflowParseError(`Workflow file not found: ${filePath}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let data: unknown;
|
|
38
|
+
try {
|
|
39
|
+
const content = readFileSync(filePath, "utf-8");
|
|
40
|
+
data = yaml.load(content);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
if (e instanceof WorkflowParseError) throw e;
|
|
43
|
+
throw new WorkflowParseError(`Invalid YAML: ${e}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
this.basePath = path.dirname(filePath);
|
|
47
|
+
return this.parseDict(data);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
parseString(content: string): WorkflowDefinition {
|
|
51
|
+
let data: unknown;
|
|
52
|
+
try {
|
|
53
|
+
data = yaml.load(content);
|
|
54
|
+
} catch (e) {
|
|
55
|
+
throw new WorkflowParseError(`Invalid YAML: ${e}`);
|
|
56
|
+
}
|
|
57
|
+
return this.parseDict(data);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
parseDict(data: unknown): WorkflowDefinition {
|
|
61
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
62
|
+
throw new WorkflowParseError("Workflow must be a dictionary");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const dict = data as Record<string, unknown>;
|
|
66
|
+
const name = this.required(dict, "name") as string;
|
|
67
|
+
const version = (dict.version as string) ?? "1.0";
|
|
68
|
+
const description = (dict.description as string) ?? "";
|
|
69
|
+
|
|
70
|
+
const settings = this.parseSettings((dict.settings as Record<string, unknown>) ?? {});
|
|
71
|
+
const variables = ((dict.variables as unknown[]) ?? []).map((v) =>
|
|
72
|
+
this.parseVariable(v as Record<string, unknown>),
|
|
73
|
+
);
|
|
74
|
+
const steps = ((dict.steps as unknown[]) ?? []).map((s) =>
|
|
75
|
+
this.parseStep(s as Record<string, unknown>),
|
|
76
|
+
);
|
|
77
|
+
const outputs = ((dict.outputs as unknown[]) ?? []).map((o) =>
|
|
78
|
+
this.parseOutput(o as Record<string, unknown>),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return { name, version, description, settings, variables, steps, outputs };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private required(data: Record<string, unknown>, key: string): unknown {
|
|
85
|
+
if (!(key in data)) {
|
|
86
|
+
throw new WorkflowParseError(`Missing required field: ${key}`);
|
|
87
|
+
}
|
|
88
|
+
return data[key];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private parseSettings(data: Record<string, unknown>): WorkflowSettings {
|
|
92
|
+
const gitData = (data.git as Record<string, unknown>) ?? {};
|
|
93
|
+
const git: GitSettings = {
|
|
94
|
+
enabled: (gitData.enabled as boolean) ?? false,
|
|
95
|
+
worktree: (gitData.worktree as boolean) ?? false,
|
|
96
|
+
autoCommit: (gitData["auto-commit"] as boolean) ?? true,
|
|
97
|
+
autoPr: (gitData["auto-pr"] as boolean) ?? true,
|
|
98
|
+
branchPrefix: (gitData["branch-prefix"] as string) ?? "agentic",
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
maxRetry: (data["max-retry"] as number) ?? 3,
|
|
102
|
+
timeoutMinutes: (data["timeout-minutes"] as number) ?? 60,
|
|
103
|
+
trackProgress: (data["track-progress"] as boolean) ?? true,
|
|
104
|
+
autofix: (data.autofix as string) ?? "none",
|
|
105
|
+
terminalOutput: (data["terminal-output"] as string) ?? "base",
|
|
106
|
+
bypassPermissions: (data["bypass-permissions"] as boolean) ?? false,
|
|
107
|
+
strictMode: (data["strict-mode"] as boolean) ?? false,
|
|
108
|
+
model: (data.model as string) ?? null,
|
|
109
|
+
requiredTools: (data["required-tools"] as string[]) ?? [],
|
|
110
|
+
git,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private parseVariable(data: Record<string, unknown>): Variable {
|
|
115
|
+
return {
|
|
116
|
+
name: this.required(data, "name") as string,
|
|
117
|
+
type: (data.type as string) ?? "string",
|
|
118
|
+
required: (data.required as boolean) ?? true,
|
|
119
|
+
default: data.default,
|
|
120
|
+
description: (data.description as string) ?? "",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private parseStep(data: Record<string, unknown>, parentIsParallel = false): StepDefinition {
|
|
125
|
+
const name = this.required(data, "name") as string;
|
|
126
|
+
const typeStr = this.required(data, "type") as string;
|
|
127
|
+
|
|
128
|
+
if (!VALID_STEP_TYPES.includes(typeStr as StepType)) {
|
|
129
|
+
throw new WorkflowParseError(
|
|
130
|
+
`Invalid step type: ${typeStr}. Valid types: ${JSON.stringify(VALID_STEP_TYPES)}`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const stepType = typeStr as StepType;
|
|
135
|
+
|
|
136
|
+
if (parentIsParallel && stepType === "parallel") {
|
|
137
|
+
throw new WorkflowParseError(
|
|
138
|
+
`Nested parallel steps are not allowed: '${name}' is a parallel step inside another parallel step`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const step: StepDefinition = {
|
|
143
|
+
name,
|
|
144
|
+
type: stepType,
|
|
145
|
+
prompt: null,
|
|
146
|
+
agent: null,
|
|
147
|
+
steps: [],
|
|
148
|
+
mergeStrategy: "wait-all",
|
|
149
|
+
mergeMode: "independent",
|
|
150
|
+
condition: null,
|
|
151
|
+
thenSteps: [],
|
|
152
|
+
elseSteps: [],
|
|
153
|
+
maxIterations: 5,
|
|
154
|
+
completionPromise: null,
|
|
155
|
+
message: null,
|
|
156
|
+
pollingInterval: 15,
|
|
157
|
+
onTimeout: "abort",
|
|
158
|
+
model: null,
|
|
159
|
+
stepTimeoutMinutes: null,
|
|
160
|
+
stepMaxRetry: null,
|
|
161
|
+
onError: "retry",
|
|
162
|
+
checkpoint: false,
|
|
163
|
+
dependsOn: null,
|
|
164
|
+
git: null,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
if (stepType === "prompt") {
|
|
168
|
+
step.prompt = (data.prompt as string) ?? null;
|
|
169
|
+
step.agent = (data.agent as string) ?? null;
|
|
170
|
+
} else if (stepType === "parallel") {
|
|
171
|
+
step.steps = ((data.steps as unknown[]) ?? []).map((s) =>
|
|
172
|
+
this.parseStep(s as Record<string, unknown>, true),
|
|
173
|
+
);
|
|
174
|
+
step.mergeStrategy = (data["merge-strategy"] as string) ?? "wait-all";
|
|
175
|
+
step.mergeMode = (data["merge-mode"] as string) ?? "independent";
|
|
176
|
+
const gitData = data.git as Record<string, unknown> | undefined;
|
|
177
|
+
if (gitData) {
|
|
178
|
+
step.git = {
|
|
179
|
+
worktree: (gitData.worktree as boolean) ?? false,
|
|
180
|
+
autoPr: (gitData["auto-pr"] as boolean) ?? false,
|
|
181
|
+
branchPrefix: (gitData["branch-prefix"] as string) ?? "agentic",
|
|
182
|
+
} satisfies StepGitSettings;
|
|
183
|
+
}
|
|
184
|
+
} else if (stepType === "serial") {
|
|
185
|
+
step.steps = ((data.steps as unknown[]) ?? []).map((s) =>
|
|
186
|
+
this.parseStep(s as Record<string, unknown>),
|
|
187
|
+
);
|
|
188
|
+
} else if (stepType === "conditional") {
|
|
189
|
+
step.condition = (data.condition as string) ?? null;
|
|
190
|
+
step.thenSteps = ((data.then as unknown[]) ?? []).map((s) =>
|
|
191
|
+
this.parseStep(s as Record<string, unknown>),
|
|
192
|
+
);
|
|
193
|
+
step.elseSteps = ((data.else as unknown[]) ?? []).map((s) =>
|
|
194
|
+
this.parseStep(s as Record<string, unknown>),
|
|
195
|
+
);
|
|
196
|
+
} else if (stepType === "ralph-loop") {
|
|
197
|
+
step.prompt = (data.prompt as string) ?? null;
|
|
198
|
+
step.maxIterations = (data["max-iterations"] as number) ?? 5;
|
|
199
|
+
step.completionPromise = (data["completion-promise"] as string) ?? null;
|
|
200
|
+
} else if (stepType === "wait-for-human") {
|
|
201
|
+
step.message = (data.message as string) ?? null;
|
|
202
|
+
step.pollingInterval = (data["polling-interval"] as number) ?? 15;
|
|
203
|
+
step.onTimeout = (data["on-timeout"] as string) ?? "abort";
|
|
204
|
+
step.stepTimeoutMinutes = (data["timeout-minutes"] as number) ?? 5;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
step.model = (data.model as string) ?? null;
|
|
208
|
+
step.stepTimeoutMinutes = (data["timeout-minutes"] as number) ?? step.stepTimeoutMinutes;
|
|
209
|
+
step.stepMaxRetry = (data["max-retry"] as number) ?? null;
|
|
210
|
+
step.onError = (data["on-error"] as string) ?? "retry";
|
|
211
|
+
step.checkpoint = (data.checkpoint as boolean) ?? false;
|
|
212
|
+
step.dependsOn = (data["depends-on"] as string) ?? null;
|
|
213
|
+
|
|
214
|
+
return step;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private parseOutput(data: Record<string, unknown>): OutputDefinition {
|
|
218
|
+
return {
|
|
219
|
+
name: this.required(data, "name") as string,
|
|
220
|
+
template: this.required(data, "template") as string,
|
|
221
|
+
path: this.required(data, "path") as string,
|
|
222
|
+
when: (data.when as string) ?? "completed",
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
package/src/progress.ts
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/** Workflow progress tracking. */
|
|
2
|
+
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { lockSync } from "proper-lockfile";
|
|
6
|
+
import type { ParallelBranch, StepProgress, WorkflowProgress } from "./types.js";
|
|
7
|
+
|
|
8
|
+
// --- Constants ---
|
|
9
|
+
|
|
10
|
+
export const WORKFLOW_STATUS = {
|
|
11
|
+
PENDING: "pending",
|
|
12
|
+
RUNNING: "running",
|
|
13
|
+
COMPLETED: "completed",
|
|
14
|
+
FAILED: "failed",
|
|
15
|
+
PAUSED: "paused",
|
|
16
|
+
CANCELED: "canceled",
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
export const STEP_STATUS = {
|
|
20
|
+
PENDING: "pending",
|
|
21
|
+
RUNNING: "running",
|
|
22
|
+
COMPLETED: "completed",
|
|
23
|
+
FAILED: "failed",
|
|
24
|
+
SKIPPED: "skipped",
|
|
25
|
+
} as const;
|
|
26
|
+
|
|
27
|
+
// --- ID and path helpers ---
|
|
28
|
+
|
|
29
|
+
export function generateWorkflowId(workflowName: string): string {
|
|
30
|
+
const now = new Date();
|
|
31
|
+
const pad = (n: number, len = 2) => String(n).padStart(len, "0");
|
|
32
|
+
const timestamp = `${now.getUTCFullYear()}${pad(now.getUTCMonth() + 1)}${pad(now.getUTCDate())}-${pad(now.getUTCHours())}${pad(now.getUTCMinutes())}${pad(now.getUTCSeconds())}`;
|
|
33
|
+
|
|
34
|
+
let safeName = workflowName.toLowerCase().replace(/ /g, "-").replace(/_/g, "-");
|
|
35
|
+
safeName = safeName.replace(/[^a-z0-9-]/g, "");
|
|
36
|
+
return `${timestamp}-${safeName}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getProgressPath(workflowId: string, repoRoot?: string): string {
|
|
40
|
+
const root = repoRoot ?? process.cwd();
|
|
41
|
+
return path.join(root, "agentic", "outputs", workflowId, "progress.json");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// --- CRUD operations ---
|
|
45
|
+
|
|
46
|
+
export function loadProgress(workflowId: string, repoRoot?: string): WorkflowProgress | null {
|
|
47
|
+
const progressPath = getProgressPath(workflowId, repoRoot);
|
|
48
|
+
if (!existsSync(progressPath)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const data = JSON.parse(readFileSync(progressPath, "utf-8"));
|
|
52
|
+
return dictToProgress(data);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function saveProgress(progress: WorkflowProgress, repoRoot?: string): void {
|
|
56
|
+
const progressPath = getProgressPath(progress.workflowId, repoRoot);
|
|
57
|
+
const dir = path.dirname(progressPath);
|
|
58
|
+
mkdirSync(dir, { recursive: true });
|
|
59
|
+
|
|
60
|
+
// Ensure the file exists so proper-lockfile can resolve its path
|
|
61
|
+
if (!existsSync(progressPath)) {
|
|
62
|
+
writeFileSync(progressPath, "{}", "utf-8");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let release: (() => void) | undefined;
|
|
66
|
+
try {
|
|
67
|
+
release = lockSync(progressPath);
|
|
68
|
+
writeFileSync(progressPath, JSON.stringify(progressToDict(progress), null, 2), "utf-8");
|
|
69
|
+
} finally {
|
|
70
|
+
if (release) {
|
|
71
|
+
release();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function createProgress(
|
|
77
|
+
workflowId: string,
|
|
78
|
+
workflowName: string,
|
|
79
|
+
stepNames: string[],
|
|
80
|
+
variables: Record<string, unknown>,
|
|
81
|
+
workflowFile = "",
|
|
82
|
+
): WorkflowProgress {
|
|
83
|
+
return {
|
|
84
|
+
schemaVersion: "1.0",
|
|
85
|
+
workflowId,
|
|
86
|
+
workflowName,
|
|
87
|
+
status: WORKFLOW_STATUS.RUNNING,
|
|
88
|
+
startedAt: new Date().toISOString(),
|
|
89
|
+
completedAt: null,
|
|
90
|
+
currentStep: null,
|
|
91
|
+
completedSteps: [],
|
|
92
|
+
pendingSteps: [...stepNames],
|
|
93
|
+
runningSteps: [],
|
|
94
|
+
parallelBranches: [],
|
|
95
|
+
errors: [],
|
|
96
|
+
variables: { ...variables },
|
|
97
|
+
stepOutputs: {},
|
|
98
|
+
workflowFile,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// --- Step update functions ---
|
|
103
|
+
|
|
104
|
+
export function updateStepStarted(progress: WorkflowProgress, stepName: string): void {
|
|
105
|
+
const pendingIdx = progress.pendingSteps.indexOf(stepName);
|
|
106
|
+
if (pendingIdx !== -1) {
|
|
107
|
+
progress.pendingSteps.splice(pendingIdx, 1);
|
|
108
|
+
}
|
|
109
|
+
if (!progress.runningSteps.includes(stepName)) {
|
|
110
|
+
progress.runningSteps.push(stepName);
|
|
111
|
+
}
|
|
112
|
+
progress.currentStep = {
|
|
113
|
+
name: stepName,
|
|
114
|
+
retry_count: 0,
|
|
115
|
+
started_at: new Date().toISOString(),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function updateStepCompleted(
|
|
120
|
+
progress: WorkflowProgress,
|
|
121
|
+
stepName: string,
|
|
122
|
+
outputSummary = "",
|
|
123
|
+
output?: unknown,
|
|
124
|
+
): void {
|
|
125
|
+
const runIdx = progress.runningSteps.indexOf(stepName);
|
|
126
|
+
if (runIdx !== -1) {
|
|
127
|
+
progress.runningSteps.splice(runIdx, 1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let startedAt: string | null = null;
|
|
131
|
+
let retryCount = 0;
|
|
132
|
+
if (progress.currentStep) {
|
|
133
|
+
startedAt = (progress.currentStep.started_at as string) ?? null;
|
|
134
|
+
retryCount = (progress.currentStep.retry_count as number) ?? 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const step: StepProgress = {
|
|
138
|
+
name: stepName,
|
|
139
|
+
status: STEP_STATUS.COMPLETED,
|
|
140
|
+
startedAt,
|
|
141
|
+
completedAt: new Date().toISOString(),
|
|
142
|
+
retryCount,
|
|
143
|
+
outputSummary,
|
|
144
|
+
error: null,
|
|
145
|
+
humanInput: null,
|
|
146
|
+
};
|
|
147
|
+
progress.completedSteps.push(step);
|
|
148
|
+
|
|
149
|
+
if (output !== undefined) {
|
|
150
|
+
progress.stepOutputs[stepName] = output;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
progress.currentStep = null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function updateStepFailed(
|
|
157
|
+
progress: WorkflowProgress,
|
|
158
|
+
stepName: string,
|
|
159
|
+
error: string,
|
|
160
|
+
): void {
|
|
161
|
+
const runIdx = progress.runningSteps.indexOf(stepName);
|
|
162
|
+
if (runIdx !== -1) {
|
|
163
|
+
progress.runningSteps.splice(runIdx, 1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let startedAt: string | null = null;
|
|
167
|
+
let retryCount = 0;
|
|
168
|
+
if (progress.currentStep) {
|
|
169
|
+
startedAt = (progress.currentStep.started_at as string) ?? null;
|
|
170
|
+
retryCount = (progress.currentStep.retry_count as number) ?? 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const step: StepProgress = {
|
|
174
|
+
name: stepName,
|
|
175
|
+
status: STEP_STATUS.FAILED,
|
|
176
|
+
startedAt,
|
|
177
|
+
completedAt: new Date().toISOString(),
|
|
178
|
+
retryCount,
|
|
179
|
+
error,
|
|
180
|
+
outputSummary: "",
|
|
181
|
+
humanInput: null,
|
|
182
|
+
};
|
|
183
|
+
progress.completedSteps.push(step);
|
|
184
|
+
progress.errors.push({ step: stepName, error });
|
|
185
|
+
progress.currentStep = null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function updateStepSkipped(progress: WorkflowProgress, stepName: string): void {
|
|
189
|
+
const pendingIdx = progress.pendingSteps.indexOf(stepName);
|
|
190
|
+
if (pendingIdx !== -1) {
|
|
191
|
+
progress.pendingSteps.splice(pendingIdx, 1);
|
|
192
|
+
}
|
|
193
|
+
const runIdx = progress.runningSteps.indexOf(stepName);
|
|
194
|
+
if (runIdx !== -1) {
|
|
195
|
+
progress.runningSteps.splice(runIdx, 1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const step: StepProgress = {
|
|
199
|
+
name: stepName,
|
|
200
|
+
status: STEP_STATUS.SKIPPED,
|
|
201
|
+
startedAt: null,
|
|
202
|
+
completedAt: new Date().toISOString(),
|
|
203
|
+
retryCount: 0,
|
|
204
|
+
outputSummary: "",
|
|
205
|
+
error: null,
|
|
206
|
+
humanInput: null,
|
|
207
|
+
};
|
|
208
|
+
progress.completedSteps.push(step);
|
|
209
|
+
progress.currentStep = null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function prepareForResume(progress: WorkflowProgress): void {
|
|
213
|
+
if (progress.runningSteps.length > 0) {
|
|
214
|
+
progress.pendingSteps = [...progress.runningSteps, ...progress.pendingSteps];
|
|
215
|
+
progress.runningSteps = [];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (progress.currentStep) {
|
|
219
|
+
const stepName = progress.currentStep.name as string | undefined;
|
|
220
|
+
if (stepName && !progress.pendingSteps.includes(stepName)) {
|
|
221
|
+
progress.pendingSteps.unshift(stepName);
|
|
222
|
+
}
|
|
223
|
+
progress.currentStep = null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
progress.status = WORKFLOW_STATUS.RUNNING;
|
|
227
|
+
progress.completedAt = null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// --- Serialization helpers ---
|
|
231
|
+
|
|
232
|
+
export function progressToDict(progress: WorkflowProgress): Record<string, unknown> {
|
|
233
|
+
return {
|
|
234
|
+
schema_version: progress.schemaVersion,
|
|
235
|
+
workflow_id: progress.workflowId,
|
|
236
|
+
workflow_name: progress.workflowName,
|
|
237
|
+
status: progress.status,
|
|
238
|
+
started_at: progress.startedAt,
|
|
239
|
+
completed_at: progress.completedAt,
|
|
240
|
+
current_step: progress.currentStep,
|
|
241
|
+
completed_steps: progress.completedSteps.map((s) => ({
|
|
242
|
+
name: s.name,
|
|
243
|
+
status: s.status,
|
|
244
|
+
started_at: s.startedAt,
|
|
245
|
+
completed_at: s.completedAt,
|
|
246
|
+
retry_count: s.retryCount,
|
|
247
|
+
output_summary: s.outputSummary,
|
|
248
|
+
error: s.error,
|
|
249
|
+
human_input: s.humanInput,
|
|
250
|
+
})),
|
|
251
|
+
pending_steps: progress.pendingSteps,
|
|
252
|
+
running_steps: progress.runningSteps,
|
|
253
|
+
parallel_branches: progress.parallelBranches.map((b) => ({
|
|
254
|
+
branch_id: b.branchId,
|
|
255
|
+
status: b.status,
|
|
256
|
+
worktree_path: b.worktreePath,
|
|
257
|
+
progress_file: b.progressFile,
|
|
258
|
+
})),
|
|
259
|
+
errors: progress.errors,
|
|
260
|
+
variables: progress.variables,
|
|
261
|
+
step_outputs: progress.stepOutputs,
|
|
262
|
+
workflow_file: progress.workflowFile,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function dictToProgress(data: Record<string, unknown>): WorkflowProgress {
|
|
267
|
+
const completedSteps = ((data.completed_steps as Record<string, unknown>[]) ?? []).map(
|
|
268
|
+
(s): StepProgress => ({
|
|
269
|
+
name: (s.name as string) ?? "",
|
|
270
|
+
status: (s.status as string) ?? "pending",
|
|
271
|
+
startedAt: (s.started_at as string) ?? null,
|
|
272
|
+
completedAt: (s.completed_at as string) ?? null,
|
|
273
|
+
retryCount: (s.retry_count as number) ?? 0,
|
|
274
|
+
outputSummary: (s.output_summary as string) ?? "",
|
|
275
|
+
error: (s.error as string) ?? null,
|
|
276
|
+
humanInput: (s.human_input as string) ?? null,
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const parallelBranches = ((data.parallel_branches as Record<string, unknown>[]) ?? []).map(
|
|
281
|
+
(b): ParallelBranch => ({
|
|
282
|
+
branchId: (b.branch_id as string) ?? "",
|
|
283
|
+
status: (b.status as string) ?? "",
|
|
284
|
+
worktreePath: (b.worktree_path as string) ?? "",
|
|
285
|
+
progressFile: (b.progress_file as string) ?? "",
|
|
286
|
+
}),
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
schemaVersion: (data.schema_version as string) ?? "1.0",
|
|
291
|
+
workflowId: (data.workflow_id as string) ?? "",
|
|
292
|
+
workflowName: (data.workflow_name as string) ?? "",
|
|
293
|
+
status: (data.status as string) ?? "pending",
|
|
294
|
+
startedAt: (data.started_at as string) ?? null,
|
|
295
|
+
completedAt: (data.completed_at as string) ?? null,
|
|
296
|
+
currentStep: (data.current_step as Record<string, unknown>) ?? null,
|
|
297
|
+
completedSteps,
|
|
298
|
+
pendingSteps: (data.pending_steps as string[]) ?? [],
|
|
299
|
+
runningSteps: (data.running_steps as string[]) ?? [],
|
|
300
|
+
parallelBranches,
|
|
301
|
+
errors: (data.errors as Record<string, unknown>[]) ?? [],
|
|
302
|
+
variables: (data.variables as Record<string, unknown>) ?? {},
|
|
303
|
+
stepOutputs: (data.step_outputs as Record<string, unknown>) ?? {},
|
|
304
|
+
workflowFile: (data.workflow_file as string) ?? "",
|
|
305
|
+
};
|
|
306
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
## Execution Context
|
|
2
|
+
|
|
3
|
+
You are being executed in an agentic workflow without user interaction.
|
|
4
|
+
|
|
5
|
+
**Constraints**:
|
|
6
|
+
|
|
7
|
+
- You CANNOT ask user questions - make reasonable decisions autonomously
|
|
8
|
+
- You CANNOT request additional permissions - if permissions are missing, end the session
|
|
9
|
+
- You MUST complete the task or report failure with details
|
|
10
|
+
|
|
11
|
+
## Required Output Format
|
|
12
|
+
|
|
13
|
+
At the END of your session, you MUST output a JSON block. The block MUST contain these base keys:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"sessionId": "<session-id-for-resume>",
|
|
18
|
+
"isSuccess": true|false,
|
|
19
|
+
"context": "2-5 sentence summary of what was done, any errors encountered, and important notes"
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Base Keys** (REQUIRED in every output):
|
|
24
|
+
|
|
25
|
+
- `sessionId`: The session ID for /resume capability
|
|
26
|
+
- `isSuccess`: Boolean indicating if the task completed successfully
|
|
27
|
+
- `context`: 2-5 sentence summary of what was done, errors if any, important notes
|
|
28
|
+
|
|
29
|
+
**Additional Keys**: The prompt or command may require additional keys in the output JSON. Include any extra keys requested alongside the base keys.
|
|
30
|
+
|
|
31
|
+
This JSON block is REQUIRED. Include it as the last thing in your output.
|