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,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
+ });