@towles/tool 0.0.53 → 0.0.55

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 (43) hide show
  1. package/README.md +82 -72
  2. package/package.json +8 -7
  3. package/src/commands/auto-claude.ts +219 -0
  4. package/src/commands/doctor.ts +1 -34
  5. package/src/config/settings.ts +0 -10
  6. package/src/lib/auto-claude/config.test.ts +53 -0
  7. package/src/lib/auto-claude/config.ts +68 -0
  8. package/src/lib/auto-claude/index.ts +14 -0
  9. package/src/lib/auto-claude/pipeline.test.ts +14 -0
  10. package/src/lib/auto-claude/pipeline.ts +64 -0
  11. package/src/lib/auto-claude/prompt-templates/01-prompt-research.md +28 -0
  12. package/src/lib/auto-claude/prompt-templates/02-prompt-plan.md +28 -0
  13. package/src/lib/auto-claude/prompt-templates/03-prompt-plan-annotations.md +21 -0
  14. package/src/lib/auto-claude/prompt-templates/04-prompt-plan-implementation.md +33 -0
  15. package/src/lib/auto-claude/prompt-templates/05-prompt-implement.md +31 -0
  16. package/src/lib/auto-claude/prompt-templates/06-prompt-review.md +30 -0
  17. package/src/lib/auto-claude/prompt-templates/07-prompt-refresh.md +39 -0
  18. package/src/lib/auto-claude/prompt-templates/index.test.ts +145 -0
  19. package/src/lib/auto-claude/prompt-templates/index.ts +44 -0
  20. package/src/lib/auto-claude/steps/create-pr.ts +93 -0
  21. package/src/lib/auto-claude/steps/fetch-issues.ts +64 -0
  22. package/src/lib/auto-claude/steps/implement.ts +63 -0
  23. package/src/lib/auto-claude/steps/plan-annotations.ts +54 -0
  24. package/src/lib/auto-claude/steps/plan-implementation.ts +14 -0
  25. package/src/lib/auto-claude/steps/plan.ts +14 -0
  26. package/src/lib/auto-claude/steps/refresh.ts +114 -0
  27. package/src/lib/auto-claude/steps/remove-label.ts +22 -0
  28. package/src/lib/auto-claude/steps/research.ts +21 -0
  29. package/src/lib/auto-claude/steps/review.ts +14 -0
  30. package/src/lib/auto-claude/utils.test.ts +136 -0
  31. package/src/lib/auto-claude/utils.ts +334 -0
  32. package/src/commands/ralph/plan/add.ts +0 -69
  33. package/src/commands/ralph/plan/done.ts +0 -82
  34. package/src/commands/ralph/plan/list.test.ts +0 -48
  35. package/src/commands/ralph/plan/list.ts +0 -100
  36. package/src/commands/ralph/plan/remove.ts +0 -71
  37. package/src/commands/ralph/run.test.ts +0 -607
  38. package/src/commands/ralph/run.ts +0 -362
  39. package/src/commands/ralph/show.ts +0 -88
  40. package/src/lib/ralph/execution.ts +0 -292
  41. package/src/lib/ralph/formatter.ts +0 -240
  42. package/src/lib/ralph/index.ts +0 -4
  43. package/src/lib/ralph/state.ts +0 -201
@@ -1,362 +0,0 @@
1
- import * as fs from "node:fs";
2
- import { Flags } from "@oclif/core";
3
- import consola from "consola";
4
- import { colors } from "consola/utils";
5
- import { BaseCommand } from "../base.js";
6
- import {
7
- DEFAULT_STATE_FILE,
8
- DEFAULT_LOG_FILE,
9
- DEFAULT_MAX_ITERATIONS,
10
- DEFAULT_COMPLETION_MARKER,
11
- DEFAULT_TASK_DONE_MARKER,
12
- CLAUDE_DEFAULT_ARGS,
13
- loadState,
14
- saveState,
15
- appendHistory,
16
- resolveRalphPath,
17
- getRalphPaths,
18
- } from "../../lib/ralph/state.js";
19
- import {
20
- buildIterationPrompt,
21
- formatDuration,
22
- extractOutputSummary,
23
- detectCompletionMarker,
24
- } from "../../lib/ralph/formatter.js";
25
- import { checkClaudeCli, runIteration } from "../../lib/ralph/execution.js";
26
- import type { RalphPlan } from "../../lib/ralph/state.js";
27
-
28
- /** Get the plan to work on: focused plan or first incomplete */
29
- function getCurrentPlan(plans: RalphPlan[], focusedPlanId: number | null): RalphPlan | undefined {
30
- if (focusedPlanId !== null) {
31
- return plans.find((p) => p.id === focusedPlanId);
32
- }
33
- return plans.find((p) => p.status !== "done");
34
- }
35
-
36
- /**
37
- * Run the autonomous ralph loop
38
- */
39
- export default class Run extends BaseCommand {
40
- static override description = "Start the autonomous ralph loop";
41
-
42
- static override examples = [
43
- { description: "Start the autonomous loop", command: "<%= config.bin %> <%= command.id %>" },
44
- {
45
- description: "Limit to 20 iterations",
46
- command: "<%= config.bin %> <%= command.id %> --maxIterations 20",
47
- },
48
- {
49
- description: "Focus on specific plan",
50
- command: "<%= config.bin %> <%= command.id %> --planId 5",
51
- },
52
- {
53
- description: "Run without auto-committing",
54
- command: "<%= config.bin %> <%= command.id %> --no-autoCommit",
55
- },
56
- {
57
- description: "Preview config without executing",
58
- command: "<%= config.bin %> <%= command.id %> --dryRun",
59
- },
60
- ];
61
-
62
- static override flags = {
63
- ...BaseCommand.baseFlags,
64
- stateFile: Flags.string({
65
- char: "s",
66
- description: `State file path (default: ${DEFAULT_STATE_FILE})`,
67
- }),
68
- planId: Flags.integer({
69
- char: "p",
70
- description: "Focus on specific plan ID",
71
- }),
72
- maxIterations: Flags.integer({
73
- char: "m",
74
- description: "Max iterations",
75
- default: DEFAULT_MAX_ITERATIONS,
76
- }),
77
- autoCommit: Flags.boolean({
78
- description: "Auto-commit after each completed plan",
79
- default: true,
80
- allowNo: true,
81
- }),
82
- dryRun: Flags.boolean({
83
- char: "n",
84
- description: "Show config without executing",
85
- default: false,
86
- }),
87
- claudeArgs: Flags.string({
88
- description: "Extra args to pass to claude CLI (space-separated)",
89
- }),
90
- logFile: Flags.string({
91
- description: `Log file path (default: ${DEFAULT_LOG_FILE})`,
92
- }),
93
- completionMarker: Flags.string({
94
- description: "Completion marker",
95
- default: DEFAULT_COMPLETION_MARKER,
96
- }),
97
- taskDoneMarker: Flags.string({
98
- description: "Task done marker",
99
- default: DEFAULT_TASK_DONE_MARKER,
100
- }),
101
- };
102
-
103
- async run(): Promise<void> {
104
- const { flags } = await this.parse(Run);
105
- const ralphSettings = this.settings.settings.ralphSettings;
106
- const stateFile = resolveRalphPath(flags.stateFile, "stateFile", ralphSettings);
107
- const logFile = resolveRalphPath(flags.logFile, "logFile", ralphSettings);
108
- const ralphPaths = getRalphPaths(ralphSettings);
109
-
110
- const maxIterations = flags.maxIterations;
111
- const extraClaudeArgs = flags.claudeArgs?.split(" ").filter(Boolean) || [];
112
- const focusedPlanId = flags.planId ?? null;
113
-
114
- // Load existing state
115
- let state = loadState(stateFile);
116
-
117
- if (!state) {
118
- this.error(`No state file found at: ${stateFile}\nUse: tt ralph plan add --file path.md`);
119
- }
120
-
121
- const remainingPlans = state.plans.filter((t) => t.status !== "done");
122
- if (remainingPlans.length === 0) {
123
- consola.log(colors.green("✅ All plans are done!"));
124
- return;
125
- }
126
-
127
- // Validate focused plan if specified
128
- if (focusedPlanId !== null) {
129
- const focusedPlan = state.plans.find((t) => t.id === focusedPlanId);
130
- if (!focusedPlan) {
131
- this.error(`Plan #${focusedPlanId} not found. Use: tt ralph plan list`);
132
- }
133
- if (focusedPlan.status === "done") {
134
- consola.log(colors.yellow(`Plan #${focusedPlanId} is already done.`));
135
- return;
136
- }
137
- }
138
-
139
- // Get current plan to work on
140
- const currentPlan = getCurrentPlan(state.plans, focusedPlanId);
141
- if (!currentPlan) {
142
- consola.log(colors.green("✅ All plans are done!"));
143
- return;
144
- }
145
-
146
- // Dry run mode
147
- if (flags.dryRun) {
148
- consola.log(colors.bold("\n=== DRY RUN ===\n"));
149
- consola.log(colors.cyan("Config:"));
150
- consola.log(` Max iterations: ${maxIterations}`);
151
- consola.log(` State file: ${stateFile}`);
152
- consola.log(` Log file: ${logFile}`);
153
- consola.log(` Completion marker: ${flags.completionMarker}`);
154
- consola.log(` Task done marker: ${flags.taskDoneMarker}`);
155
- consola.log(` Auto-commit: ${flags.autoCommit}`);
156
- consola.log(` Claude args: ${[...CLAUDE_DEFAULT_ARGS, ...extraClaudeArgs].join(" ")}`);
157
- consola.log(` Remaining plans: ${remainingPlans.length}`);
158
-
159
- consola.log(colors.cyan("\nCurrent plan:"));
160
- consola.log(` #${currentPlan.id}: ${currentPlan.planFilePath}`);
161
-
162
- // Show prompt preview
163
- const prompt = buildIterationPrompt({
164
- completionMarker: flags.completionMarker,
165
- taskDoneMarker: flags.taskDoneMarker,
166
- plan: currentPlan,
167
- skipCommit: !flags.autoCommit,
168
- });
169
- consola.log(colors.dim("─".repeat(60)));
170
- consola.log(colors.bold("Prompt Preview"));
171
- consola.log(colors.dim("─".repeat(60)));
172
- consola.log(prompt);
173
- consola.log(colors.dim("─".repeat(60)));
174
-
175
- consola.log(colors.bold("\n=== END DRY RUN ===\n"));
176
- return;
177
- }
178
-
179
- // Check claude CLI is available
180
- if (!(await checkClaudeCli())) {
181
- this.error(
182
- "claude CLI not found in PATH\nInstall Claude Code: https://docs.anthropic.com/en/docs/claude-code",
183
- );
184
- }
185
-
186
- // Update state for this run
187
- state.status = "running";
188
-
189
- // Create log stream (append mode)
190
- const logStream = fs.createWriteStream(logFile, { flags: "a" });
191
-
192
- const ready = state.plans.filter((t) => t.status === "ready").length;
193
- const done = state.plans.filter((t) => t.status === "done").length;
194
-
195
- logStream.write(`\n${"=".repeat(60)}\n`);
196
- logStream.write(`Ralph Loop Started: ${new Date().toISOString()}\n`);
197
- logStream.write(`${"=".repeat(60)}\n\n`);
198
-
199
- consola.log(colors.bold(colors.blue("\nRalph Loop Starting\n")));
200
- consola.log(colors.dim(`Focus: ${focusedPlanId ? `Plan #${focusedPlanId}` : "Ralph picks"}`));
201
- consola.log(colors.dim(`Max iterations: ${maxIterations}`));
202
- consola.log(colors.dim(`Log file: ${logFile}`));
203
- consola.log(colors.dim(`Auto-commit: ${flags.autoCommit}`));
204
- consola.log(colors.dim(`Tasks: ${state.plans.length} (${done} done, ${ready} ready)`));
205
- consola.log("");
206
-
207
- logStream.write(`Focus: ${focusedPlanId ? `Plan #${focusedPlanId}` : "Ralph picks"}\n`);
208
- logStream.write(`Max iterations: ${maxIterations}\n`);
209
- logStream.write(`Tasks: ${state.plans.length} (${done} done, ${ready} ready)\n\n`);
210
-
211
- // Handle SIGINT gracefully
212
- let interrupted = false;
213
- process.on("SIGINT", () => {
214
- if (interrupted) {
215
- logStream.end();
216
- process.exit(130);
217
- }
218
- interrupted = true;
219
- const msg = "\n\nInterrupted. Press Ctrl+C again to force exit.\n";
220
- consola.log(colors.yellow(msg));
221
- logStream.write(msg);
222
- state.status = "error";
223
- saveState(state, stateFile);
224
- });
225
-
226
- // Main loop
227
- let completed = false;
228
- let iteration = 0;
229
-
230
- while (iteration < maxIterations && !interrupted && !completed) {
231
- iteration++;
232
-
233
- const iterHeader = `Iteration ${iteration}/${maxIterations}`;
234
- logStream.write(`\n━━━ ${iterHeader} ━━━\n`);
235
-
236
- const iterationStart = new Date().toISOString();
237
- // Get current plan for this iteration
238
- const plan = getCurrentPlan(state.plans, focusedPlanId);
239
- if (!plan) {
240
- completed = true;
241
- state.status = "completed";
242
- saveState(state, stateFile);
243
- consola.log(
244
- colors.bold(colors.green(`\n✅ All plans completed after ${iteration} iteration(s)`)),
245
- );
246
- logStream.write(`\n✅ All plans completed after ${iteration} iteration(s)\n`);
247
- break;
248
- }
249
-
250
- const prompt = buildIterationPrompt({
251
- completionMarker: flags.completionMarker,
252
- taskDoneMarker: flags.taskDoneMarker,
253
- plan: plan,
254
- skipCommit: !flags.autoCommit,
255
- });
256
-
257
- // Log the prompt
258
- logStream.write(`\n--- Prompt ---\n${prompt}\n--- End Prompt ---\n\n`);
259
-
260
- // Build claude args
261
- const iterClaudeArgs = [...extraClaudeArgs];
262
-
263
- // Print iteration header
264
- consola.log("");
265
- consola.log(colors.bold(colors.blue(`━━━ ${iterHeader} ━━━`)));
266
- consola.log(colors.dim("─".repeat(60)));
267
- consola.log(colors.bold("Prompt"));
268
- consola.log(colors.dim("─".repeat(60)));
269
- consola.log(prompt);
270
- consola.log(colors.dim("─".repeat(60)));
271
-
272
- // Run iteration - output goes directly to stdout
273
- const iterResult = await runIteration(prompt, iterClaudeArgs, logStream);
274
-
275
- // Reload state from disk to pick up changes made by child claude process
276
- const freshState = loadState(stateFile);
277
- if (freshState) {
278
- Object.assign(state, freshState);
279
- }
280
-
281
- const iterationEnd = new Date().toISOString();
282
- const taskMarkerFound = detectCompletionMarker(iterResult.output, flags.taskDoneMarker);
283
- const planMarkerFound = detectCompletionMarker(iterResult.output, flags.completionMarker);
284
-
285
- // Calculate duration
286
- const startTime = new Date(iterationStart).getTime();
287
- const endTime = new Date(iterationEnd).getTime();
288
- const durationMs = endTime - startTime;
289
- const durationHuman = formatDuration(durationMs);
290
-
291
- // Record history
292
- appendHistory(
293
- {
294
- iteration,
295
- startedAt: iterationStart,
296
- completedAt: iterationEnd,
297
- durationMs,
298
- durationHuman,
299
- outputSummary: extractOutputSummary(iterResult.output),
300
- markerFound: planMarkerFound,
301
- taskMarkerFound,
302
- contextUsedPercent: iterResult.contextUsedPercent,
303
- },
304
- ralphPaths.historyFile,
305
- );
306
-
307
- // Save state
308
- saveState(state, stateFile);
309
-
310
- // Log marker status
311
- if (taskMarkerFound) {
312
- consola.log(colors.cyan(`Task marker found - current plan done, checking for more plans`));
313
- logStream.write(`Task marker found - continuing to next plan\n`);
314
- }
315
-
316
- // Log summary
317
- const contextInfo =
318
- iterResult.contextUsedPercent !== undefined
319
- ? ` | Context: ${iterResult.contextUsedPercent}%`
320
- : "";
321
- logStream.write(
322
- `\n━━━ Iteration ${iteration} Summary ━━━\nDuration: ${durationHuman}${contextInfo}\nTask marker: ${taskMarkerFound ? "yes" : "no"}\nPlan marker: ${planMarkerFound ? "yes" : "no"}\n`,
323
- );
324
- consola.log(
325
- colors.dim(
326
- `Duration: ${durationHuman}${contextInfo} | Task: ${taskMarkerFound ? colors.green("yes") : colors.yellow("no")} | Plan: ${planMarkerFound ? colors.green("yes") : colors.yellow("no")}`,
327
- ),
328
- );
329
-
330
- // Check completion (only when ALL plans done marker found)
331
- if (planMarkerFound) {
332
- completed = true;
333
- state.status = "completed";
334
- saveState(state, stateFile);
335
- consola.log(
336
- colors.bold(colors.green(`\n✅ All plans completed after ${iteration} iteration(s)`)),
337
- );
338
- logStream.write(`\n✅ All plans completed after ${iteration} iteration(s)\n`);
339
- }
340
- }
341
-
342
- logStream.end();
343
-
344
- // Final status
345
- if (completed) {
346
- return;
347
- }
348
-
349
- if (!interrupted && iteration >= maxIterations) {
350
- state.status = "max_iterations_reached";
351
- saveState(state, stateFile);
352
- consola.log(
353
- colors.bold(
354
- colors.yellow(`\n⚠️ Max iterations (${maxIterations}) reached without completion`),
355
- ),
356
- );
357
- consola.log(colors.dim(`State saved to: ${stateFile}`));
358
- logStream.write(`\n⚠️ Max iterations (${maxIterations}) reached without completion\n`);
359
- this.exit(1);
360
- }
361
- }
362
- }
@@ -1,88 +0,0 @@
1
- import { Flags } from "@oclif/core";
2
- import consola from "consola";
3
- import { colors } from "consola/utils";
4
- import { BaseCommand } from "../base.js";
5
- import { DEFAULT_STATE_FILE, loadState, resolveRalphPath } from "../../lib/ralph/state.js";
6
- import {
7
- formatPlanAsMarkdown,
8
- formatPlanAsJson,
9
- copyToClipboard,
10
- } from "../../lib/ralph/formatter.js";
11
-
12
- /**
13
- * Show plan summary with status, tasks, and mermaid graph
14
- */
15
- export default class Show extends BaseCommand {
16
- static override description = "Show plan summary with status, tasks, and mermaid graph";
17
-
18
- static override examples = [
19
- {
20
- description: "Show plan summary with mermaid graph",
21
- command: "<%= config.bin %> <%= command.id %>",
22
- },
23
- {
24
- description: "Output plan as JSON",
25
- command: "<%= config.bin %> <%= command.id %> --format json",
26
- },
27
- {
28
- description: "Copy plan output to clipboard",
29
- command: "<%= config.bin %> <%= command.id %> --copy",
30
- },
31
- ];
32
-
33
- static override flags = {
34
- ...BaseCommand.baseFlags,
35
- stateFile: Flags.string({
36
- char: "s",
37
- description: `State file path (default: ${DEFAULT_STATE_FILE})`,
38
- }),
39
- format: Flags.string({
40
- char: "f",
41
- description: "Output format",
42
- default: "default",
43
- options: ["default", "markdown", "json"],
44
- }),
45
- copy: Flags.boolean({
46
- char: "c",
47
- description: "Copy output to clipboard",
48
- default: false,
49
- }),
50
- };
51
-
52
- async run(): Promise<void> {
53
- const { flags } = await this.parse(Show);
54
- const ralphSettings = this.settings.settings.ralphSettings;
55
- const stateFile = resolveRalphPath(flags.stateFile, "stateFile", ralphSettings);
56
-
57
- const state = loadState(stateFile);
58
-
59
- if (!state) {
60
- consola.log(colors.yellow(`No state file found at: ${stateFile}`));
61
- return;
62
- }
63
-
64
- if (state.plans.length === 0) {
65
- consola.log(colors.yellow("No tasks in state file."));
66
- consola.log(colors.dim('Use: tt ralph plan add "description"'));
67
- return;
68
- }
69
-
70
- let output: string;
71
-
72
- if (flags.format === "json") {
73
- output = formatPlanAsJson(state.plans, state);
74
- } else {
75
- output = formatPlanAsMarkdown(state.plans, state);
76
- }
77
-
78
- consola.log(output);
79
-
80
- if (flags.copy) {
81
- if (copyToClipboard(output)) {
82
- consola.log(colors.green("✓ Copied to clipboard"));
83
- } else {
84
- consola.log(colors.yellow("⚠ Could not copy to clipboard (xclip/xsel not installed?)"));
85
- }
86
- }
87
- }
88
- }