Skip to content

Commit adca721

Browse files
josealekhineclaude
andcommitted
feat(cli): add ctx loop command for Ralph loop script generation
Implement `ctx loop` command that generates ready-to-use shell scripts for running Ralph loops with different AI tools: - --tool: claude (default), aider, or generic - --prompt: prompt file (default: PROMPT.md) - --max-iterations: limit iterations (default: unlimited) - --completion: completion signal (default: SYSTEM_CONVERGED) - --output: output filename (default: loop.sh) Also adds /ctx-loop skill for Claude Code integration. Example: ctx loop --tool claude --max-iterations 50 --prompt PROMPT.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 25ad8aa commit adca721

4 files changed

Lines changed: 189 additions & 8 deletions

File tree

.context/TASKS.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,17 @@
7070
- [x] Create `/ctx-add-task` skill — calls `ctx add task`
7171
- [x] Create `/ctx-agent` skill — calls `ctx agent` (manual context load)
7272
- [x] Create `/ctx-archive` skill — calls `ctx tasks archive`
73-
- [ ] Create `/ctx-loop` skill — calls `ctx loop` (generate Ralph loop script)
73+
- [x] Create `/ctx-loop` skill — calls `ctx loop` (generate Ralph loop script)
7474
- [x] Update `ctx init` to create skill definitions in `.claude/commands/`
7575

7676
### Phase 9b: Ralph Loop Integration `#priority:medium` `#area:cli`
77-
- [ ] Implement `ctx loop` command — generate a ready-to-use loop.sh script
78-
- [ ] Detect AI tool in use (claude, aider, etc.) and generate appropriate invocation
79-
- [ ] Include configurable max iterations, prompt file path
80-
- [ ] Include completion signal detection (SYSTEM_CONVERGED, SYSTEM_BLOCKED)
81-
- [ ] Make script executable by default
82-
- [ ] Add `ctx loop --prompt PROMPT.md` — specify custom prompt file
83-
- [ ] Add `ctx loop --tool claude|aider|generic` — target specific AI CLI
77+
- [x] Implement `ctx loop` command — generate a ready-to-use loop.sh script
78+
- [x] Detect AI tool in use (claude, aider, etc.) and generate appropriate invocation
79+
- [x] Include configurable max iterations, prompt file path
80+
- [x] Include completion signal detection (SYSTEM_CONVERGED, SYSTEM_BLOCKED)
81+
- [x] Make script executable by default
82+
- [x] Add `ctx loop --prompt PROMPT.md` — specify custom prompt file
83+
- [x] Add `ctx loop --tool claude|aider|generic` — target specific AI CLI
8484
- [ ] Document in README that `/ralph-loop` exists for Claude Code users
8585

8686
### Phase 10: Project Rename `#priority:medium` `#area:branding`

cmd/ctx/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func init() {
4343
rootCmd.AddCommand(cli.HookCmd())
4444
rootCmd.AddCommand(cli.SessionCmd())
4545
rootCmd.AddCommand(cli.TasksCmd())
46+
rootCmd.AddCommand(cli.LoopCmd())
4647
}
4748

4849
func main() {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
description: "Generate a Ralph loop script"
3+
argument-hint: "[--tool claude|aider] [--prompt FILE] [--max-iterations N]"
4+
---
5+
6+
Generate a ready-to-use Ralph loop shell script.
7+
8+
```!
9+
ctx loop $ARGUMENTS
10+
```
11+
12+
Report the generated script path and how to run it.

internal/cli/loop.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// / Context: https://ctx.ist
2+
// ,'`./ do you remember?
3+
// `.,'\
4+
// \ Copyright 2025-present Context contributors.
5+
// SPDX-License-Identifier: Apache-2.0
6+
7+
package cli
8+
9+
import (
10+
"fmt"
11+
"os"
12+
"path/filepath"
13+
14+
"github.com/fatih/color"
15+
"github.com/spf13/cobra"
16+
)
17+
18+
var (
19+
loopPromptFile string
20+
loopTool string
21+
loopMaxIterations int
22+
loopCompletionMsg string
23+
loopOutputFile string
24+
)
25+
26+
// LoopCmd returns the loop command.
27+
func LoopCmd() *cobra.Command {
28+
cmd := &cobra.Command{
29+
Use: "loop",
30+
Short: "Generate a Ralph loop script",
31+
Long: `Generate a ready-to-use shell script for running a Ralph loop.
32+
33+
A Ralph loop continuously runs an AI assistant with the same prompt until
34+
a completion signal is detected. This enables iterative development where
35+
the AI can build on its previous work.
36+
37+
Examples:
38+
ctx loop # Generate loop.sh for Claude
39+
ctx loop --tool aider # Generate for Aider
40+
ctx loop --prompt TASKS.md # Use custom prompt file
41+
ctx loop --max-iterations 10 # Limit to 10 iterations
42+
ctx loop -o my-loop.sh # Output to custom file`,
43+
RunE: runLoop,
44+
}
45+
46+
cmd.Flags().StringVarP(&loopPromptFile, "prompt", "p", "PROMPT.md", "Prompt file to use")
47+
cmd.Flags().StringVarP(&loopTool, "tool", "t", "claude", "AI tool: claude, aider, or generic")
48+
cmd.Flags().IntVarP(&loopMaxIterations, "max-iterations", "n", 0, "Maximum iterations (0 = unlimited)")
49+
cmd.Flags().StringVarP(&loopCompletionMsg, "completion", "c", "SYSTEM_CONVERGED", "Completion signal to detect")
50+
cmd.Flags().StringVarP(&loopOutputFile, "output", "o", "loop.sh", "Output script filename")
51+
52+
return cmd
53+
}
54+
55+
func runLoop(cmd *cobra.Command, args []string) error {
56+
green := color.New(color.FgGreen).SprintFunc()
57+
58+
// Validate tool
59+
validTools := map[string]bool{"claude": true, "aider": true, "generic": true}
60+
if !validTools[loopTool] {
61+
return fmt.Errorf("invalid tool %q: must be claude, aider, or generic", loopTool)
62+
}
63+
64+
// Generate the script
65+
script := generateLoopScript(loopPromptFile, loopTool, loopMaxIterations, loopCompletionMsg)
66+
67+
// Write to file
68+
if err := os.WriteFile(loopOutputFile, []byte(script), 0755); err != nil {
69+
return fmt.Errorf("failed to write %s: %w", loopOutputFile, err)
70+
}
71+
72+
fmt.Printf("%s Generated %s\n", green("✓"), loopOutputFile)
73+
fmt.Println()
74+
fmt.Println("To start the loop:")
75+
fmt.Printf(" ./%s\n", loopOutputFile)
76+
fmt.Println()
77+
fmt.Printf("Tool: %s\n", loopTool)
78+
fmt.Printf("Prompt: %s\n", loopPromptFile)
79+
if loopMaxIterations > 0 {
80+
fmt.Printf("Max iterations: %d\n", loopMaxIterations)
81+
} else {
82+
fmt.Println("Max iterations: unlimited")
83+
}
84+
fmt.Printf("Completion signal: %s\n", loopCompletionMsg)
85+
86+
return nil
87+
}
88+
89+
func generateLoopScript(promptFile, tool string, maxIterations int, completionMsg string) string {
90+
// Get absolute path for prompt file
91+
absPrompt, _ := filepath.Abs(promptFile)
92+
93+
var aiCommand string
94+
switch tool {
95+
case "claude":
96+
aiCommand = fmt.Sprintf(`claude --print "$(cat %s)"`, absPrompt)
97+
case "aider":
98+
aiCommand = fmt.Sprintf(`aider --message-file %s`, absPrompt)
99+
case "generic":
100+
aiCommand = fmt.Sprintf(`# Replace with your AI CLI command
101+
cat %s | your-ai-cli`, absPrompt)
102+
}
103+
104+
maxIterCheck := ""
105+
if maxIterations > 0 {
106+
maxIterCheck = fmt.Sprintf(`
107+
# Check iteration limit
108+
if [ $ITERATION -ge %d ]; then
109+
echo "Reached maximum iterations (%d)"
110+
break
111+
fi`, maxIterations, maxIterations)
112+
}
113+
114+
script := fmt.Sprintf(`#!/bin/bash
115+
#
116+
# Ralph Loop Script
117+
# Generated by: ctx loop
118+
#
119+
# This script runs an AI assistant in a loop until completion.
120+
# The AI works on the same prompt file repeatedly, building on
121+
# previous work visible in files and git history.
122+
#
123+
124+
set -e
125+
126+
PROMPT_FILE="%s"
127+
COMPLETION_SIGNAL="%s"
128+
ITERATION=0
129+
130+
echo "Starting Ralph Loop"
131+
echo "==================="
132+
echo "Prompt: $PROMPT_FILE"
133+
echo "Completion signal: $COMPLETION_SIGNAL"
134+
echo ""
135+
136+
# Ensure prompt file exists
137+
if [ ! -f "$PROMPT_FILE" ]; then
138+
echo "Error: Prompt file not found: $PROMPT_FILE"
139+
exit 1
140+
fi
141+
142+
while true; do
143+
ITERATION=$((ITERATION + 1))
144+
echo ""
145+
echo "=== Iteration $ITERATION ==="
146+
echo ""
147+
%s
148+
# Run the AI tool
149+
OUTPUT=$(%s 2>&1) || true
150+
151+
echo "$OUTPUT"
152+
153+
# Check for completion signal
154+
if echo "$OUTPUT" | grep -q "$COMPLETION_SIGNAL"; then
155+
echo ""
156+
echo "=== Loop Complete ==="
157+
echo "Detected completion signal: $COMPLETION_SIGNAL"
158+
echo "Total iterations: $ITERATION"
159+
break
160+
fi
161+
162+
# Small delay to prevent runaway loops
163+
sleep 1
164+
done
165+
`, absPrompt, completionMsg, maxIterCheck, aiCommand)
166+
167+
return script
168+
}

0 commit comments

Comments
 (0)