codex-ralph 0.1.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.
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # Codex Ralph Loop
2
+
3
+ Minimal Ralph Loop runner that feeds a sprint requirement into Codex, one item per iteration.
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ npx ralph-loop --sprint=path/to/Sprint_0001.md --max-iterations=1
9
+ ```
10
+
11
+ ## Sprint format (Markdown)
12
+
13
+ Each requirement is a level-2 heading with a checkbox. The loop picks the first unchecked item.
14
+
15
+ ```markdown
16
+ ## [ ] Requirement description
17
+ Description: Free-form task details.
18
+ ```
19
+
20
+ ## Behavior
21
+
22
+ - Reads the sprint file, finds the first unchecked item, and passes it to Codex.
23
+ - Derives a parallel notes file alongside the sprint (for example `Sprint_0001.md` -> `SprintNotes_0001.md`) and includes its path in the prompt.
24
+ - Marks items complete only when all steps are satisfied.
25
+ - Uses conventional commits for each completed requirement.
@@ -0,0 +1,18 @@
1
+ # Ralph Loop (Codex) Sprint Executor
2
+
3
+ You are running inside a loop. Each iteration must do **exactly one** sprint requirement from the sprint file provided below, in order.
4
+
5
+ Process:
6
+ 1. Open the sprint file path provided below.
7
+ 2. Find the first item that is unchecked.
8
+ 3. Implement that requirement fully.
9
+ 4. Update the sprint item to checked only when all its steps are satisfied.
10
+ 5. Commit your changes with a **conventional commit** message.
11
+
12
+ If you cannot complete the requirement, leave it unchecked and record the blocker in the Sprint notes file.
13
+
14
+ After each iteration, update the Sprint notes file with what you did, including details or nuances that will help the next iteration.
15
+
16
+ When **all** items are checked, output:
17
+
18
+ <promise>DONE</promise>
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ SCRIPT_PATH="$(python3 - <<PY
6
+ import os
7
+ print(os.path.realpath("${BASH_SOURCE[0]}"))
8
+ PY
9
+ )"
10
+ ROOT_DIR="$(cd "$(dirname "$SCRIPT_PATH")/.." && pwd)"
11
+ SPRINT_FILE=""
12
+ MAX_ITERATIONS=0
13
+ NOTES_FILE=""
14
+
15
+ usage() {
16
+ cat <<USAGE
17
+ Usage: ralph-loop --sprint=PATH [--max-iterations=N]
18
+
19
+ Options:
20
+ --sprint=PATH Path to the sprint markdown file (required).
21
+ --max-iterations=N Stop after N iterations (0 = no limit).
22
+ -h, --help Show this help.
23
+ USAGE
24
+ }
25
+
26
+ for arg in "$@"; do
27
+ case "$arg" in
28
+ --sprint=*)
29
+ SPRINT_FILE="${arg#*=}"
30
+ ;;
31
+ --max-iterations=*)
32
+ MAX_ITERATIONS="${arg#*=}"
33
+ ;;
34
+ -h|--help)
35
+ usage
36
+ exit 0
37
+ ;;
38
+ *)
39
+ echo "Unknown argument: $arg" >&2
40
+ usage >&2
41
+ exit 1
42
+ ;;
43
+ esac
44
+ done
45
+
46
+ if [[ -z "$SPRINT_FILE" ]]; then
47
+ echo "Sprint file path is required." >&2
48
+ usage >&2
49
+ exit 1
50
+ fi
51
+
52
+ if [[ ! -f "$SPRINT_FILE" ]]; then
53
+ echo "Sprint file not found: $SPRINT_FILE" >&2
54
+ exit 1
55
+ fi
56
+
57
+ if [[ -z "$NOTES_FILE" ]]; then
58
+ sprint_dir="$(cd "$(dirname "$SPRINT_FILE")" && pwd)"
59
+ sprint_base="$(basename "$SPRINT_FILE")"
60
+ notes_base="${sprint_base/Sprint_/SprintNotes_}"
61
+ if [[ "$notes_base" == "$sprint_base" ]]; then
62
+ notes_base="${sprint_base%.*}Notes.${sprint_base##*.}"
63
+ fi
64
+ NOTES_FILE="$sprint_dir/$notes_base"
65
+ fi
66
+
67
+ if [[ ! -f "$NOTES_FILE" ]]; then
68
+ mkdir -p "$(dirname "$NOTES_FILE")"
69
+ : > "$NOTES_FILE"
70
+ fi
71
+
72
+ PROMPT_FILE="$ROOT_DIR/agent/prompt.md"
73
+ if [[ ! -f "$PROMPT_FILE" ]]; then
74
+ echo "Prompt file not found: $PROMPT_FILE" >&2
75
+ exit 1
76
+ fi
77
+
78
+ remaining_count() {
79
+ python3 - <<PY
80
+ from pathlib import Path
81
+ import re
82
+
83
+ path = Path("$SPRINT_FILE")
84
+ text = path.read_text(encoding="utf-8")
85
+ items = re.findall(r"^## \[( |x|X)\] ", text, flags=re.M)
86
+ print(sum(1 for m in items if m.strip() != "x"))
87
+ PY
88
+ }
89
+
90
+ next_description() {
91
+ python3 - <<PY
92
+ from pathlib import Path
93
+ import re
94
+
95
+ path = Path("$SPRINT_FILE")
96
+ text = path.read_text(encoding="utf-8")
97
+ for match in re.finditer(r"^## \[( |x|X)\] (.+)$", text, flags=re.M):
98
+ checked = match.group(1)
99
+ desc = match.group(2).strip()
100
+ if checked != "x" and checked != "X":
101
+ print(desc)
102
+ break
103
+ PY
104
+ }
105
+
106
+ current_item_block() {
107
+ python3 - <<PY
108
+ from pathlib import Path
109
+ import re
110
+
111
+ path = Path("$SPRINT_FILE")
112
+ lines = path.read_text(encoding="utf-8").splitlines()
113
+ start = None
114
+ end = None
115
+ for i, line in enumerate(lines):
116
+ if re.match(r"^## \[( |x|X)\] ", line):
117
+ if start is None:
118
+ checked = line.split("[", 1)[1].split("]", 1)[0].strip()
119
+ if checked.lower() != "x":
120
+ start = i
121
+ elif start is not None:
122
+ end = i
123
+ break
124
+ if start is None:
125
+ print("<no remaining items>")
126
+ else:
127
+ block = lines[start:end] if end is not None else lines[start:]
128
+ print("\n".join(block))
129
+ PY
130
+ }
131
+
132
+ iteration=1
133
+ while true; do
134
+ remaining="$(remaining_count)"
135
+ if [[ "$remaining" == "0" ]]; then
136
+ echo "All sprint requirements complete."
137
+ echo "<promise>DONE</promise>"
138
+ exit 0
139
+ fi
140
+
141
+ if [[ "$MAX_ITERATIONS" -gt 0 && "$iteration" -gt "$MAX_ITERATIONS" ]]; then
142
+ echo "Reached max iterations ($MAX_ITERATIONS) with $remaining remaining." >&2
143
+ exit 2
144
+ fi
145
+
146
+ desc="$(next_description)"
147
+ echo "Iteration $iteration - next requirement: $desc"
148
+
149
+ prompt_tmp="$(mktemp)"
150
+ {
151
+ cat "$PROMPT_FILE"
152
+ echo ""
153
+ echo "Sprint file: $SPRINT_FILE"
154
+ echo "Sprint notes file: $NOTES_FILE"
155
+ echo ""
156
+ echo "Current requirement (Markdown):"
157
+ current_item_block
158
+ } > "$prompt_tmp"
159
+
160
+ codex exec - < "$prompt_tmp"
161
+
162
+ rm -f "$prompt_tmp"
163
+
164
+ iteration=$((iteration + 1))
165
+ done
package/package.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "codex-ralph",
3
+ "version": "0.1.0",
4
+ "description": "Ralph Loop sprint runner for Codex.",
5
+ "bin": {
6
+ "ralph-loop": "agent/ralph-loop.sh"
7
+ },
8
+ "scripts": {
9
+ "ralph-loop": "agent/ralph-loop.sh"
10
+ },
11
+ "author": "Igor Sachivka <juicy.igor@gmail.com>",
12
+ "license": "MIT"
13
+ }