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,418 @@
1
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import path from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import {
6
+ type CompletionResult,
7
+ buildRalphSystemMessage,
8
+ createRalphState,
9
+ deactivateRalphState,
10
+ deleteRalphState,
11
+ detectCompletionPromise,
12
+ getRalphStatePath,
13
+ loadRalphState,
14
+ parseRalphState,
15
+ updateRalphIteration,
16
+ } from "../src/ralph-loop.js";
17
+
18
+ let tempDir: string;
19
+
20
+ beforeEach(() => {
21
+ tempDir = path.join(tmpdir(), `ralph-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
22
+ mkdirSync(tempDir, { recursive: true });
23
+ });
24
+
25
+ afterEach(() => {
26
+ try {
27
+ const { rmSync } = require("node:fs");
28
+ rmSync(tempDir, { recursive: true, force: true });
29
+ } catch {
30
+ // Ignore cleanup errors on Windows
31
+ }
32
+ });
33
+
34
+ describe("getRalphStatePath", () => {
35
+ it("follows expected format", () => {
36
+ const result = getRalphStatePath("workflow-123", "apply-fixes", tempDir);
37
+ const expected = path.join(
38
+ tempDir,
39
+ "agentic",
40
+ "outputs",
41
+ "workflow-123",
42
+ "ralph-apply-fixes.md",
43
+ );
44
+ expect(result).toBe(expected);
45
+ });
46
+
47
+ it("sanitizes special characters in step name", () => {
48
+ const result = getRalphStatePath("workflow", "step/with:special@chars", tempDir);
49
+ const filename = path.basename(result);
50
+ expect(filename).not.toContain("/");
51
+ expect(filename).not.toContain(":");
52
+ expect(filename).not.toContain("@");
53
+ });
54
+ });
55
+
56
+ describe("createRalphState", () => {
57
+ it("creates basic ralph state", () => {
58
+ const state = createRalphState(
59
+ "test-workflow",
60
+ "apply-fixes",
61
+ "Fix the issues",
62
+ 10,
63
+ "FIXES_COMPLETE",
64
+ tempDir,
65
+ );
66
+
67
+ expect(state.active).toBe(true);
68
+ expect(state.iteration).toBe(1);
69
+ expect(state.maxIterations).toBe(10);
70
+ expect(state.completionPromise).toBe("FIXES_COMPLETE");
71
+ expect(state.prompt).toBe("Fix the issues");
72
+ expect(state.startedAt).toBeTruthy();
73
+ });
74
+
75
+ it("saves file to disk", () => {
76
+ createRalphState("save-test", "test-step", "Test prompt", 5, "DONE", tempDir);
77
+
78
+ const statePath = getRalphStatePath("save-test", "test-step", tempDir);
79
+ expect(existsSync(statePath)).toBe(true);
80
+ const content = readFileSync(statePath, "utf-8");
81
+ expect(content).toContain("active: true");
82
+ expect(content).toContain("iteration: 1");
83
+ });
84
+ });
85
+
86
+ describe("loadRalphState", () => {
87
+ it("loads existing ralph state", () => {
88
+ createRalphState("load-test", "test-step", "Original prompt", 10, "COMPLETE", tempDir);
89
+
90
+ const loaded = loadRalphState("load-test", "test-step", tempDir);
91
+
92
+ expect(loaded).not.toBeNull();
93
+ expect(loaded?.active).toBe(true);
94
+ expect(loaded?.iteration).toBe(1);
95
+ expect(loaded?.maxIterations).toBe(10);
96
+ expect(loaded?.completionPromise).toBe("COMPLETE");
97
+ expect(loaded?.prompt).toBe("Original prompt");
98
+ });
99
+
100
+ it("returns null for nonexistent state", () => {
101
+ expect(loadRalphState("nonexistent", "step", tempDir)).toBeNull();
102
+ });
103
+ });
104
+
105
+ describe("updateRalphIteration", () => {
106
+ it("increments iteration counter", () => {
107
+ createRalphState("update-test", "test-step", "Test", 10, "DONE", tempDir);
108
+
109
+ const state = updateRalphIteration("update-test", "test-step", tempDir);
110
+
111
+ expect(state).not.toBeNull();
112
+ expect(state?.iteration).toBe(2);
113
+ });
114
+
115
+ it("persists iteration update to file", () => {
116
+ createRalphState("persist-test", "test-step", "Test", 10, "DONE", tempDir);
117
+
118
+ updateRalphIteration("persist-test", "test-step", tempDir);
119
+ const loaded = loadRalphState("persist-test", "test-step", tempDir);
120
+
121
+ expect(loaded).not.toBeNull();
122
+ expect(loaded?.iteration).toBe(2);
123
+ });
124
+
125
+ it("returns null for nonexistent state", () => {
126
+ expect(updateRalphIteration("nonexistent", "step", tempDir)).toBeNull();
127
+ });
128
+ });
129
+
130
+ describe("deactivateRalphState", () => {
131
+ it("deactivates ralph state", () => {
132
+ createRalphState("deactivate-test", "test-step", "Test", 10, "DONE", tempDir);
133
+
134
+ deactivateRalphState("deactivate-test", "test-step", tempDir);
135
+ const loaded = loadRalphState("deactivate-test", "test-step", tempDir);
136
+
137
+ expect(loaded).not.toBeNull();
138
+ expect(loaded?.active).toBe(false);
139
+ });
140
+ });
141
+
142
+ describe("deleteRalphState", () => {
143
+ it("deletes ralph state file", () => {
144
+ createRalphState("delete-test", "test-step", "Test", 10, "DONE", tempDir);
145
+
146
+ const statePath = getRalphStatePath("delete-test", "test-step", tempDir);
147
+ expect(existsSync(statePath)).toBe(true);
148
+
149
+ deleteRalphState("delete-test", "test-step", tempDir);
150
+ expect(existsSync(statePath)).toBe(false);
151
+ });
152
+
153
+ it("does not throw for nonexistent state", () => {
154
+ expect(() => deleteRalphState("nonexistent", "step", tempDir)).not.toThrow();
155
+ });
156
+ });
157
+
158
+ describe("parseRalphState", () => {
159
+ it("parses valid markdown with frontmatter", () => {
160
+ const content = `---
161
+ active: true
162
+ iteration: 3
163
+ max_iterations: 10
164
+ completion_promise: COMPLETE
165
+ started_at: "2026-01-11T14:30:00Z"
166
+ ---
167
+
168
+ # Ralph Loop Prompt
169
+
170
+ Fix all the bugs in the codebase.
171
+ `;
172
+ const state = parseRalphState(content);
173
+
174
+ expect(state).not.toBeNull();
175
+ expect(state?.active).toBe(true);
176
+ expect(state?.iteration).toBe(3);
177
+ expect(state?.maxIterations).toBe(10);
178
+ expect(state?.completionPromise).toBe("COMPLETE");
179
+ expect(state?.prompt).toBe("Fix all the bugs in the codebase.");
180
+ });
181
+
182
+ it("returns null for content without frontmatter", () => {
183
+ expect(parseRalphState("Just plain text without frontmatter")).toBeNull();
184
+ });
185
+
186
+ it("parses incomplete frontmatter with defaults", () => {
187
+ const content = `---
188
+ active: true
189
+ ---`;
190
+ const state = parseRalphState(content);
191
+ expect(state).not.toBeNull();
192
+ expect(state?.active).toBe(true);
193
+ expect(state?.iteration).toBe(1);
194
+ expect(state?.maxIterations).toBe(5);
195
+ });
196
+ });
197
+
198
+ describe("detectCompletionPromise", () => {
199
+ it("detects completion in JSON code block", () => {
200
+ const output = `
201
+ I've completed the task.
202
+
203
+ \`\`\`json
204
+ {
205
+ "ralph_complete": true,
206
+ "promise": "COMPLETED"
207
+ }
208
+ \`\`\`
209
+ `;
210
+ const result = detectCompletionPromise(output, "COMPLETED");
211
+ expect(result.isComplete).toBe(true);
212
+ expect(result.promiseMatched).toBe(true);
213
+ expect(result.promiseValue).toBe("COMPLETED");
214
+ });
215
+
216
+ it("detects completion in plain code block", () => {
217
+ const output = `
218
+ Done!
219
+
220
+ \`\`\`
221
+ {"ralph_complete": true, "promise": "DONE"}
222
+ \`\`\`
223
+ `;
224
+ const result = detectCompletionPromise(output, "DONE");
225
+ expect(result.isComplete).toBe(true);
226
+ expect(result.promiseMatched).toBe(true);
227
+ });
228
+
229
+ it("detects completion in raw JSON", () => {
230
+ const output = 'The task is done. {"ralph_complete": true, "promise": "FINISHED"}';
231
+ const result = detectCompletionPromise(output, "FINISHED");
232
+ expect(result.isComplete).toBe(true);
233
+ expect(result.promiseMatched).toBe(true);
234
+ });
235
+
236
+ it("detects wrong promise value", () => {
237
+ const output = `
238
+ \`\`\`json
239
+ {"ralph_complete": true, "promise": "WRONG_PROMISE"}
240
+ \`\`\`
241
+ `;
242
+ const result = detectCompletionPromise(output, "EXPECTED_PROMISE");
243
+ expect(result.isComplete).toBe(true);
244
+ expect(result.promiseMatched).toBe(false);
245
+ expect(result.promiseValue).toBe("WRONG_PROMISE");
246
+ });
247
+
248
+ it("detects ralph_complete: false as not complete", () => {
249
+ const output = `
250
+ \`\`\`json
251
+ {"ralph_complete": false, "promise": "DONE"}
252
+ \`\`\`
253
+ `;
254
+ const result = detectCompletionPromise(output, "DONE");
255
+ expect(result.isComplete).toBe(false);
256
+ expect(result.promiseMatched).toBe(false);
257
+ });
258
+
259
+ it("returns not complete for output without JSON", () => {
260
+ const result = detectCompletionPromise("Just some regular output", "DONE");
261
+ expect(result.isComplete).toBe(false);
262
+ expect(result.promiseMatched).toBe(false);
263
+ expect(result.promiseValue).toBeNull();
264
+ });
265
+
266
+ it("matches promise case-insensitively", () => {
267
+ const output = '{"ralph_complete": true, "promise": "completed"}';
268
+ const result = detectCompletionPromise(output, "COMPLETED");
269
+ expect(result.isComplete).toBe(true);
270
+ expect(result.promiseMatched).toBe(true);
271
+ });
272
+
273
+ it("handles invalid JSON gracefully", () => {
274
+ const output = `
275
+ \`\`\`json
276
+ {invalid json here}
277
+ \`\`\`
278
+ `;
279
+ const result = detectCompletionPromise(output, "DONE");
280
+ expect(result.isComplete).toBe(false);
281
+ });
282
+
283
+ it("preserves original output in result", () => {
284
+ const output = "Full output text here";
285
+ const result = detectCompletionPromise(output, "DONE");
286
+ expect(result.output).toBe(output);
287
+ });
288
+
289
+ it("detects failure signal in JSON code block", () => {
290
+ const output = `
291
+ I cannot complete the task due to permission errors.
292
+
293
+ \`\`\`json
294
+ {
295
+ "ralph_failed": true,
296
+ "reason": "Permission denied: cannot write to file"
297
+ }
298
+ \`\`\`
299
+ `;
300
+ const result = detectCompletionPromise(output, "COMPLETED");
301
+ expect(result.isComplete).toBe(true);
302
+ expect(result.isFailed).toBe(true);
303
+ expect(result.promiseMatched).toBe(false);
304
+ expect(result.failureReason).toBe("Permission denied: cannot write to file");
305
+ });
306
+
307
+ it("detects failure signal in raw JSON", () => {
308
+ const output = 'Cannot proceed. {"ralph_failed": true, "reason": "Access denied"}';
309
+ const result = detectCompletionPromise(output, "DONE");
310
+ expect(result.isComplete).toBe(true);
311
+ expect(result.isFailed).toBe(true);
312
+ expect(result.failureReason).toBe("Access denied");
313
+ });
314
+
315
+ it("uses default reason for failure without reason field", () => {
316
+ const output = '{"ralph_failed": true}';
317
+ const result = detectCompletionPromise(output, "DONE");
318
+ expect(result.isComplete).toBe(true);
319
+ expect(result.isFailed).toBe(true);
320
+ expect(result.failureReason).toBe("Unknown error");
321
+ });
322
+
323
+ it("prefers completion over failure when both present", () => {
324
+ const output = `
325
+ \`\`\`json
326
+ {"ralph_complete": true, "promise": "DONE"}
327
+ \`\`\`
328
+ Also has failure:
329
+ \`\`\`json
330
+ {"ralph_failed": true, "reason": "Some error"}
331
+ \`\`\`
332
+ `;
333
+ const result = detectCompletionPromise(output, "DONE");
334
+ expect(result.isComplete).toBe(true);
335
+ expect(result.promiseMatched).toBe(true);
336
+ expect(result.isFailed).toBe(false);
337
+ });
338
+ });
339
+
340
+ describe("buildRalphSystemMessage", () => {
341
+ it("formats system message correctly", () => {
342
+ const message = buildRalphSystemMessage(3, 10, "COMPLETE");
343
+ expect(message).toContain("Iteration 3/10");
344
+ expect(message).toContain("COMPLETE");
345
+ expect(message).toContain("ralph_complete");
346
+ expect(message).toContain("7 iterations remaining");
347
+ });
348
+
349
+ it("handles first iteration", () => {
350
+ const message = buildRalphSystemMessage(1, 5, "DONE");
351
+ expect(message).toContain("Iteration 1/5");
352
+ expect(message).toContain("4 iterations remaining");
353
+ });
354
+
355
+ it("handles last iteration", () => {
356
+ const message = buildRalphSystemMessage(10, 10, "FINISHED");
357
+ expect(message).toContain("Iteration 10/10");
358
+ expect(message).toContain("0 iterations remaining");
359
+ });
360
+
361
+ it("includes failure signal instructions", () => {
362
+ const message = buildRalphSystemMessage(1, 5, "DONE");
363
+ expect(message).toContain("ralph_failed");
364
+ expect(message).toContain("Failure Signal");
365
+ expect(message.toLowerCase()).toContain("permission errors");
366
+ });
367
+ });
368
+
369
+ describe("CompletionResult", () => {
370
+ it("supports default values", () => {
371
+ const result: CompletionResult = {
372
+ isComplete: false,
373
+ promiseMatched: false,
374
+ promiseValue: null,
375
+ output: "test output",
376
+ isFailed: false,
377
+ failureReason: null,
378
+ };
379
+
380
+ expect(result.isComplete).toBe(false);
381
+ expect(result.promiseMatched).toBe(false);
382
+ expect(result.promiseValue).toBeNull();
383
+ expect(result.output).toBe("test output");
384
+ });
385
+
386
+ it("supports success state", () => {
387
+ const result: CompletionResult = {
388
+ isComplete: true,
389
+ promiseMatched: true,
390
+ promiseValue: "DONE",
391
+ output: "completed",
392
+ isFailed: false,
393
+ failureReason: null,
394
+ };
395
+
396
+ expect(result.isComplete).toBe(true);
397
+ expect(result.promiseMatched).toBe(true);
398
+ expect(result.promiseValue).toBe("DONE");
399
+ expect(result.isFailed).toBe(false);
400
+ expect(result.failureReason).toBeNull();
401
+ });
402
+
403
+ it("supports failure state", () => {
404
+ const result: CompletionResult = {
405
+ isComplete: true,
406
+ promiseMatched: false,
407
+ promiseValue: null,
408
+ output: "failed output",
409
+ isFailed: true,
410
+ failureReason: "Permission denied",
411
+ };
412
+
413
+ expect(result.isComplete).toBe(true);
414
+ expect(result.isFailed).toBe(true);
415
+ expect(result.failureReason).toBe("Permission denied");
416
+ expect(result.promiseMatched).toBe(false);
417
+ });
418
+ });