agentic-forge 0.0.0 → 0.7.1

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 (212) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +64 -24
  3. package/{src/claude → dist/authoring}/.claude/skills/workflow-builder/SKILL.md +2 -2
  4. package/{src/claude → dist/authoring}/.claude/skills/workflow-builder/references/REFERENCE.md +9 -3
  5. package/{src/claude → dist/authoring}/.claude/skills/workflow-builder/references/workflow-example.yaml +15 -8
  6. package/dist/checkpoints/manager.d.ts +5 -0
  7. package/dist/checkpoints/manager.js +87 -0
  8. package/dist/checkpoints/manager.js.map +1 -0
  9. package/{src → dist}/claude/.claude/skills/analyze/SKILL.md +1 -1
  10. package/{src → dist}/claude/.claude/skills/create-checkpoint/SKILL.md +1 -1
  11. package/{src → dist}/claude/.claude/skills/create-log/SKILL.md +1 -1
  12. package/{src → dist}/claude/.claude/skills/fix-analyze/SKILL.md +1 -1
  13. package/{src → dist}/claude/.claude/skills/git-branch/SKILL.md +1 -1
  14. package/{src → dist}/claude/.claude/skills/git-commit/SKILL.md +1 -1
  15. package/{src → dist}/claude/.claude/skills/git-pr/SKILL.md +1 -1
  16. package/{src → dist}/claude/.claude/skills/sdlc-plan/SKILL.md +1 -1
  17. package/{src → dist}/claude/.claude/skills/sdlc-review/SKILL.md +1 -1
  18. package/dist/cli.d.ts +3 -0
  19. package/dist/cli.js +173 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/commands/authoring-dir.d.ts +2 -0
  22. package/dist/commands/authoring-dir.js +9 -0
  23. package/dist/commands/authoring-dir.js.map +1 -0
  24. package/dist/commands/config-cmd.d.ts +2 -0
  25. package/dist/commands/config-cmd.js +30 -0
  26. package/dist/commands/config-cmd.js.map +1 -0
  27. package/{src/commands/index.ts → dist/commands/index.d.ts} +2 -10
  28. package/dist/commands/index.js +14 -0
  29. package/dist/commands/index.js.map +1 -0
  30. package/dist/commands/init.d.ts +6 -0
  31. package/dist/commands/init.js +83 -0
  32. package/dist/commands/init.js.map +1 -0
  33. package/dist/commands/release-notes.d.ts +5 -0
  34. package/dist/commands/release-notes.js +68 -0
  35. package/dist/commands/release-notes.js.map +1 -0
  36. package/dist/commands/resume.d.ts +5 -0
  37. package/dist/commands/resume.js +79 -0
  38. package/dist/commands/resume.js.map +1 -0
  39. package/dist/commands/run.d.ts +27 -0
  40. package/dist/commands/run.js +243 -0
  41. package/dist/commands/run.js.map +1 -0
  42. package/dist/commands/shortcuts.d.ts +2 -0
  43. package/dist/commands/shortcuts.js +11 -0
  44. package/dist/commands/shortcuts.js.map +1 -0
  45. package/dist/commands/skills-dir.d.ts +2 -0
  46. package/dist/commands/skills-dir.js +9 -0
  47. package/dist/commands/skills-dir.js.map +1 -0
  48. package/dist/commands/status.d.ts +4 -0
  49. package/dist/commands/status.js +99 -0
  50. package/dist/commands/status.js.map +1 -0
  51. package/dist/commands/update.d.ts +4 -0
  52. package/dist/commands/update.js +65 -0
  53. package/dist/commands/update.js.map +1 -0
  54. package/dist/commands/version.d.ts +3 -0
  55. package/dist/commands/version.js +26 -0
  56. package/dist/commands/version.js.map +1 -0
  57. package/dist/commands/workflows.d.ts +4 -0
  58. package/dist/commands/workflows.js +111 -0
  59. package/dist/commands/workflows.js.map +1 -0
  60. package/dist/config.d.ts +8 -0
  61. package/dist/config.js +110 -0
  62. package/dist/config.js.map +1 -0
  63. package/dist/console.d.ts +103 -0
  64. package/dist/console.js +670 -0
  65. package/dist/console.js.map +1 -0
  66. package/dist/executor.d.ts +27 -0
  67. package/dist/executor.js +236 -0
  68. package/dist/executor.js.map +1 -0
  69. package/dist/git/worktree.d.ts +23 -0
  70. package/dist/git/worktree.js +170 -0
  71. package/dist/git/worktree.js.map +1 -0
  72. package/dist/logging/logger.d.ts +27 -0
  73. package/dist/logging/logger.js +69 -0
  74. package/dist/logging/logger.js.map +1 -0
  75. package/dist/orchestrator.d.ts +44 -0
  76. package/dist/orchestrator.js +587 -0
  77. package/dist/orchestrator.js.map +1 -0
  78. package/dist/parser.d.ts +17 -0
  79. package/dist/parser.js +184 -0
  80. package/dist/parser.js.map +1 -0
  81. package/dist/progress.d.ts +29 -0
  82. package/dist/progress.js +275 -0
  83. package/dist/progress.js.map +1 -0
  84. package/dist/ralph-loop.d.ts +26 -0
  85. package/dist/ralph-loop.js +194 -0
  86. package/dist/ralph-loop.js.map +1 -0
  87. package/dist/renderer.d.ts +15 -0
  88. package/dist/renderer.js +123 -0
  89. package/dist/renderer.js.map +1 -0
  90. package/dist/runner.d.ts +84 -0
  91. package/dist/runner.js +529 -0
  92. package/dist/runner.js.map +1 -0
  93. package/dist/signal-manager.d.ts +16 -0
  94. package/dist/signal-manager.js +50 -0
  95. package/dist/signal-manager.js.map +1 -0
  96. package/dist/steps/base.d.ts +28 -0
  97. package/dist/steps/base.js +23 -0
  98. package/dist/steps/base.js.map +1 -0
  99. package/dist/steps/conditional-step.d.ts +12 -0
  100. package/dist/steps/conditional-step.js +106 -0
  101. package/dist/steps/conditional-step.js.map +1 -0
  102. package/{src/steps/index.ts → dist/steps/index.d.ts} +1 -9
  103. package/dist/steps/index.js +8 -0
  104. package/dist/steps/index.js.map +1 -0
  105. package/dist/steps/parallel-step.d.ts +11 -0
  106. package/dist/steps/parallel-step.js +166 -0
  107. package/dist/steps/parallel-step.js.map +1 -0
  108. package/dist/steps/prompt-step.d.ts +8 -0
  109. package/dist/steps/prompt-step.js +94 -0
  110. package/dist/steps/prompt-step.js.map +1 -0
  111. package/dist/steps/ralph-loop-step.d.ts +8 -0
  112. package/dist/steps/ralph-loop-step.js +132 -0
  113. package/dist/steps/ralph-loop-step.js.map +1 -0
  114. package/dist/steps/serial-step.d.ts +10 -0
  115. package/dist/steps/serial-step.js +57 -0
  116. package/dist/steps/serial-step.js.map +1 -0
  117. package/dist/types.d.ts +118 -0
  118. package/dist/types.js +10 -0
  119. package/dist/types.js.map +1 -0
  120. package/package.json +59 -2
  121. package/.gitattributes +0 -24
  122. package/.github/workflows/ci.yml +0 -70
  123. package/.markdownlint-cli2.jsonc +0 -16
  124. package/.prettierignore +0 -3
  125. package/.prettierrc +0 -6
  126. package/.vscode/agentic-forge.code-workspace +0 -26
  127. package/CHANGELOG.md +0 -100
  128. package/CLAUDE.md +0 -158
  129. package/CONTRIBUTING.md +0 -152
  130. package/biome.json +0 -21
  131. package/scripts/copy-assets.js +0 -21
  132. package/src/checkpoints/manager.ts +0 -119
  133. package/src/cli.ts +0 -182
  134. package/src/commands/config-cmd.ts +0 -28
  135. package/src/commands/init.ts +0 -96
  136. package/src/commands/release-notes.ts +0 -85
  137. package/src/commands/resume.ts +0 -103
  138. package/src/commands/run.ts +0 -234
  139. package/src/commands/shortcuts.ts +0 -11
  140. package/src/commands/skills-dir.ts +0 -11
  141. package/src/commands/status.ts +0 -112
  142. package/src/commands/update.ts +0 -64
  143. package/src/commands/version.ts +0 -27
  144. package/src/commands/workflows.ts +0 -129
  145. package/src/config.ts +0 -129
  146. package/src/console.ts +0 -790
  147. package/src/executor.ts +0 -354
  148. package/src/git/worktree.ts +0 -236
  149. package/src/logging/logger.ts +0 -95
  150. package/src/orchestrator.ts +0 -815
  151. package/src/parser.ts +0 -225
  152. package/src/progress.ts +0 -306
  153. package/src/ralph-loop.ts +0 -260
  154. package/src/renderer.ts +0 -164
  155. package/src/runner.ts +0 -634
  156. package/src/signal-manager.ts +0 -55
  157. package/src/steps/base.ts +0 -71
  158. package/src/steps/conditional-step.ts +0 -144
  159. package/src/steps/parallel-step.ts +0 -213
  160. package/src/steps/prompt-step.ts +0 -121
  161. package/src/steps/ralph-loop-step.ts +0 -186
  162. package/src/steps/serial-step.ts +0 -84
  163. package/src/types.ts +0 -141
  164. package/tests/config.test.ts +0 -219
  165. package/tests/console.test.ts +0 -506
  166. package/tests/executor.test.ts +0 -339
  167. package/tests/init.test.ts +0 -86
  168. package/tests/logger.test.ts +0 -110
  169. package/tests/parser.test.ts +0 -290
  170. package/tests/progress.test.ts +0 -345
  171. package/tests/ralph-loop.test.ts +0 -418
  172. package/tests/renderer.test.ts +0 -350
  173. package/tests/runner.test.ts +0 -497
  174. package/tests/setup.test.ts +0 -7
  175. package/tests/signal-manager.test.ts +0 -26
  176. package/tests/steps.test.ts +0 -412
  177. package/tests/worktree.test.ts +0 -411
  178. package/tsconfig.json +0 -18
  179. package/vitest.config.ts +0 -8
  180. /package/{src → dist}/agents/explorer.md +0 -0
  181. /package/{src → dist}/agents/reviewer.md +0 -0
  182. /package/{src → dist}/claude/.claude/skills/analyze/references/bug.md +0 -0
  183. /package/{src → dist}/claude/.claude/skills/analyze/references/debt.md +0 -0
  184. /package/{src → dist}/claude/.claude/skills/analyze/references/doc.md +0 -0
  185. /package/{src → dist}/claude/.claude/skills/analyze/references/security.md +0 -0
  186. /package/{src → dist}/claude/.claude/skills/analyze/references/style.md +0 -0
  187. /package/{src → dist}/claude/.claude/skills/orchestrate/SKILL.md +0 -0
  188. /package/{src → dist}/claude/.claude/skills/sdlc-plan/references/bug.md +0 -0
  189. /package/{src → dist}/claude/.claude/skills/sdlc-plan/references/chore.md +0 -0
  190. /package/{src → dist}/claude/.claude/skills/sdlc-plan/references/feature.md +0 -0
  191. /package/{src → dist}/prompts/agentic-system.md +0 -0
  192. /package/{src → dist}/templates/analysis/bug.md.j2 +0 -0
  193. /package/{src → dist}/templates/analysis/debt.md.j2 +0 -0
  194. /package/{src → dist}/templates/analysis/doc.md.j2 +0 -0
  195. /package/{src → dist}/templates/analysis/security.md.j2 +0 -0
  196. /package/{src → dist}/templates/analysis/style.md.j2 +0 -0
  197. /package/{src → dist}/templates/analysis-summary.md.j2 +0 -0
  198. /package/{src → dist}/templates/checkpoint.md.j2 +0 -0
  199. /package/{src → dist}/templates/implementation-report.md.j2 +0 -0
  200. /package/{src → dist}/templates/memory.md.j2 +0 -0
  201. /package/{src → dist}/templates/plan-bug.md.j2 +0 -0
  202. /package/{src → dist}/templates/plan-chore.md.j2 +0 -0
  203. /package/{src → dist}/templates/plan-feature.md.j2 +0 -0
  204. /package/{src → dist}/templates/progress.json.j2 +0 -0
  205. /package/{src → dist}/templates/ralph-report.md.j2 +0 -0
  206. /package/{src → dist}/workflows/analyze-codebase-merge.yaml +0 -0
  207. /package/{src → dist}/workflows/analyze-codebase.yaml +0 -0
  208. /package/{src → dist}/workflows/analyze-single.yaml +0 -0
  209. /package/{src → dist}/workflows/demo.yaml +0 -0
  210. /package/{src → dist}/workflows/one-shot.yaml +0 -0
  211. /package/{src → dist}/workflows/plan-build-review.yaml +0 -0
  212. /package/{src → dist}/workflows/ralph-loop.yaml +0 -0
package/src/console.ts DELETED
@@ -1,790 +0,0 @@
1
- /** Console output utilities for workflow execution. */
2
-
3
- import type { Writable } from "node:stream";
4
-
5
- // --- Enums and constants ---
6
-
7
- export const OutputLevel = {
8
- BASE: "base",
9
- ALL: "all",
10
- } as const;
11
- export type OutputLevel = (typeof OutputLevel)[keyof typeof OutputLevel];
12
-
13
- export const Color = {
14
- RESET: "\x1b[0m",
15
- BOLD: "\x1b[1m",
16
- DIM: "\x1b[2m",
17
- RED: "\x1b[31m",
18
- GREEN: "\x1b[32m",
19
- YELLOW: "\x1b[33m",
20
- BLUE: "\x1b[34m",
21
- MAGENTA: "\x1b[35m",
22
- CYAN: "\x1b[36m",
23
- WHITE: "\x1b[37m",
24
- BRIGHT_RED: "\x1b[91m",
25
- BRIGHT_GREEN: "\x1b[92m",
26
- BRIGHT_YELLOW: "\x1b[93m",
27
- BRIGHT_BLUE: "\x1b[94m",
28
- BRIGHT_CYAN: "\x1b[96m",
29
- } as const;
30
- export type Color = (typeof Color)[keyof typeof Color];
31
-
32
- /** Maximum characters to accumulate in BASE mode before truncating (1MB). */
33
- const MAX_ACCUMULATED_TEXT = 1024 * 1024;
34
-
35
- // --- Utility functions ---
36
-
37
- export function supportsColor(): boolean {
38
- if (typeof process.stdout.isTTY !== "boolean") {
39
- return false;
40
- }
41
- return process.stdout.isTTY;
42
- }
43
-
44
- export function colorize(text: string, ...colors: Color[]): string {
45
- if (!supportsColor()) {
46
- return text;
47
- }
48
- const colorCodes = colors.join("");
49
- return `${colorCodes}${text}${Color.RESET}`;
50
- }
51
-
52
- /**
53
- * Format a model name for display (e.g., "claude-sonnet-4-5-20250929" -> "Sonnet 4.5").
54
- * This is a simplified version; the full version lives in runner.ts.
55
- * We accept an optional formatter to avoid circular imports.
56
- */
57
- type ModelFormatter = (model: string) => string | null;
58
-
59
- // --- ParallelOutputHandler ---
60
-
61
- export class ParallelOutputHandler {
62
- stream: Writable;
63
- level: OutputLevel;
64
- private printFn: ((message: string) => void) | null;
65
-
66
- private branches: string[] = [];
67
- private branchIndex: Map<string, number> = new Map();
68
- private branchDone: Map<string, boolean> = new Map();
69
- private deferredMessages: string[] = [];
70
- private linesPerBranch = 4;
71
- private active = false;
72
-
73
- // Per-branch accumulation (single-threaded, so we use maps instead of thread-local)
74
- private currentBranch: string | null = null;
75
- private accumulatedText = "";
76
- private accumulatedRole = "";
77
- private accumulatedModel: string | null = null;
78
- private baseAccumulatedText = "";
79
-
80
- // Message queue for ALL mode parallel
81
- private messageQueue: Array<[string, string, string, string | null]> = [];
82
-
83
- constructor(
84
- stream: Writable = process.stdout,
85
- level: OutputLevel = OutputLevel.BASE,
86
- printFn?: (message: string) => void,
87
- ) {
88
- this.stream = stream;
89
- this.level = level;
90
- this.printFn = printFn ?? null;
91
- }
92
-
93
- private print(message: string): void {
94
- if (this.printFn) {
95
- this.printFn(message);
96
- } else {
97
- this.stream.write(`${message}\n`);
98
- }
99
- }
100
-
101
- get isActive(): boolean {
102
- return this.active;
103
- }
104
-
105
- get hasBranches(): boolean {
106
- return this.branches.length > 0;
107
- }
108
-
109
- registerBranches(branches: string[]): void {
110
- this.branches = [...branches];
111
- this.branchIndex = new Map(branches.map((name, idx) => [name, idx]));
112
- this.branchDone = new Map(branches.map((name) => [name, false]));
113
- this.deferredMessages = [];
114
-
115
- if (this.level === OutputLevel.BASE && supportsColor()) {
116
- for (const branch of branches) {
117
- const prefix = colorize(`[${branch}]`, Color.CYAN, Color.BOLD);
118
- this.print(prefix);
119
- const waiting = colorize("waiting...", Color.DIM);
120
- this.print(` ${waiting}`);
121
- this.print(""); // Message line 2
122
- this.print(""); // Blank line
123
- }
124
- }
125
- }
126
-
127
- updateBranchLine(branch: string, text: string, maxWidth = 100): void {
128
- if (!supportsColor() || !this.branchIndex.has(branch)) return;
129
- if (this.branchDone.get(branch)) return;
130
-
131
- const idx = this.branchIndex.get(branch) ?? 0;
132
- const totalBranches = this.branches.length;
133
- const totalLines = totalBranches * this.linesPerBranch;
134
- const branchStartLine = idx * this.linesPerBranch;
135
- const messageLine1 = branchStartLine + 1;
136
- const messageLine2 = branchStartLine + 2;
137
- const linesUpToMsg1 = totalLines - messageLine1;
138
- const linesUpToMsg2 = totalLines - messageLine2;
139
-
140
- const trimmed = text.trim();
141
- let line1: string;
142
- let line2 = "";
143
- if (trimmed.length <= maxWidth) {
144
- line1 = trimmed;
145
- } else {
146
- let breakPoint = trimmed.lastIndexOf(" ", maxWidth);
147
- if (breakPoint === -1) breakPoint = maxWidth;
148
- line1 = trimmed.slice(0, breakPoint).trim();
149
- line2 = trimmed.slice(breakPoint).trim();
150
- if (line2.length > maxWidth) {
151
- line2 = `${line2.slice(0, maxWidth - 3)}...`;
152
- }
153
- }
154
-
155
- const prefix = colorize("...", Color.DIM);
156
- const w = this.stream;
157
- w.write("\x1b[s"); // Save cursor
158
- w.write(`\x1b[${linesUpToMsg1}A`); // Move up
159
- w.write("\x1b[K"); // Clear line
160
- w.write(` ${prefix} ${line1}`);
161
- w.write(`\x1b[${linesUpToMsg1 - linesUpToMsg2}B`); // Move down to line 2
162
- w.write("\r\x1b[K"); // Clear line
163
- if (line2) {
164
- w.write(` ${prefix} ${line2}`);
165
- }
166
- w.write("\x1b[u"); // Restore cursor
167
- }
168
-
169
- markBranchDone(branch: string, success = true): void {
170
- if (!supportsColor() || !this.branchIndex.has(branch)) return;
171
-
172
- this.branchDone.set(branch, true);
173
- const idx = this.branchIndex.get(branch) ?? 0;
174
- const totalBranches = this.branches.length;
175
- const totalLines = totalBranches * this.linesPerBranch;
176
- const branchStartLine = idx * this.linesPerBranch;
177
- const linesUpToHeader = totalLines - branchStartLine;
178
- const messageLine1 = branchStartLine + 1;
179
- const messageLine2 = branchStartLine + 2;
180
- const linesUpToMsg1 = totalLines - messageLine1;
181
- const linesUpToMsg2 = totalLines - messageLine2;
182
-
183
- const branchPrefix = colorize(`[${branch}]`, Color.CYAN, Color.BOLD);
184
- const status = success
185
- ? colorize("[Done]", Color.BRIGHT_GREEN, Color.BOLD)
186
- : colorize("[Failed]", Color.BRIGHT_RED, Color.BOLD);
187
-
188
- const w = this.stream;
189
- w.write("\x1b[s");
190
- w.write(`\x1b[${linesUpToHeader}A`);
191
- w.write("\x1b[K");
192
- w.write(`${branchPrefix} ${status}`);
193
- w.write(`\x1b[${linesUpToHeader - linesUpToMsg1}B`);
194
- w.write("\r\x1b[K");
195
- w.write(`\x1b[${linesUpToMsg1 - linesUpToMsg2}B`);
196
- w.write("\r\x1b[K");
197
- w.write("\x1b[u");
198
- }
199
-
200
- private clearDisplay(): void {
201
- if (this.branches.length === 0 || !supportsColor()) {
202
- this.branches = [];
203
- this.branchIndex = new Map();
204
- this.branchDone = new Map();
205
- return;
206
- }
207
-
208
- const totalLines = this.branches.length * this.linesPerBranch;
209
- for (let i = 0; i < totalLines; i++) {
210
- this.stream.write("\x1b[A\x1b[2K");
211
- }
212
-
213
- this.branches = [];
214
- this.branchIndex = new Map();
215
- this.branchDone = new Map();
216
- }
217
-
218
- enter(): void {
219
- this.active = true;
220
- if (this.level === OutputLevel.ALL) {
221
- this.messageQueue = [];
222
- }
223
- }
224
-
225
- exit(): void {
226
- if (this.level === OutputLevel.BASE) {
227
- if (this.branches.length > 0) {
228
- this.clearDisplay();
229
- }
230
- for (const msg of this.deferredMessages) {
231
- this.print(msg);
232
- }
233
- this.deferredMessages = [];
234
- }
235
-
236
- // Flush remaining messages in ALL mode
237
- if (this.level === OutputLevel.ALL) {
238
- for (const [branch, role, text, model] of this.messageQueue) {
239
- this.printBranchMessage(branch, role, text, model);
240
- }
241
- this.messageQueue = [];
242
- }
243
-
244
- this.active = false;
245
- }
246
-
247
- deferMessage(message: string): void {
248
- this.deferredMessages.push(message);
249
- }
250
-
251
- setBranch(branchName: string): void {
252
- this.currentBranch = branchName;
253
- this.accumulatedText = "";
254
- this.accumulatedRole = "";
255
- this.accumulatedModel = null;
256
- this.baseAccumulatedText = "";
257
- }
258
-
259
- getCurrentBranch(): string | null {
260
- return this.currentBranch;
261
- }
262
-
263
- enqueueCurrentMessage(): void {
264
- if (
265
- this.level === OutputLevel.ALL &&
266
- this.active &&
267
- this.accumulatedText.trim() &&
268
- this.accumulatedRole &&
269
- this.currentBranch
270
- ) {
271
- this.messageQueue.push([
272
- this.currentBranch,
273
- this.accumulatedRole,
274
- this.accumulatedText.trim(),
275
- this.accumulatedModel,
276
- ]);
277
- }
278
-
279
- this.accumulatedText = "";
280
- this.accumulatedRole = "";
281
- this.accumulatedModel = null;
282
- }
283
-
284
- private printBranchMessage(
285
- branch: string,
286
- role: string,
287
- text: string,
288
- model: string | null,
289
- formatModelName?: ModelFormatter,
290
- ): void {
291
- const formattedModel = formatModelName && model ? formatModelName(model) : null;
292
- const branchPrefix = colorize(`[${branch}] `, Color.CYAN, Color.BOLD);
293
-
294
- if (role === "user") {
295
- const prefix = colorize(">", Color.BRIGHT_CYAN, Color.BOLD);
296
- const label = colorize(" [user]", Color.DIM);
297
- this.print(`\n${branchPrefix}${prefix}${label}`);
298
- for (const line of text.split("\n")) {
299
- const coloredLine = colorize(line, Color.GREEN);
300
- this.print(` ${coloredLine}`);
301
- }
302
- } else {
303
- const bullet = colorize("*", Color.BRIGHT_GREEN, Color.BOLD);
304
- let modelLabel = "";
305
- if (formattedModel) {
306
- modelLabel = ` ${colorize(`[${formattedModel}]`, Color.DIM)}`;
307
- }
308
- const lines = text.split("\n");
309
- this.print(`\n${branchPrefix}${bullet}${modelLabel} ${lines[0]}`);
310
- for (const line of lines.slice(1)) {
311
- this.print(` ${line}`);
312
- }
313
- }
314
- }
315
-
316
- accumulateText(text: string, role: string, model?: string | null): void {
317
- if (this.accumulatedRole && this.accumulatedRole !== role && this.accumulatedText.trim()) {
318
- this.enqueueCurrentMessage();
319
- }
320
-
321
- this.accumulatedText += text;
322
- this.accumulatedRole = role;
323
- if (model) {
324
- this.accumulatedModel = model;
325
- }
326
- }
327
-
328
- accumulateBaseText(text: string): string {
329
- this.baseAccumulatedText += text;
330
- const accumulated = this.baseAccumulatedText.trim();
331
- if (accumulated) {
332
- const lines = accumulated.split("\n");
333
- return lines[lines.length - 1];
334
- }
335
- return "";
336
- }
337
- }
338
-
339
- // --- ConsoleOutput ---
340
-
341
- export class ConsoleOutput {
342
- level: OutputLevel;
343
- stream: Writable;
344
- _baseAccumulatedText = "";
345
- private baseLastDisplayLines = 0;
346
- _parallelHandler: ParallelOutputHandler;
347
- private formatModelName: ModelFormatter | null = null;
348
-
349
- constructor(level: OutputLevel = OutputLevel.BASE, stream: Writable = process.stdout) {
350
- this.level = level;
351
- this.stream = stream;
352
- this._parallelHandler = new ParallelOutputHandler(stream, level, (msg) => this.printLine(msg));
353
- }
354
-
355
- /** Set model name formatter to avoid circular imports with runner. */
356
- setModelFormatter(fn: ModelFormatter): void {
357
- this.formatModelName = fn;
358
- }
359
-
360
- private printLine(message: string, end = "\n"): void {
361
- this.stream.write(`${message}${end}`);
362
- }
363
-
364
- private printInplace(message: string): void {
365
- const lines = message.trim().split("\n");
366
- const numLines = lines.length;
367
-
368
- if (supportsColor()) {
369
- if (this.baseLastDisplayLines > 0) {
370
- this.stream.write(`\x1b[${this.baseLastDisplayLines}A`);
371
- this.stream.write("\x1b[J");
372
- }
373
- const content = `${lines.join("\n")}\n`;
374
- this.stream.write(content);
375
- } else {
376
- const lastLine = lines[lines.length - 1] ?? "";
377
- const truncated = lastLine.length > 80 ? `${lastLine.slice(0, 80)}...` : lastLine;
378
- this.printLine(` ... ${truncated}`);
379
- }
380
-
381
- this.baseLastDisplayLines = numLines;
382
- }
383
-
384
- private clearInplace(): void {
385
- if (this.baseLastDisplayLines > 0 && supportsColor()) {
386
- this.stream.write(`\x1b[${this.baseLastDisplayLines}A\x1b[J`);
387
- }
388
- this.baseLastDisplayLines = 0;
389
- }
390
-
391
- // --- Parallel mode delegation ---
392
-
393
- registerParallelBranches(branches: string[]): void {
394
- this._parallelHandler.registerBranches(branches);
395
- }
396
-
397
- enterParallelMode(): void {
398
- this._parallelHandler.enter();
399
- }
400
-
401
- exitParallelMode(): void {
402
- this._parallelHandler.exit();
403
- }
404
-
405
- setParallelBranch(branchName: string): void {
406
- this._parallelHandler.setBranch(branchName);
407
- }
408
-
409
- flushParallelBranch(_branchName: string): void {
410
- // No-op, kept for compatibility
411
- }
412
-
413
- markParallelBranchDone(branch: string, success = true): void {
414
- this._parallelHandler.markBranchDone(branch, success);
415
- }
416
-
417
- private isParallelMode(): boolean {
418
- return this._parallelHandler.isActive;
419
- }
420
-
421
- private shouldDeferMessage(): boolean {
422
- return (
423
- this._parallelHandler.isActive &&
424
- this.level === OutputLevel.BASE &&
425
- this._parallelHandler.hasBranches
426
- );
427
- }
428
-
429
- private deferMessage(message: string): void {
430
- this._parallelHandler.deferMessage(message);
431
- }
432
-
433
- // --- Workflow-level messages ---
434
-
435
- workflowStart(workflowName: string, workflowId: string): void {
436
- const header = colorize(`Workflow: ${workflowName}`, Color.BOLD, Color.BRIGHT_CYAN);
437
- const idStr = colorize(`[${workflowId}]`, Color.DIM);
438
- this.printLine(`\n${header} ${idStr}`);
439
- this.printLine(colorize("-".repeat(50), Color.DIM));
440
- }
441
-
442
- workflowComplete(workflowName: string, status: string): void {
443
- this.printLine(colorize("-".repeat(50), Color.DIM));
444
- const statusStr =
445
- status === "completed"
446
- ? colorize("COMPLETED", Color.BOLD, Color.BRIGHT_GREEN)
447
- : colorize(status.toUpperCase(), Color.BOLD, Color.BRIGHT_RED);
448
- this.printLine(`Workflow ${workflowName}: ${statusStr}\n`);
449
- }
450
-
451
- // --- Step-level messages ---
452
-
453
- stepStart(stepName: string, stepType?: string | null, model?: string | null): void {
454
- let modelStr = "";
455
- if (this.level === OutputLevel.BASE && model) {
456
- const formattedModel = this.formatModelName ? this.formatModelName(model) : model;
457
- if (formattedModel) {
458
- modelStr = `${colorize(`[${formattedModel}] `, Color.DIM)}`;
459
- }
460
- }
461
-
462
- const stepStr = colorize(`Step: ${stepName}`, Color.BOLD);
463
- const typeStr = stepType ? colorize(`[${stepType}]`, Color.DIM) : "";
464
- const formatted = `\n${modelStr}${stepStr} ${typeStr}`;
465
- if (this.shouldDeferMessage()) {
466
- this.deferMessage(formatted);
467
- } else {
468
- this.printLine(formatted);
469
- }
470
- }
471
-
472
- stepComplete(stepName: string, summary?: string | null): void {
473
- if (this.level === OutputLevel.BASE && !this.isParallelMode()) {
474
- this.baseLastDisplayLines = 0;
475
- this._baseAccumulatedText = "";
476
- }
477
-
478
- const check = colorize("[OK]", Color.BRIGHT_GREEN);
479
- const name = colorize(stepName, Color.GREEN);
480
- const mainLine = `${check} ${name} completed`;
481
-
482
- const lines = [mainLine];
483
- if (summary && this.level === OutputLevel.BASE) {
484
- const summaryLines = summary.trim().split("\n");
485
- for (const line of summaryLines.slice(0, 5)) {
486
- const truncated = line.length > 200 ? `${line.slice(0, 200)}...` : line;
487
- lines.push(colorize(` ${truncated}`, Color.DIM));
488
- }
489
- if (summaryLines.length > 5) {
490
- lines.push(colorize(` ... (${summaryLines.length - 5} more lines)`, Color.DIM));
491
- }
492
- }
493
-
494
- if (this.shouldDeferMessage()) {
495
- const branch = this._parallelHandler.getCurrentBranch();
496
- if (branch) {
497
- this.markParallelBranchDone(branch);
498
- }
499
- for (const line of lines) {
500
- this.deferMessage(line);
501
- }
502
- } else {
503
- for (const line of lines) {
504
- this.printLine(line);
505
- }
506
- }
507
- }
508
-
509
- stepFailed(stepName: string, error?: string | null): void {
510
- if (this.level === OutputLevel.BASE && !this.isParallelMode()) {
511
- this.baseLastDisplayLines = 0;
512
- this._baseAccumulatedText = "";
513
- }
514
-
515
- const cross = colorize("[FAIL]", Color.BRIGHT_RED);
516
- const name = colorize(stepName, Color.RED);
517
- const mainLine = `${cross} ${name} failed`;
518
-
519
- const lines = [mainLine];
520
- if (error) {
521
- const errorLines = error.trim().split("\n");
522
- for (const line of errorLines.slice(0, 10)) {
523
- const truncated = line.length > 200 ? `${line.slice(0, 200)}...` : line;
524
- lines.push(colorize(` ${truncated}`, Color.RED));
525
- }
526
- if (errorLines.length > 10) {
527
- lines.push(colorize(` ... (${errorLines.length - 10} more lines)`, Color.DIM));
528
- }
529
- }
530
-
531
- if (this.shouldDeferMessage()) {
532
- const branch = this._parallelHandler.getCurrentBranch();
533
- if (branch) {
534
- this.markParallelBranchDone(branch, false);
535
- }
536
- for (const line of lines) {
537
- this.deferMessage(line);
538
- }
539
- } else {
540
- for (const line of lines) {
541
- this.printLine(line);
542
- }
543
- }
544
- }
545
-
546
- stepRetry(stepName: string, attempt: number, maxAttempts: number, error?: string | null): void {
547
- const retry = colorize(`[RETRY ${attempt}/${maxAttempts}]`, Color.YELLOW);
548
- const name = colorize(stepName, Color.YELLOW);
549
- const mainLine = `${retry} ${name}`;
550
-
551
- const lines = [mainLine];
552
- if (error) {
553
- const firstLine = error.trim().split("\n")[0].slice(0, 100);
554
- lines.push(colorize(` Reason: ${firstLine}`, Color.DIM));
555
- }
556
-
557
- if (this.shouldDeferMessage()) {
558
- for (const line of lines) {
559
- this.deferMessage(line);
560
- }
561
- } else {
562
- for (const line of lines) {
563
- this.printLine(line);
564
- }
565
- }
566
- }
567
-
568
- // --- Ralph loop messages ---
569
-
570
- ralphIterationStart(stepName: string, iteration: number, maxIterations: number): void {
571
- if (this.level === OutputLevel.BASE && !this.isParallelMode()) {
572
- this.baseLastDisplayLines = 0;
573
- this._baseAccumulatedText = "";
574
- }
575
-
576
- const progress = colorize(`[${iteration}/${maxIterations}]`, Color.CYAN);
577
- const name = colorize(stepName, Color.CYAN);
578
- this.printLine(`${progress} ${name} iteration`);
579
- }
580
-
581
- ralphIteration(
582
- _stepName: string,
583
- _iteration: number,
584
- _maxIterations: number,
585
- summary?: string | null,
586
- ): void {
587
- if (this.level === OutputLevel.BASE && !this.isParallelMode()) {
588
- return;
589
- }
590
-
591
- if (summary && this.level === OutputLevel.ALL) {
592
- const summaryLines = summary.trim().split("\n");
593
- for (const line of summaryLines.slice(0, 3)) {
594
- const truncated = line.length > 150 ? `${line.slice(0, 150)}...` : line;
595
- this.printLine(colorize(` ${truncated}`, Color.DIM));
596
- }
597
- }
598
- }
599
-
600
- ralphComplete(stepName: string, iteration: number, maxIterations: number): void {
601
- if (this.level === OutputLevel.BASE && !this.isParallelMode()) {
602
- this.baseLastDisplayLines = 0;
603
- this._baseAccumulatedText = "";
604
- }
605
-
606
- const check = colorize("[OK]", Color.BRIGHT_GREEN);
607
- const name = colorize(stepName, Color.GREEN);
608
- this.printLine(`${check} ${name} completed at iteration ${iteration}/${maxIterations}`);
609
- }
610
-
611
- ralphMaxIterations(stepName: string, maxIterations: number): void {
612
- if (this.level === OutputLevel.BASE && !this.isParallelMode()) {
613
- this.baseLastDisplayLines = 0;
614
- this._baseAccumulatedText = "";
615
- }
616
-
617
- const warn = colorize("[WARN]", Color.YELLOW);
618
- const name = colorize(stepName, Color.YELLOW);
619
- this.printLine(`${warn} ${name} reached max iterations (${maxIterations})`);
620
- }
621
-
622
- // --- Generic messages ---
623
-
624
- info(message: string): void {
625
- const infoPrefix = colorize("[INFO]", Color.BLUE);
626
- const formatted = `${infoPrefix} ${message}`;
627
- if (this.shouldDeferMessage()) {
628
- this.deferMessage(formatted);
629
- } else {
630
- this.printLine(formatted);
631
- }
632
- }
633
-
634
- warning(message: string): void {
635
- const warn = colorize("[WARN]", Color.YELLOW);
636
- const formatted = `${warn} ${message}`;
637
- if (this.shouldDeferMessage()) {
638
- this.deferMessage(formatted);
639
- } else {
640
- this.printLine(formatted);
641
- }
642
- }
643
-
644
- error(message: string): void {
645
- const err = colorize("[ERROR]", Color.BRIGHT_RED);
646
- const formatted = `${err} ${message}`;
647
- if (this.shouldDeferMessage()) {
648
- this.deferMessage(formatted);
649
- } else {
650
- this.printLine(formatted);
651
- }
652
- }
653
-
654
- // --- Streaming ---
655
-
656
- streamText(text: string, role = "assistant", model?: string | null): void {
657
- const formattedModel = this.formatModelName && model ? this.formatModelName(model) : null;
658
-
659
- if (this.level === OutputLevel.ALL) {
660
- if (!text || !text.trim()) return;
661
-
662
- if (this.isParallelMode()) {
663
- this._parallelHandler.accumulateText(text, role, model);
664
- return;
665
- }
666
-
667
- if (role === "user") {
668
- const prefix = colorize(">", Color.BRIGHT_CYAN, Color.BOLD);
669
- const label = colorize(" [user]", Color.DIM);
670
- this.printLine("");
671
- this.printLine(`${prefix}${label}`);
672
- for (const line of text.split("\n")) {
673
- const coloredLine = colorize(line, Color.GREEN);
674
- this.printLine(` ${coloredLine}`);
675
- }
676
- } else {
677
- const bullet = colorize("*", Color.BRIGHT_GREEN, Color.BOLD);
678
- let modelLabel = "";
679
- if (formattedModel) {
680
- modelLabel = ` ${colorize(`[${formattedModel}]`, Color.DIM)}`;
681
- }
682
- const lines = text.split("\n");
683
- this.printLine("");
684
- this.printLine(`${bullet}${modelLabel} ${lines[0]}`);
685
- for (const line of lines.slice(1)) {
686
- this.printLine(` ${line}`);
687
- }
688
- }
689
- } else if (this.level === OutputLevel.BASE) {
690
- if (role === "user") return;
691
-
692
- if (this.isParallelMode()) {
693
- const branch = this._parallelHandler.getCurrentBranch();
694
- if (branch && this._parallelHandler.hasBranches) {
695
- const lastLine = this._parallelHandler.accumulateBaseText(text);
696
- if (lastLine) {
697
- this._parallelHandler.updateBranchLine(branch, lastLine);
698
- }
699
- }
700
- return;
701
- }
702
-
703
- this._baseAccumulatedText += text;
704
- if (this._baseAccumulatedText.length > MAX_ACCUMULATED_TEXT) {
705
- this._baseAccumulatedText = this._baseAccumulatedText.slice(-MAX_ACCUMULATED_TEXT);
706
- }
707
-
708
- if (this._baseAccumulatedText.trim()) {
709
- const prefix = colorize("...", Color.DIM);
710
- const lines = this._baseAccumulatedText.trim().split("\n");
711
- const formattedLines = [`${prefix} ${lines[0]}`];
712
- for (const line of lines.slice(1)) {
713
- formattedLines.push(` ${line}`);
714
- }
715
- const displayText = formattedLines.join("\n");
716
- this.printInplace(displayText);
717
- }
718
- }
719
- }
720
-
721
- streamComplete(): void {
722
- if (this.level === OutputLevel.ALL && this.isParallelMode()) {
723
- this._parallelHandler.enqueueCurrentMessage();
724
- }
725
- this._baseAccumulatedText = "";
726
- }
727
- }
728
-
729
- // --- Standalone utility functions ---
730
-
731
- export function extractJson(output: string): Record<string, unknown> | null {
732
- if (!output || !output.trim()) return null;
733
-
734
- const pattern = /```json\s*([\s\S]*?)```/g;
735
- const matches = [...output.matchAll(pattern)];
736
-
737
- if (matches.length === 0) return null;
738
-
739
- const jsonStr = matches[matches.length - 1][1].trim();
740
-
741
- try {
742
- return JSON.parse(jsonStr) as Record<string, unknown>;
743
- } catch {
744
- return null;
745
- }
746
- }
747
-
748
- export function extractSummary(output: string, maxLines = 5, maxChars = 500): string {
749
- if (!output || !output.trim()) return "";
750
-
751
- const trimmed = output.trim();
752
-
753
- const summaryMarkers = [
754
- "## Summary",
755
- "### Summary",
756
- "Summary:",
757
- "Result:",
758
- "Completed:",
759
- "Done:",
760
- ];
761
-
762
- for (const marker of summaryMarkers) {
763
- if (trimmed.includes(marker)) {
764
- const idx = trimmed.indexOf(marker);
765
- const summarySection = trimmed.slice(idx);
766
- const lines = summarySection.split("\n");
767
- const resultLines: string[] = [];
768
- for (let i = 1; i < lines.length; i++) {
769
- if (i > maxLines) break;
770
- if (lines[i].startsWith("#") && i > 1) break;
771
- resultLines.push(lines[i]);
772
- }
773
- if (resultLines.length > 0) {
774
- return resultLines.join("\n").trim().slice(0, maxChars);
775
- }
776
- }
777
- }
778
-
779
- const nonEmptyLines = trimmed.split("\n").filter((line) => line.trim());
780
- if (nonEmptyLines.length === 0) return "";
781
-
782
- const lastLines = nonEmptyLines.slice(-maxLines);
783
- let summary = lastLines.join("\n");
784
-
785
- if (summary.length > maxChars) {
786
- summary = `${summary.slice(0, maxChars)}...`;
787
- }
788
-
789
- return summary;
790
- }