# converge — implement, then loop until the suite is green.
#
# The archetype `type: loop` exists for. `max_retries` was never this: a retry
# re-runs *the same step* on *its own* failure, and converging needs three
# different steps — probe, decide, repair — with the decision in the middle.
#
# Read the loop in three lines:
#
#   suite   is `allow_failure: true`, so a red run advances instead of blocking
#           the task. That is what turns its exit code into data.
#   passed  reads that exit code and ends the loop the first time it is zero.
#           A `break` is post-test by construction: it can only run after the
#           body above it has, which is why there is no `while:`.
#   repair  only runs when the break did not fire, and sees the failure text.
#
# `count: 4` is the budget, not a target — four repairs and no more. The final
# `verify` step is deliberately outside the loop and carries no
# `allow_failure`: if the loop spent its budget without converging, that is the
# step that blocks the task, and it is the one whose red output a human reads.
name: converge
description: Implement a change, then repair until the test suite passes.

defaults:
  agent: claude
  max_retries: 2
  timeout: 30m

steps:
  - id: implement
    type: agent
    prompt: |
      Implement this change on {{.Task.BranchName}}.

      {{.Task.Title}}
      {{with .Task.Description}}
      Details:
      {{.}}
      {{end}}
      Match the conventions of the code you are editing. Add tests for what you
      add; do not weaken, skip or delete an existing one.

  - id: green
    type: loop
    count: 4
    steps:
      # A probe has nothing to gain from retrying: it is deterministic, and a
      # second identical run costs a minute to reach the same answer.
      - id: suite
        type: command
        run: go test ./...
        allow_failure: true
        max_retries: 0

      - id: passed
        type: break
        if: '{{ eq (index .Steps "suite").ExitCode 0 }}'

      - id: repair
        type: agent
        max_retries: 0
        prompt: |
          The test suite is red on iteration {{.Loop.Index}} of {{.Task.Title}}:

          {{ (index .Steps "suite").Result }}

          Fix the underlying cause. Do not weaken, skip, disable or delete a
          test to get green, and do not change a test's expectations unless the
          test itself is what is wrong — if it is, say so in the commit.

  # Outside the loop on purpose: the loop succeeding means "it stopped", and
  # this is what makes it mean "it worked".
  - id: verify
    type: command
    run: go test ./...
    max_retries: 0

  - id: commit
    type: command
    run: 'git add -A && git commit -m "{{.Task.Title}}"'
