diff --git a/tests/test_scheduling.py b/tests/test_scheduling.py index a78d757..7cfa7d9 100644 --- a/tests/test_scheduling.py +++ b/tests/test_scheduling.py @@ -680,3 +680,49 @@ def test_get_program_name_extracted(self): def test_read_program_state_extracted(self): # read_program_state exists in the workflow but depends on file I/O assert "read_program_state" in _funcs + + +# --------------------------------------------------------------------------- +# Workflow step ordering — repo-memory must be available before scheduling +# --------------------------------------------------------------------------- + +class TestWorkflowStepOrdering: + """Verify that the repo-memory clone step appears before the scheduling step. + + The scheduling pre-step reads persisted state from repo-memory. If the + clone happens after scheduling, the script cannot see previous-run state, + causing incorrect selection/skip behaviour. + """ + + CLONE_STEP = "Clone repo-memory for scheduling" + SCHED_STEP = "Check which programs are due" + + def _load_steps(self): + """Return the list of pre-step names from workflows/autoloop.md.""" + import os + import re + + wf_path = os.path.join(os.path.dirname(__file__), "..", "workflows", "autoloop.md") + with open(wf_path) as f: + content = f.read() + step_names = [] + for m in re.finditer(r'^\s*-\s*name:\s*(.+)$', content, re.MULTILINE): + step_names.append(m.group(1).strip()) + return step_names + + def test_clone_step_exists(self): + """A step that clones repo-memory for scheduling must exist.""" + steps = self._load_steps() + assert self.CLONE_STEP in steps, ( + f"Expected step '{self.CLONE_STEP}' not found. Steps: {steps}" + ) + + def test_clone_before_scheduling(self): + """The repo-memory clone step must come before 'Check which programs are due'.""" + steps = self._load_steps() + clone_idx = steps.index(self.CLONE_STEP) + sched_idx = steps.index(self.SCHED_STEP) + assert clone_idx < sched_idx, ( + f"'{self.CLONE_STEP}' (index {clone_idx}) must come before " + f"'{self.SCHED_STEP}' (index {sched_idx}). Steps: {steps}" + ) diff --git a/workflows/autoloop.md b/workflows/autoloop.md index 32ddde3..de29c07 100644 --- a/workflows/autoloop.md +++ b/workflows/autoloop.md @@ -81,6 +81,28 @@ imports: - shared/reporting.md steps: + - name: Clone repo-memory for scheduling + env: + GH_TOKEN: ${{ github.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} + run: | + # Clone the repo-memory branch so the scheduling step can read persisted state + # from previous runs. The framework-managed repo-memory clone happens after + # pre-steps, so we perform an early shallow clone here. + MEMORY_DIR="/tmp/gh-aw/repo-memory/autoloop" + BRANCH="memory/autoloop" + mkdir -p "$(dirname "$MEMORY_DIR")" + REPO_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" + AUTH_URL="$(echo "$REPO_URL" | sed "s|https://|https://x-access-token:${GH_TOKEN}@|")" + if git ls-remote --exit-code --heads "$AUTH_URL" "$BRANCH" > /dev/null 2>&1; then + git clone --single-branch --branch "$BRANCH" --depth 1 "$AUTH_URL" "$MEMORY_DIR" 2>&1 + echo "Cloned repo-memory branch to $MEMORY_DIR" + else + mkdir -p "$MEMORY_DIR" + echo "No repo-memory branch found yet (first run). Created empty directory." + fi + - name: Check which programs are due env: GITHUB_TOKEN: ${{ github.token }}