From f0b5ead181f79be8fb9a64fe629d6a22a1a8e1e3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 20:08:15 +0000 Subject: [PATCH 1/8] Initial plan From c2eb4729b4f1f0cfdefd3f1b231ee8934395a45e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 20:17:35 +0000 Subject: [PATCH 2/8] Add Ralph loop scaffold Co-authored-by: acoyfellow <1666099+acoyfellow@users.noreply.github.com> --- .github/workflows/ralph.yml | 59 ++++++++++++++++++++++++++++++ .opencode/opencode.json | 16 +++++++++ AGENTS.md | 25 +++++++++++++ README.md | 12 ++++++- scripts/ralph/constraints.json | 7 ++++ scripts/ralph/failure.json | 6 ++++ scripts/ralph/guard.sh | 66 ++++++++++++++++++++++++++++++++++ scripts/ralph/prd.json | 32 +++++++++++++++++ scripts/ralph/progress.txt | 2 ++ 9 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ralph.yml create mode 100644 .opencode/opencode.json create mode 100644 AGENTS.md create mode 100644 scripts/ralph/constraints.json create mode 100644 scripts/ralph/failure.json create mode 100755 scripts/ralph/guard.sh create mode 100644 scripts/ralph/prd.json create mode 100644 scripts/ralph/progress.txt diff --git a/.github/workflows/ralph.yml b/.github/workflows/ralph.yml new file mode 100644 index 0000000..70a181d --- /dev/null +++ b/.github/workflows/ralph.yml @@ -0,0 +1,59 @@ +name: Ralph Loop + +on: + workflow_dispatch: + push: + paths: + - "AGENTS.md" + - "scripts/ralph/**" + +jobs: + ralph: + if: ${{ github.event_name == 'workflow_dispatch' || github.actor == 'Copilot' || github.actor == 'copilot-swe-agent[bot]' }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.RALPH_PAT }} + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install jq + run: sudo apt-get update && sudo apt-get install -y jq + + - name: Install OpenCode + run: npm install -g @github/opencode + + - name: Validate state files + run: | + jq empty scripts/ralph/prd.json + jq empty scripts/ralph/constraints.json + jq empty scripts/ralph/failure.json + + - name: Ensure guard is executable + run: chmod +x scripts/ralph/guard.sh + + - name: Run OpenCode iteration + run: opencode run --config .opencode/opencode.json + + - name: Run guard + run: bash scripts/ralph/guard.sh scripts/ralph/constraints.json + + - name: Commit and push changes + env: + GIT_AUTHOR_NAME: ralph-bot + GIT_AUTHOR_EMAIL: ralph@example.com + GIT_COMMITTER_NAME: ralph-bot + GIT_COMMITTER_EMAIL: ralph@example.com + run: | + if [[ -z "$(git status --porcelain)" ]]; then + echo "No changes to commit." + exit 0 + fi + git add . + git commit -m "chore: ralph iteration" + git push diff --git a/.opencode/opencode.json b/.opencode/opencode.json new file mode 100644 index 0000000..c51b4d2 --- /dev/null +++ b/.opencode/opencode.json @@ -0,0 +1,16 @@ +{ + "workdir": ".", + "agents": [ + { + "id": "ralph", + "agentFile": "AGENTS.md", + "state": { + "prd": "scripts/ralph/prd.json", + "progress": "scripts/ralph/progress.txt", + "constraints": "scripts/ralph/constraints.json", + "failure": "scripts/ralph/failure.json" + }, + "maxIterations": 1 + } + ] +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b93d325 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,25 @@ +# Ralph Agents + +- PAUSED: false +- MAX_FAILURE_RETRIES: 3 +- COMMIT_AUTHOR: ralph-bot + +## Loop checklist +1) Load AGENTS.md and scripts/ralph state files. +2) If paused, exit immediately. +3) Pick the first `"status": "todo"` story in prd.json and mark it doing before work. +4) Keep one story per run and make the smallest possible diff. +5) Run scripts/ralph/guard.sh before committing; never bypass it. +6) Update progress.txt with a short learning when a story is marked done. +7) If failures exceed MAX_FAILURE_RETRIES, set PAUSED: true and push state. + +## Working files +- scripts/ralph/prd.json - story queue (todo/doing/done) +- scripts/ralph/progress.txt - chronological learnings +- scripts/ralph/constraints.json - guard limits +- scripts/ralph/failure.json - consecutive failure bookkeeping + +## Footguns +- Do not touch build artifacts (dist, node_modules, .svelte-kit, build). +- Avoid changing more than 25 files or 400 total lines per iteration. +- Guard must pass before any commit or push.*** diff --git a/README.md b/README.md index 29e1293..55a472e 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,16 @@ npx create-remote-app my-app Then follow the setup guide in your new project's README. +## Ralph Loop Automation + +This repo includes a minimal Ralph loop to run one story at a time. + +1. Add a repo-scoped PAT secret named `RALPH_PAT` (contents + workflow). +2. Trigger the **Ralph Loop** workflow manually, or push changes to `AGENTS.md` or `scripts/ralph/**` (only bot pushes auto-run). +3. The loop reads `AGENTS.md` plus `scripts/ralph/{prd.json,progress.txt,constraints.json,failure.json}`, runs `scripts/ralph/guard.sh`, and commits if the guard passes. + +State files live under `scripts/ralph/` and `.opencode/opencode.json` wires them into OpenCode. + ## What This Repo Shows This project demonstrates: @@ -213,4 +223,4 @@ This is a pragmatic starting point for projects needing authenticated persistent ## Requirements - Node.js + Bun -- Cloudflare account (for deployment) \ No newline at end of file +- Cloudflare account (for deployment) diff --git a/scripts/ralph/constraints.json b/scripts/ralph/constraints.json new file mode 100644 index 0000000..9a1dfcd --- /dev/null +++ b/scripts/ralph/constraints.json @@ -0,0 +1,7 @@ +{ + "maxFilesChanged": 25, + "maxLinesChanged": 400, + "forbiddenPaths": ["node_modules", "dist", "build", ".svelte-kit", ".next"], + "requireProgressUpdate": true, + "maxFailureRetries": 3 +} diff --git a/scripts/ralph/failure.json b/scripts/ralph/failure.json new file mode 100644 index 0000000..c5a87e9 --- /dev/null +++ b/scripts/ralph/failure.json @@ -0,0 +1,6 @@ +{ + "consecutiveFailures": 0, + "lastRunUrl": "", + "lastError": "", + "lastTimestamp": "" +} diff --git a/scripts/ralph/guard.sh b/scripts/ralph/guard.sh new file mode 100755 index 0000000..085e7b7 --- /dev/null +++ b/scripts/ralph/guard.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +set -euo pipefail + +constraints_file="${1:-}" + +if [[ -z "${constraints_file}" ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +if ! command -v jq >/dev/null 2>&1; then + echo "jq is required for guard checks" >&2 + exit 1 +fi + +if [[ ! -f "${constraints_file}" ]]; then + echo "Constraints file not found: ${constraints_file}" >&2 + exit 1 +fi + +if grep -Eq "^-\\s*PAUSED:\\s*true" AGENTS.md; then + echo "Guard blocked: AGENTS.md is paused" >&2 + exit 1 +fi + +max_files=$(jq -r '.maxFilesChanged // 0' "${constraints_file}") +max_lines=$(jq -r '.maxLinesChanged // 0' "${constraints_file}") +require_progress=$(jq -r '.requireProgressUpdate // false' "${constraints_file}") +readarray -t forbidden_paths < <(jq -r '.forbiddenPaths[]?' "${constraints_file}") + +files_changed=$(git diff --name-only) + +if [[ -z "${files_changed}" ]]; then + echo "Guard note: no changes detected; nothing to validate." + exit 0 +fi + +file_count=$(echo "${files_changed}" | wc -l | tr -d ' ') +if [[ "${max_files}" -gt 0 && "${file_count}" -gt "${max_files}" ]]; then + echo "Guard failed: ${file_count} files changed (max ${max_files})." >&2 + exit 1 +fi + +line_total=$(git diff --numstat | awk '{add+=$1; del+=$2} END {print add+del}') +if [[ "${max_lines}" -gt 0 && "${line_total}" -gt "${max_lines}" ]]; then + echo "Guard failed: ${line_total} total line changes (max ${max_lines})." >&2 + exit 1 +fi + +for path in "${forbidden_paths[@]}"; do + if echo "${files_changed}" | grep -E "^${path}(/|$)" >/dev/null 2>&1; then + echo "Guard failed: forbidden path touched (${path})." >&2 + exit 1 + fi +done + +if [[ "${require_progress}" == "true" ]]; then + prd_changed=$(echo "${files_changed}" | grep -c "scripts/ralph/prd.json" || true) + progress_changed=$(echo "${files_changed}" | grep -c "scripts/ralph/progress.txt" || true) + if [[ "${prd_changed}" -gt 0 && "${progress_changed}" -eq 0 ]]; then + echo "Guard failed: prd.json changed without updating progress.txt." >&2 + exit 1 + fi +fi + +echo "Guard passed: constraints satisfied." diff --git a/scripts/ralph/prd.json b/scripts/ralph/prd.json new file mode 100644 index 0000000..3a73ca1 --- /dev/null +++ b/scripts/ralph/prd.json @@ -0,0 +1,32 @@ +[ + { + "id": "S1", + "title": "Boot Ralph automation scaffold", + "status": "todo", + "acceptanceCriteria": [ + "Required Ralph state files and OpenCode config exist in the repo", + "Guard script is available for later iterations" + ], + "notes": "" + }, + { + "id": "S2", + "title": "Add/verify diff guard in CI", + "status": "todo", + "acceptanceCriteria": [ + "Guard runs in the Ralph workflow", + "Guard fails on forbidden paths or oversized diffs" + ], + "notes": "" + }, + { + "id": "S3", + "title": "Make README describe how to run", + "status": "todo", + "acceptanceCriteria": [ + "README documents the Ralph loop triggers", + "README lists required secrets and state files" + ], + "notes": "" + } +] diff --git a/scripts/ralph/progress.txt b/scripts/ralph/progress.txt new file mode 100644 index 0000000..3d3ebcd --- /dev/null +++ b/scripts/ralph/progress.txt @@ -0,0 +1,2 @@ +# Ralph Progress +- Initialized scaffold and baseline state. From 2503208201f7630c7944528161b17a0baa960d8e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 20:19:21 +0000 Subject: [PATCH 3/8] Harden guard script Co-authored-by: acoyfellow <1666099+acoyfellow@users.noreply.github.com> --- AGENTS.md | 2 +- scripts/ralph/guard.sh | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b93d325..fc8ffa1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,4 +22,4 @@ ## Footguns - Do not touch build artifacts (dist, node_modules, .svelte-kit, build). - Avoid changing more than 25 files or 400 total lines per iteration. -- Guard must pass before any commit or push.*** +- Guard must pass before any commit or push. diff --git a/scripts/ralph/guard.sh b/scripts/ralph/guard.sh index 085e7b7..7f2c3fa 100755 --- a/scripts/ralph/guard.sh +++ b/scripts/ralph/guard.sh @@ -41,18 +41,20 @@ if [[ "${max_files}" -gt 0 && "${file_count}" -gt "${max_files}" ]]; then exit 1 fi -line_total=$(git diff --numstat | awk '{add+=$1; del+=$2} END {print add+del}') +line_total=$(git diff --numstat | awk '{add+=$1; del+=$2} END {total=add+del; if (total=="") print 0; else print total}') if [[ "${max_lines}" -gt 0 && "${line_total}" -gt "${max_lines}" ]]; then echo "Guard failed: ${line_total} total line changes (max ${max_lines})." >&2 exit 1 fi -for path in "${forbidden_paths[@]}"; do - if echo "${files_changed}" | grep -E "^${path}(/|$)" >/dev/null 2>&1; then - echo "Guard failed: forbidden path touched (${path})." >&2 - exit 1 - fi -done +if [[ ${#forbidden_paths[@]} -gt 0 ]]; then + for path in "${forbidden_paths[@]}"; do + if echo "${files_changed}" | grep -E "^${path}(/|$)" >/dev/null 2>&1; then + echo "Guard failed: forbidden path touched (${path})." >&2 + exit 1 + fi + done +fi if [[ "${require_progress}" == "true" ]]; then prd_changed=$(echo "${files_changed}" | grep -c "scripts/ralph/prd.json" || true) From 65d5cd60a5a0d007b6d720cdfd4f681c65ffb410 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 20:21:59 +0000 Subject: [PATCH 4/8] Address review updates Co-authored-by: acoyfellow <1666099+acoyfellow@users.noreply.github.com> --- .github/workflows/ralph.yml | 2 +- scripts/ralph/guard.sh | 17 +++++++++++------ scripts/ralph/prd.json | 2 +- scripts/ralph/progress.txt | 1 + 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ralph.yml b/.github/workflows/ralph.yml index 70a181d..995a2d0 100644 --- a/.github/workflows/ralph.yml +++ b/.github/workflows/ralph.yml @@ -9,7 +9,7 @@ on: jobs: ralph: - if: ${{ github.event_name == 'workflow_dispatch' || github.actor == 'Copilot' || github.actor == 'copilot-swe-agent[bot]' }} + if: ${{ github.event_name == 'workflow_dispatch' || github.actor_id == 198982749 }} runs-on: ubuntu-latest steps: - name: Checkout diff --git a/scripts/ralph/guard.sh b/scripts/ralph/guard.sh index 7f2c3fa..4be6cd4 100755 --- a/scripts/ralph/guard.sh +++ b/scripts/ralph/guard.sh @@ -18,7 +18,7 @@ if [[ ! -f "${constraints_file}" ]]; then exit 1 fi -if grep -Eq "^-\\s*PAUSED:\\s*true" AGENTS.md; then +if grep -Eq "^[[:space:]-]*PAUSED:[[:space:]]*true" AGENTS.md; then echo "Guard blocked: AGENTS.md is paused" >&2 exit 1 fi @@ -35,6 +35,8 @@ if [[ -z "${files_changed}" ]]; then exit 0 fi +readarray -t changed_list <<< "${files_changed}" + file_count=$(echo "${files_changed}" | wc -l | tr -d ' ') if [[ "${max_files}" -gt 0 && "${file_count}" -gt "${max_files}" ]]; then echo "Guard failed: ${file_count} files changed (max ${max_files})." >&2 @@ -48,11 +50,14 @@ if [[ "${max_lines}" -gt 0 && "${line_total}" -gt "${max_lines}" ]]; then fi if [[ ${#forbidden_paths[@]} -gt 0 ]]; then - for path in "${forbidden_paths[@]}"; do - if echo "${files_changed}" | grep -E "^${path}(/|$)" >/dev/null 2>&1; then - echo "Guard failed: forbidden path touched (${path})." >&2 - exit 1 - fi + for file in "${changed_list[@]}"; do + for path in "${forbidden_paths[@]}"; do + [[ -z "${path}" ]] && continue + if [[ "${file}" == "${path}" || "${file}" == "${path}/"* ]]; then + echo "Guard failed: forbidden path touched (${path})." >&2 + exit 1 + fi + done done fi diff --git a/scripts/ralph/prd.json b/scripts/ralph/prd.json index 3a73ca1..ab9ad49 100644 --- a/scripts/ralph/prd.json +++ b/scripts/ralph/prd.json @@ -21,7 +21,7 @@ }, { "id": "S3", - "title": "Make README describe how to run", + "title": "Update README with usage instructions", "status": "todo", "acceptanceCriteria": [ "README documents the Ralph loop triggers", diff --git a/scripts/ralph/progress.txt b/scripts/ralph/progress.txt index 3d3ebcd..7a6a0d2 100644 --- a/scripts/ralph/progress.txt +++ b/scripts/ralph/progress.txt @@ -1,2 +1,3 @@ # Ralph Progress - Initialized scaffold and baseline state. +- Clarified story naming and guard behavior. From e66a1a9c55f4438e411aefd25574a290de1ce3b4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 20:22:44 +0000 Subject: [PATCH 5/8] Refine guard diff scope Co-authored-by: acoyfellow <1666099+acoyfellow@users.noreply.github.com> --- scripts/ralph/guard.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ralph/guard.sh b/scripts/ralph/guard.sh index 4be6cd4..0eed84f 100755 --- a/scripts/ralph/guard.sh +++ b/scripts/ralph/guard.sh @@ -28,7 +28,7 @@ max_lines=$(jq -r '.maxLinesChanged // 0' "${constraints_file}") require_progress=$(jq -r '.requireProgressUpdate // false' "${constraints_file}") readarray -t forbidden_paths < <(jq -r '.forbiddenPaths[]?' "${constraints_file}") -files_changed=$(git diff --name-only) +files_changed=$(git diff HEAD --name-only) if [[ -z "${files_changed}" ]]; then echo "Guard note: no changes detected; nothing to validate." @@ -43,7 +43,7 @@ if [[ "${max_files}" -gt 0 && "${file_count}" -gt "${max_files}" ]]; then exit 1 fi -line_total=$(git diff --numstat | awk '{add+=$1; del+=$2} END {total=add+del; if (total=="") print 0; else print total}') +line_total=$(git diff HEAD --numstat | awk '{add+=$1; del+=$2} END {total=add+del; if (total=="") print 0; else print total}') if [[ "${max_lines}" -gt 0 && "${line_total}" -gt "${max_lines}" ]]; then echo "Guard failed: ${line_total} total line changes (max ${max_lines})." >&2 exit 1 From cfceeadec27e066da538e20317c3f7e52be90cc5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 20:24:35 +0000 Subject: [PATCH 6/8] Tighten guard checks Co-authored-by: acoyfellow <1666099+acoyfellow@users.noreply.github.com> --- .github/workflows/ralph.yml | 2 +- scripts/ralph/guard.sh | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ralph.yml b/.github/workflows/ralph.yml index 995a2d0..e670d90 100644 --- a/.github/workflows/ralph.yml +++ b/.github/workflows/ralph.yml @@ -9,7 +9,7 @@ on: jobs: ralph: - if: ${{ github.event_name == 'workflow_dispatch' || github.actor_id == 198982749 }} + if: ${{ github.event_name == 'workflow_dispatch' || format('{0}', github.actor_id) == vars.RALPH_BOT_ID || github.actor_id == 198982749 }} runs-on: ubuntu-latest steps: - name: Checkout diff --git a/scripts/ralph/guard.sh b/scripts/ralph/guard.sh index 0eed84f..fcec067 100755 --- a/scripts/ralph/guard.sh +++ b/scripts/ralph/guard.sh @@ -18,7 +18,7 @@ if [[ ! -f "${constraints_file}" ]]; then exit 1 fi -if grep -Eq "^[[:space:]-]*PAUSED:[[:space:]]*true" AGENTS.md; then +if grep -Eq "^[[:space:]]*-[[:space:]]*PAUSED:[[:space:]]*true" AGENTS.md; then echo "Guard blocked: AGENTS.md is paused" >&2 exit 1 fi @@ -53,10 +53,12 @@ if [[ ${#forbidden_paths[@]} -gt 0 ]]; then for file in "${changed_list[@]}"; do for path in "${forbidden_paths[@]}"; do [[ -z "${path}" ]] && continue - if [[ "${file}" == "${path}" || "${file}" == "${path}/"* ]]; then - echo "Guard failed: forbidden path touched (${path})." >&2 - exit 1 - fi + case "${file}" in + "${path}"|"${path}"/*) + echo "Guard failed: forbidden path touched (${path})." >&2 + exit 1 + ;; + esac done done fi From c7fec3a060ea3ae2b69c21ba3682bd052958bab2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 20:26:50 +0000 Subject: [PATCH 7/8] Improve guard parsing Co-authored-by: acoyfellow <1666099+acoyfellow@users.noreply.github.com> --- .github/workflows/ralph.yml | 1 + scripts/ralph/guard.sh | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ralph.yml b/.github/workflows/ralph.yml index e670d90..9489ff2 100644 --- a/.github/workflows/ralph.yml +++ b/.github/workflows/ralph.yml @@ -9,6 +9,7 @@ on: jobs: ralph: + # 198982749 is the Copilot app actor id fallback when RALPH_BOT_ID is unset. if: ${{ github.event_name == 'workflow_dispatch' || format('{0}', github.actor_id) == vars.RALPH_BOT_ID || github.actor_id == 198982749 }} runs-on: ubuntu-latest steps: diff --git a/scripts/ralph/guard.sh b/scripts/ralph/guard.sh index fcec067..24d407f 100755 --- a/scripts/ralph/guard.sh +++ b/scripts/ralph/guard.sh @@ -27,23 +27,20 @@ max_files=$(jq -r '.maxFilesChanged // 0' "${constraints_file}") max_lines=$(jq -r '.maxLinesChanged // 0' "${constraints_file}") require_progress=$(jq -r '.requireProgressUpdate // false' "${constraints_file}") readarray -t forbidden_paths < <(jq -r '.forbiddenPaths[]?' "${constraints_file}") +readarray -t changed_list < <(git diff HEAD --name-only) -files_changed=$(git diff HEAD --name-only) - -if [[ -z "${files_changed}" ]]; then +if [[ ${#changed_list[@]} -eq 0 ]]; then echo "Guard note: no changes detected; nothing to validate." exit 0 fi -readarray -t changed_list <<< "${files_changed}" - -file_count=$(echo "${files_changed}" | wc -l | tr -d ' ') +file_count=${#changed_list[@]} if [[ "${max_files}" -gt 0 && "${file_count}" -gt "${max_files}" ]]; then echo "Guard failed: ${file_count} files changed (max ${max_files})." >&2 exit 1 fi -line_total=$(git diff HEAD --numstat | awk '{add+=$1; del+=$2} END {total=add+del; if (total=="") print 0; else print total}') +line_total=$(git diff HEAD --numstat | awk '{add+=$1; del+=$2} END {total=add+del; if (NR==0) print 0; else print total}') if [[ "${max_lines}" -gt 0 && "${line_total}" -gt "${max_lines}" ]]; then echo "Guard failed: ${line_total} total line changes (max ${max_lines})." >&2 exit 1 @@ -64,8 +61,12 @@ if [[ ${#forbidden_paths[@]} -gt 0 ]]; then fi if [[ "${require_progress}" == "true" ]]; then - prd_changed=$(echo "${files_changed}" | grep -c "scripts/ralph/prd.json" || true) - progress_changed=$(echo "${files_changed}" | grep -c "scripts/ralph/progress.txt" || true) + prd_changed=0 + progress_changed=0 + for file in "${changed_list[@]}"; do + [[ "${file}" == "scripts/ralph/prd.json" ]] && prd_changed=$((prd_changed+1)) + [[ "${file}" == "scripts/ralph/progress.txt" ]] && progress_changed=$((progress_changed+1)) + done if [[ "${prd_changed}" -gt 0 && "${progress_changed}" -eq 0 ]]; then echo "Guard failed: prd.json changed without updating progress.txt." >&2 exit 1 From 4edd6ea4073e0412f7cb32b11ca7321520c2e082 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 20:28:27 +0000 Subject: [PATCH 8/8] Add workflow permissions Co-authored-by: acoyfellow <1666099+acoyfellow@users.noreply.github.com> --- .github/workflows/ralph.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ralph.yml b/.github/workflows/ralph.yml index 9489ff2..00a29ae 100644 --- a/.github/workflows/ralph.yml +++ b/.github/workflows/ralph.yml @@ -7,6 +7,9 @@ on: - "AGENTS.md" - "scripts/ralph/**" +permissions: + contents: write + jobs: ralph: # 198982749 is the Copilot app actor id fallback when RALPH_BOT_ID is unset.