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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
name: ralph-loop
|
|
2
|
+
version: "1.0"
|
|
3
|
+
description: Workflow using the Ralph Wiggum iterative loop pattern
|
|
4
|
+
|
|
5
|
+
settings:
|
|
6
|
+
max-retry: 3
|
|
7
|
+
timeout-minutes: 120
|
|
8
|
+
track-progress: true
|
|
9
|
+
bypass-permissions: false
|
|
10
|
+
terminal-output: base
|
|
11
|
+
git:
|
|
12
|
+
enabled: true
|
|
13
|
+
auto-commit: true
|
|
14
|
+
auto-pr: false
|
|
15
|
+
|
|
16
|
+
variables:
|
|
17
|
+
- name: task
|
|
18
|
+
type: string
|
|
19
|
+
required: true
|
|
20
|
+
description: Task to complete iteratively
|
|
21
|
+
- name: completion_promise
|
|
22
|
+
type: string
|
|
23
|
+
required: false
|
|
24
|
+
default: TASK_COMPLETE
|
|
25
|
+
description: Completion promise text that signals the loop should exit
|
|
26
|
+
- name: max_iterations
|
|
27
|
+
type: number
|
|
28
|
+
required: false
|
|
29
|
+
default: 25
|
|
30
|
+
description: Maximum iterations before stopping
|
|
31
|
+
|
|
32
|
+
steps:
|
|
33
|
+
- name: iterative-task
|
|
34
|
+
type: ralph-loop
|
|
35
|
+
prompt: |
|
|
36
|
+
## Ralph Wiggum Loop - Iteration {{iteration}}/{{max_iterations}}
|
|
37
|
+
|
|
38
|
+
You are in a Ralph Wiggum iterative loop. Each iteration starts a fresh session.
|
|
39
|
+
|
|
40
|
+
## Task
|
|
41
|
+
{{ variables.task }}
|
|
42
|
+
|
|
43
|
+
## How Iterations Work
|
|
44
|
+
- Each iteration is independent - just STOP when your unit of work is done
|
|
45
|
+
- The loop automatically starts a new iteration after you stop
|
|
46
|
+
- You do NOT need to output anything special to end an iteration - just stop working
|
|
47
|
+
|
|
48
|
+
## Instructions
|
|
49
|
+
1. Analyze the current state of the codebase
|
|
50
|
+
2. Identify what needs to be done next
|
|
51
|
+
3. Make incremental progress on the task
|
|
52
|
+
4. Commit your changes with a clear message
|
|
53
|
+
5. Assess if the task is fully complete
|
|
54
|
+
|
|
55
|
+
## Completion Signal
|
|
56
|
+
When your task is FULLY complete (not just one step), output:
|
|
57
|
+
```json
|
|
58
|
+
{"ralph_complete": true, "promise": "{{ variables.completion_promise }}"}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**IMPORTANT**:
|
|
62
|
+
- Only output the completion JSON when the ENTIRE task is genuinely finished
|
|
63
|
+
- If you're doing iterative work (one item at a time), do NOT output completion after each item
|
|
64
|
+
- Simply STOP after completing your iteration - no special output needed
|
|
65
|
+
max-iterations: "{{ variables.max_iterations }}"
|
|
66
|
+
completion-promise: "{{ variables.completion_promise }}"
|
|
67
|
+
timeout-minutes: 30
|
|
68
|
+
|
|
69
|
+
outputs:
|
|
70
|
+
- name: ralph-report
|
|
71
|
+
template: ralph-report.md.j2
|
|
72
|
+
path: ralph-report.md
|
|
73
|
+
when: completed
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
import {
|
|
6
|
+
deepMerge,
|
|
7
|
+
getConfigPath,
|
|
8
|
+
getConfigValue,
|
|
9
|
+
getDefaultConfig,
|
|
10
|
+
loadConfig,
|
|
11
|
+
saveConfig,
|
|
12
|
+
setConfigValue,
|
|
13
|
+
} from "../src/config.js";
|
|
14
|
+
|
|
15
|
+
function makeTempDir(): string {
|
|
16
|
+
const dir = path.join(
|
|
17
|
+
tmpdir(),
|
|
18
|
+
`agentic-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
19
|
+
);
|
|
20
|
+
mkdirSync(dir, { recursive: true });
|
|
21
|
+
return dir;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("Config defaults", () => {
|
|
25
|
+
it("should return a copy from getDefaultConfig", () => {
|
|
26
|
+
const config1 = getDefaultConfig();
|
|
27
|
+
const config2 = getDefaultConfig();
|
|
28
|
+
|
|
29
|
+
config1.outputDirectory = "modified";
|
|
30
|
+
|
|
31
|
+
expect(config2.outputDirectory).toBe("agentic");
|
|
32
|
+
expect(config1).not.toBe(config2);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should have correct default structure", () => {
|
|
36
|
+
const config = getDefaultConfig();
|
|
37
|
+
|
|
38
|
+
expect(config).toHaveProperty("outputDirectory");
|
|
39
|
+
expect(config).toHaveProperty("logging");
|
|
40
|
+
expect(config).toHaveProperty("git");
|
|
41
|
+
expect(config).toHaveProperty("defaults");
|
|
42
|
+
expect(config).toHaveProperty("execution");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("should have sonnet as default model", () => {
|
|
46
|
+
const config = getDefaultConfig();
|
|
47
|
+
expect((config.defaults as Record<string, unknown>).model).toBe("sonnet");
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("Config path", () => {
|
|
52
|
+
it("should construct correct config path", () => {
|
|
53
|
+
const tempDir = makeTempDir();
|
|
54
|
+
const configPath = getConfigPath(tempDir);
|
|
55
|
+
expect(configPath).toBe(path.join(tempDir, "agentic", "config.json"));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should use cwd when no root specified", () => {
|
|
59
|
+
const configPath = getConfigPath();
|
|
60
|
+
expect(path.basename(configPath)).toBe("config.json");
|
|
61
|
+
expect(path.basename(path.dirname(configPath))).toBe("agentic");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("Load config", () => {
|
|
66
|
+
let tempDir: string;
|
|
67
|
+
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
tempDir = makeTempDir();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should load default config when no file exists", () => {
|
|
73
|
+
const config = loadConfig(tempDir);
|
|
74
|
+
|
|
75
|
+
expect(config.outputDirectory).toBe("agentic");
|
|
76
|
+
expect((config.defaults as Record<string, unknown>).model).toBe("sonnet");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should load existing config file", () => {
|
|
80
|
+
const configDir = path.join(tempDir, "agentic");
|
|
81
|
+
mkdirSync(configDir, { recursive: true });
|
|
82
|
+
writeFileSync(
|
|
83
|
+
path.join(configDir, "config.json"),
|
|
84
|
+
JSON.stringify({
|
|
85
|
+
outputDirectory: "custom-output",
|
|
86
|
+
defaults: { model: "opus" },
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const config = loadConfig(tempDir);
|
|
91
|
+
|
|
92
|
+
expect(config.outputDirectory).toBe("custom-output");
|
|
93
|
+
expect((config.defaults as Record<string, unknown>).model).toBe("opus");
|
|
94
|
+
// Other defaults should still be present
|
|
95
|
+
expect((config.defaults as Record<string, unknown>).maxRetry).toBe(3);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("should merge loaded config with defaults", () => {
|
|
99
|
+
const configDir = path.join(tempDir, "agentic");
|
|
100
|
+
mkdirSync(configDir, { recursive: true });
|
|
101
|
+
writeFileSync(
|
|
102
|
+
path.join(configDir, "config.json"),
|
|
103
|
+
JSON.stringify({ logging: { level: "Debug" } }),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const config = loadConfig(tempDir);
|
|
107
|
+
|
|
108
|
+
expect((config.logging as Record<string, unknown>).level).toBe("Debug");
|
|
109
|
+
expect((config.logging as Record<string, unknown>).enabled).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
describe("Save config", () => {
|
|
114
|
+
let tempDir: string;
|
|
115
|
+
|
|
116
|
+
beforeEach(() => {
|
|
117
|
+
tempDir = makeTempDir();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should create directory if needed", () => {
|
|
121
|
+
const config = { test: "value" };
|
|
122
|
+
saveConfig(config, tempDir);
|
|
123
|
+
|
|
124
|
+
const configPath = getConfigPath(tempDir);
|
|
125
|
+
const content = readFileSync(configPath, "utf-8");
|
|
126
|
+
expect(content).toBeTruthy();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should write valid JSON", () => {
|
|
130
|
+
const config = { test: "value", nested: { key: 123 } };
|
|
131
|
+
saveConfig(config, tempDir);
|
|
132
|
+
|
|
133
|
+
const configPath = getConfigPath(tempDir);
|
|
134
|
+
const loaded = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
135
|
+
expect(loaded).toEqual(config);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("Config values", () => {
|
|
140
|
+
let tempDir: string;
|
|
141
|
+
|
|
142
|
+
beforeEach(() => {
|
|
143
|
+
tempDir = makeTempDir();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should get simple config value", () => {
|
|
147
|
+
const value = getConfigValue("outputDirectory", tempDir);
|
|
148
|
+
expect(value).toBe("agentic");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should get nested config value", () => {
|
|
152
|
+
const value = getConfigValue("defaults.model", tempDir);
|
|
153
|
+
expect(value).toBe("sonnet");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("should return null for missing key", () => {
|
|
157
|
+
const value = getConfigValue("nonexistent.key", tempDir);
|
|
158
|
+
expect(value).toBeNull();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should set simple config value", () => {
|
|
162
|
+
setConfigValue("outputDirectory", "new-output", tempDir);
|
|
163
|
+
const value = getConfigValue("outputDirectory", tempDir);
|
|
164
|
+
expect(value).toBe("new-output");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("should set nested config value", () => {
|
|
168
|
+
setConfigValue("defaults.model", "opus", tempDir);
|
|
169
|
+
const value = getConfigValue("defaults.model", tempDir);
|
|
170
|
+
expect(value).toBe("opus");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should create nested structure when setting value", () => {
|
|
174
|
+
setConfigValue("new.nested.key", "value", tempDir);
|
|
175
|
+
const value = getConfigValue("new.nested.key", tempDir);
|
|
176
|
+
expect(value).toBe("value");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should parse boolean true value", () => {
|
|
180
|
+
setConfigValue("logging.enabled", "true", tempDir);
|
|
181
|
+
const value = getConfigValue("logging.enabled", tempDir);
|
|
182
|
+
expect(value).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("should parse boolean false value", () => {
|
|
186
|
+
setConfigValue("logging.enabled", "false", tempDir);
|
|
187
|
+
const value = getConfigValue("logging.enabled", tempDir);
|
|
188
|
+
expect(value).toBe(false);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should parse integer value", () => {
|
|
192
|
+
setConfigValue("defaults.maxRetry", "10", tempDir);
|
|
193
|
+
const value = getConfigValue("defaults.maxRetry", tempDir);
|
|
194
|
+
expect(value).toBe(10);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe("Deep merge", () => {
|
|
199
|
+
it("should merge simple objects", () => {
|
|
200
|
+
const base = { a: 1, b: 2 };
|
|
201
|
+
const override = { b: 3, c: 4 };
|
|
202
|
+
const result = deepMerge(base as Record<string, unknown>, override as Record<string, unknown>);
|
|
203
|
+
expect(result).toEqual({ a: 1, b: 3, c: 4 });
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should merge nested objects", () => {
|
|
207
|
+
const base = { a: { x: 1, y: 2 }, b: 3 };
|
|
208
|
+
const override = { a: { y: 99, z: 100 } };
|
|
209
|
+
const result = deepMerge(base as Record<string, unknown>, override as Record<string, unknown>);
|
|
210
|
+
expect(result).toEqual({ a: { x: 1, y: 99, z: 100 }, b: 3 });
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("should not mutate base dictionary", () => {
|
|
214
|
+
const base = { a: { x: 1 } };
|
|
215
|
+
const override = { a: { y: 2 } };
|
|
216
|
+
deepMerge(base as Record<string, unknown>, override as Record<string, unknown>);
|
|
217
|
+
expect(base).toEqual({ a: { x: 1 } });
|
|
218
|
+
});
|
|
219
|
+
});
|