work

star 1

Ralph Loop tick-loop for single-ticket Quay development. Replaces the /start → /code → /pr → /poll skill chain with one continuous state machine. Each tick: read state, do one thing, write state, continue.

quay By quay schedule Updated 5/6/2026

name: work description: > Ralph Loop tick-loop for single-ticket Quay development. Replaces the /start → /code → /pr → /poll skill chain with one continuous state machine. Each tick: read state, do one thing, write state, continue. argument-hint: PROJQUAY-XXXX [--manual] allowed-tools: - Bash(bash .claude/scripts/tick-state.sh *) - Bash(bash .claude/scripts/jira-ops.sh *) - Bash(bash .claude/scripts/format-and-lint.sh *) - Bash(bash .claude/scripts/validate-pr-title.sh *) - Bash(bash .claude/scripts/poll-pr.sh *) - Bash(bash .claude/scripts/session-setup.sh) - Bash(git *) - Bash(gh *) - Bash(make *) - Bash(pytest *) - Bash(pre-commit *) - Bash(alembic *) - Bash(npm *) - Read - Write - Edit - Glob - Grep - Agent - AskUserQuestion

/work — Ralph Loop Tick-Loop

Implement JIRA ticket $ARGUMENTS from assignment to merge-ready PR in one continuous loop. No separate skills, no chaining — one state machine that advances mechanically.


Execution Model

Parse $ARGUMENTS: the first token is the ticket key, --manual (if present) sets manual stepping mode.

TICKET = first token of $ARGUMENTS
MODE = "manual" if --manual present, else "auto"

Initialize or Resume

bash .claude/scripts/session-setup.sh
bash .claude/scripts/tick-state.sh init $TICKET --mode $MODE

If state already exists, this prints the current state and resumes from there.

The Tick Loop

while state != COMPLETE:
    1. READ   — bash .claude/scripts/tick-state.sh read $TICKET
    2. ACT    — execute the handler for the current state (below)
    3. WRITE  — bash .claude/scripts/tick-state.sh advance $TICKET <NEXT_STATE>
    4. SLEEP  — if DORMANT: the poll script blocks (0 tokens consumed)
    5. PAUSE  — if manual mode: ask user [c]ontinue / [s]kip / [i]nspect / [a]bort
    6. LOOP   — go back to step 1

CRITICAL: Do NOT stop between ticks. The loop is continuous. The only valid exit points are:

  • State reaches COMPLETE
  • Manual mode and user chooses [a]bort
  • triage_attempts >= 3 (ask user for guidance)

State Handlers

ASSIGN

View the JIRA ticket, assign it to yourself, and transition to ASSIGNED.

bash .claude/scripts/jira-ops.sh view $TICKET
bash .claude/scripts/jira-ops.sh assign $TICKET
bash .claude/scripts/jira-ops.sh transition $TICKET "ASSIGNED"

Review the ticket summary and description to understand the scope.

→ advance to BRANCH


BRANCH

Check if backporting is needed, create a feature branch, and load area docs.

bash .claude/scripts/jira-ops.sh check-version $TICKET

If Target Version is set, record it:

bash .claude/scripts/tick-state.sh set $TICKET backport_required true

Create the branch:

git checkout master && git pull origin master
git checkout -b $TICKET-short-description

Derive the branch name from the ticket summary (kebab-case). Record it:

bash .claude/scripts/tick-state.sh set $TICKET branch "<branch-name>"

Based on the ticket's area, read the relevant docs:

Area Doc
API endpoints, auth agent_docs/api.md
Database, migrations agent_docs/database.md
Testing agent_docs/testing.md
Architecture agent_docs/architecture.md
React frontend web/AGENTS.md

Record which docs were loaded:

bash .claude/scripts/tick-state.sh set $TICKET area_docs '["agent_docs/api.md"]'

→ advance to IMPLEMENT


IMPLEMENT

Read AGENTS.md for universal conventions, plus the area docs loaded in BRANCH.

Implement the changes following Quay conventions:

  • Exception types from endpoints/exception.py
  • Existing import ordering patterns
  • Never hand-write migration files — use alembic revision -m "description"
  • No secrets in code

This is the main implementation state. Write code, create tests, handle edge cases.

→ advance to TEST


TEST

Run quality checks:

bash .claude/scripts/format-and-lint.sh            # pre-commit on staged files

Run relevant tests:

TEST=true PYTHONPATH="." pytest path/to/test.py -v  # specific tests
make unit-test                                       # all unit tests
make types-test                                      # mypy

If tests fail: fix the code and re-run. Stay in TEST until all checks pass. Do NOT go back to IMPLEMENT — fix in-place.

→ advance to COMMIT when all pass


COMMIT

Stage and commit with the proper message format:

git add <specific files>
git commit -m "<subsystem>: <what changed> ($TICKET)

<why this change was made>"

Pre-commit hooks run automatically on commit. If they fail: fix, re-stage, create a new commit. Stay in COMMIT until the commit succeeds.

→ advance to PR_CREATE


PR_CREATE

Validate the PR title against the CI-enforced regex:

bash .claude/scripts/validate-pr-title.sh "$TICKET: type(scope): description"

Title format: PROJQUAY-XXXX: type(scope): description (type is lowercase).

Read the description template at .claude/templates/pr-description.md and fill it in. Write the filled template to /tmp/pr-body.md.

Check for ambient session:

echo $AGENTIC_SESSION_NAME

Create the PR:

gh pr create \
  --title "$TICKET: type(scope): description" \
  --body "$(cat /tmp/pr-body.md)" \
  --base master

Add --label "ambient-session" if AGENTIC_SESSION_NAME is set.

Record the PR number:

PR_NUM=$(gh pr view --json number --jq '.number')
bash .claude/scripts/tick-state.sh set $TICKET pr_number $PR_NUM

→ advance to DORMANT_CI


DORMANT_CI

This is a yield point. The poll script blocks internally — zero tokens consumed.

PR_NUMBER=$(bash .claude/scripts/tick-state.sh read $TICKET | jq -r '.pr_number')
bash .claude/scripts/poll-pr.sh $PR_NUMBER --once

Read the exit code and route:

Exit Code Meaning Next State
0 All checks pass COMPLETE
1 CI failures ADDRESS_FEEDBACK
2 Checks pending DORMANT_CI (re-poll)
3 Review comments ADDRESS_FEEDBACK
4 Awaiting human review DORMANT_REVIEW

Record the exit code:

bash .claude/scripts/tick-state.sh set $TICKET last_poll_exit $EXIT_CODE

→ advance based on exit code


ADDRESS_FEEDBACK

Read the last poll exit code to determine what kind of feedback to address.

Restore the PR number from state:

PR_NUMBER=$(bash .claude/scripts/tick-state.sh read $TICKET | jq -r '.pr_number')

Exit 1 — CI failures:

  • Run bash .claude/scripts/poll-pr.sh $PR_NUMBER --once --full to see which jobs failed
  • Fix the failing code
  • Run tests locally to verify
  • Commit and push

Exit 3 — Review comments:

  • Run bash .claude/scripts/poll-pr.sh $PR_NUMBER --once --full to see inline comments with reply and resolve commands
  • For each comment, evaluate critically:
    • Valid: fix the code, reply explaining what you changed, resolve the thread
    • Invalid: reply with your reasoning, resolve the thread
    • Unclear: reply asking for clarification (do NOT resolve)
  • Commit fixes and push

Triage guard:

# Increment triage_attempts
ATTEMPTS=$(bash .claude/scripts/tick-state.sh read $TICKET | jq '.triage_attempts')
bash .claude/scripts/tick-state.sh set $TICKET triage_attempts $((ATTEMPTS + 1))

If triage_attempts >= 3: stop the loop and ask the user for guidance. The same class of failure keeps recurring — a human needs to look.

After pushing fixes: → advance to DORMANT_CI (re-poll to verify the fix)


DORMANT_REVIEW

This is a yield point. The PR is awaiting human review approval.

PR_NUMBER=$(bash .claude/scripts/tick-state.sh read $TICKET | jq -r '.pr_number')
bash .claude/scripts/poll-pr.sh $PR_NUMBER --once
Exit Code Next State
0 COMPLETE
3 ADDRESS_FEEDBACK (new comments)
4 DORMANT_REVIEW (still waiting — re-poll)

→ advance based on exit code


COMPLETE

The PR is merge-ready. Report the summary:

═══════════════════════════════════════════════════════════
  COMPLETE — $TICKET
═══════════════════════════════════════════════════════════
  Branch:    <branch>
  PR:        #<number>
  CI:        all passing
  Review:    approved
  Ticks:     <tick_count>
  Duration:  <created_at → now>
═══════════════════════════════════════════════════════════

If backport_required is true, suggest running /backport <PR#>.

Exit the loop. The task is done.


Manual Mode

When mode is "manual" in the state file, pause after each tick and ask:

───────────────────────────────────────
  Tick #N: CURRENT_STATE → NEXT_STATE
  Completed: <brief summary of what was done>
  Next: <what the next state will do>
───────────────────────────────────────
  [c] Continue    [s] Skip to next state
  [i] Inspect     [a] Abort

Use AskUserQuestion to present this prompt. On [a]bort, stop the loop immediately. On [s]kip, advance without executing. On [i]nspect, show the full state file and re-prompt.

Manual mode is the Ralph Loop's "watch the loop" principle — start manual to understand behavior, then switch to auto for full autonomy.

Install via CLI
npx skills add https://github.com/quay/ai-helpers --skill work
Repository Details
star Stars 1
call_split Forks 6
navigation Branch main
article Path SKILL.md
More from Creator