Skip to content

Instantly share code, notes, and snippets.

@davidaurelio
Last active July 1, 2026 14:24
Show Gist options
  • Select an option

  • Save davidaurelio/7fd3c3e46cfb908a992e519c11e84206 to your computer and use it in GitHub Desktop.

Select an option

Save davidaurelio/7fd3c3e46cfb908a992e519c11e84206 to your computer and use it in GitHub Desktop.
Skill to work on a DRS Agent-ergonomic refactoring task autonomously. Put this into `{.agents,.claude,.codex}/skills/drs-epic-task-runner/`
#!/usr/bin/env python3
"""Mark one child issue checkbox complete in the DRS epic body.
GitHub's public GraphQL updateIssue mutation does not expose the website's
bodyVersion field. This helper avoids blind overwrites by fetching the latest
body, changing only one exact task-list line, re-fetching before update, and
verifying after update.
"""
from __future__ import annotations
import argparse
import json
import re
import subprocess
import sys
import urllib.request
from dataclasses import dataclass
DEFAULT_REPO = "adobe-rnd/llmo-data-retrieval-service"
DEFAULT_EPIC = 2066
@dataclass(frozen=True)
class IssueBody:
node_id: str
body: str
updated_at: str
url: str
def parse_repo(repo: str) -> tuple[str, str]:
parts = repo.split("/", 1)
if len(parts) != 2 or not all(parts):
raise ValueError(f"repo must look like owner/name, got {repo!r}")
return parts[0], parts[1]
def gh_token() -> str:
result = subprocess.run(
["gh", "auth", "token"],
check=True,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
return result.stdout.strip()
def graphql(token: str, query: str, variables: dict[str, object]) -> dict[str, object]:
request = urllib.request.Request(
"https://api.github.com/graphql",
data=json.dumps({"query": query, "variables": variables}).encode(),
headers={
"Authorization": f"Bearer {token}",
"Accept": "application/vnd.github+json",
"Content-Type": "application/json",
"X-GitHub-Api-Version": "2022-11-28",
},
method="POST",
)
with urllib.request.urlopen(request) as response:
payload = json.loads(response.read().decode())
if payload.get("errors"):
raise RuntimeError(json.dumps(payload["errors"], indent=2))
return payload["data"]
def fetch_issue_body(token: str, repo: str, number: int) -> IssueBody:
owner, name = parse_repo(repo)
query = """
query($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
issue(number: $number) {
id
body
updatedAt
url
}
}
}
"""
data = graphql(token, query, {"owner": owner, "name": name, "number": number})
issue = data["repository"]["issue"]
if issue is None:
raise RuntimeError(f"issue #{number} not found in {repo}")
return IssueBody(
node_id=issue["id"],
body=issue["body"],
updated_at=issue["updatedAt"],
url=issue["url"],
)
def update_issue_body(token: str, node_id: str, body: str) -> None:
mutation = """
mutation($id: ID!, $body: String!) {
updateIssue(input: {id: $id, body: $body}) {
issue {
id
updatedAt
}
}
}
"""
graphql(token, mutation, {"id": node_id, "body": body})
def line_mentions_issue(line: str, issue: int) -> bool:
issue_ref = rf"(?<!\d)#{issue}(?!\d)"
issue_url = rf"/issues/{issue}(?!\d)"
return re.search(issue_ref, line) is not None or re.search(issue_url, line) is not None
def patch_checkbox(body: str, issue: int) -> tuple[str, str, str, bool]:
lines = body.splitlines(keepends=True)
matches: list[tuple[int, str, str]] = []
task_pattern = re.compile(r"^(\s*[-*]\s+\[)([ xX])(\]\s+.*)$")
for index, line in enumerate(lines):
match = task_pattern.match(line)
if match and line_mentions_issue(line, issue):
matches.append((index, match.group(2), line.rstrip("\n")))
if not matches:
raise RuntimeError(f"no Markdown task-list checkbox found for issue #{issue}")
if len(matches) > 1:
rendered = "\n".join(f"- {line}" for _, _, line in matches)
raise RuntimeError(f"ambiguous task-list matches for issue #{issue}:\n{rendered}")
index, marker, old_line = matches[0]
if marker.lower() == "x":
return body, old_line, old_line, False
new_line = task_pattern.sub(r"\1x\3", lines[index], count=1)
lines[index] = new_line
return "".join(lines), old_line, new_line.rstrip("\n"), True
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("issue", type=int, help="child issue number to mark complete")
parser.add_argument("--repo", default=DEFAULT_REPO)
parser.add_argument("--epic", type=int, default=DEFAULT_EPIC)
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args()
token = gh_token()
first = fetch_issue_body(token, args.repo, args.epic)
patched, old_line, new_line, changed = patch_checkbox(first.body, args.issue)
if not changed:
print(f"issue #{args.issue} is already checked in epic #{args.epic}")
print(old_line)
return 0
if args.dry_run:
print(f"would update epic #{args.epic}:")
print(f"- {old_line}")
print(f"+ {new_line}")
return 0
latest = fetch_issue_body(token, args.repo, args.epic)
if latest.body != first.body:
patched, old_line, new_line, changed = patch_checkbox(latest.body, args.issue)
if not changed:
print(f"issue #{args.issue} became checked before update")
print(old_line)
return 0
update_issue_body(token, latest.node_id, patched)
verified = fetch_issue_body(token, args.repo, args.epic)
_, _, verified_line, verified_changed = patch_checkbox(verified.body, args.issue)
if verified_changed:
raise RuntimeError(
f"epic #{args.epic} update did not verify; issue #{args.issue} is still unchecked"
)
print(f"marked issue #{args.issue} complete in epic #{args.epic}")
print(verified_line)
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as exc:
print(f"error: {exc}", file=sys.stderr)
raise SystemExit(1)
interface:
display_name: "DRS Epic Task Runner"
short_description: "Run assigned DRS epic tasks end to end"
default_prompt: "Use $drs-epic-task-runner to select, implement, review, and merge the next DRS epic child task."
name drs-epic-task-runner
description Run llmo-data-retrieval-service epic

DRS Epic Task Runner

Overview

Use this skill to operate one child task from adobe-rnd/llmo-data-retrieval-service epic #2066 with one accountable Codex thread. Treat subagents as bounded helpers for research, independent review, or CI triage; do not delegate task ownership to one subagent per issue.

Always use the gh CLI for GitHub because the repository is private. Read the repository's AGENTS.md and CLAUDE.md before making task or code decisions.

Non-Stop Execution Rule

Do not send a final response after task selection, assignment, branch creation, repository setup, research, or partial implementation.

A final response is allowed only when one of these is true:

  1. The selected child issue is fully completed: implementation merged, PR squash-merged, CI/reviews passed, and issue closure verified.
  2. A real blocker prevents progress after reasonable retries. The blocker must be specific and external, such as an auth/permission failure, missing required information that cannot be inferred from repo/GitHub research, CI/review wait exceeding the session's practical ability to continue, or an explicit user pause request.

If neither condition is true, continue working. Use commentary updates for progress. Never convert a setup milestone into a final response.

Branch creation is not a stopping point. After creating the branch, immediately continue with issue research, code changes, tests, commit, review, PR, CI, and merge.

Completion Contract

Do not stop after task selection, assignment, branch creation, repository research, or a status update. Those are setup milestones, not completion.

Continue autonomously through implementation, local verification, commit, Claude review, PR creation, MysticatBot review handling, CI handling, and squash merge unless one of these blockers occurs:

  • required information cannot be resolved from the repo, issues, PRs, or docs,
  • GitHub or local tooling blocks progress with an authentication/permission failure,
  • an external review or CI wait exceeds the current session's practical ability to continue,
  • the user explicitly asks to pause or only report status.

When a blocker occurs, report the exact blocker and the next command/action needed. Otherwise, use concise progress updates and keep working.

If tempted to summarize progress, first ask: "Has the PR been merged and the issue state verified, or am I genuinely blocked?" If the answer is no, do not send final; continue executing the next workflow step.

Selection Policy

Gather live state before selecting a task:

gh api repos/adobe-rnd/llmo-data-retrieval-service/issues/comments/4677784765 --jq '{updated_at, body}'
gh issue view 2066 --repo adobe-rnd/llmo-data-retrieval-service --json number,title,state,body,assignees,labels,url

Parse the prioritized table and the epic body. Use live issue state, not stale checkboxes or comment text, as the source of truth for closed/assigned status.

Choose the first child issue that is:

  • open,
  • unassigned,
  • not explicitly blocked by another open/incomplete issue,
  • compatible with the epic's recommended execution order.

When the prioritization comment and epic body conflict, prefer documented dependencies and the epic's recommended execution order; use the ranking table as a tie-breaker. Record the selected issue and the policy reason in the working notes or final summary.

Inspect candidates with:

gh issue view <issue> --repo adobe-rnd/llmo-data-retrieval-service \
  --json number,title,state,assignees,body,comments,labels,url

Assign only after selection is defensible:

gh issue edit <issue> --repo adobe-rnd/llmo-data-retrieval-service --add-assignee davidaurelio

Research Before Questions

Before asking the user anything, research:

  • the selected issue body and comments,
  • epic #2066 and relevant child issues,
  • merged PRs mentioned by the issue,
  • CLAUDE.md, imported docs, and local architecture/testing docs for the touched area,
  • existing source, tests, local patterns, and documentation in docs/.

Ask only when repository and issue research cannot resolve a material ambiguity.

Implementation Workflow

Start cleanly from current origin/main:

git fetch origin
git switch main
git pull --ff-only origin main
git switch -c aurelio/<short-issue-slug>

After branch creation, immediately continue into issue research and implementation. Do not send a final answer that only reports the selected issue or branch setup.

Implement conservatively, following local patterns. Prefer a PR scope that fully closes the selected issue. If the selected issue is too large for one PR, choose the smallest defensible tranche from its own split path, make the PR scope explicit, and do not use a closing keyword unless the PR actually completes the selected issue. Never commit directly to main; never use git commit --amend.

Run targeted tests first, then the relevant repo checks from CLAUDE.md. At minimum, run touched-area tests plus:

uv run ruff check
uv run ruff format --check

Use broader checks when the change touches shared behavior, generated references, CDK, architecture boundaries, or API surfaces.

Commit with a normal commit:

git status --short
git add <files>
git commit -m "<concise imperative summary>"

Claude Review

After the first implementation commit, shell out exactly as requested with the prompt quoted on the command line:

claude --model opus --effort high "Review the current branch against origin/main for correctness, regressions, missing tests, and alignment with the DRS repo instructions. Focus on actionable findings with file/line references. Do not make changes."

Evaluate each finding. Address review feedback unless there is a stronger technical reason not to; when declining a finding, record the reason. Commit any fixes separately.

Pull Request And PR Reviews

Push and open the PR:

git push -u origin HEAD
gh pr create --repo adobe-rnd/llmo-data-retrieval-service \
  --title "<title>" \
  --body $'Introduced by: #<issue>\nFixes #<issue>\n\n<summary>\n\n<tests>'

Every PR body must include Introduced by: #<issue> or Introduced by: N/A.

For a PR that fully completes the selected issue, include Fixes #<issue> in the PR body so GitHub closes the issue when the PR is merged. Use the issue number, not the PR number. If the PR is an intentional partial tranche that must not close the selected issue, use Refs #<issue> instead and document the remaining scope in the issue or PR.

Request MysticatBot review. Prefer a GitHub review request if it works; otherwise leave a PR comment using the repository's established bot convention:

gh pr edit <pr> --repo adobe-rnd/llmo-data-retrieval-service --add-reviewer MysticatBot

Wait for the requested MysticatBot review, but do not ignore other reviewers or review bots. Inspect all PR reviews, review threads, line comments, issue comments, and check annotations, including feedback from reviewers such as hawkeye-reviewer[bot].

Do not wait for review by drs-team before proceeding. In particular, if MysticatBot has approved the PR, required CI is green, and there is no unresolved substantive feedback, a pending drs-team review is not a blocker and merge may proceed. If drs-team or one of its members leaves concrete feedback, treat that feedback like any other substantive review comment.

Use gh pr view, gh api, gh pr checks, and targeted review/comment endpoints as needed. Treat every substantive reviewer finding as input: address it unless there is a stronger technical reason not to. When declining feedback, record the reason in a PR reply or working summary. Commit and push fixes.

If any reviewer or bot requests changes, address or explicitly resolve the feedback, then request review again from the required reviewer(s). Repeat until required reviews approve or no longer request changes and no unresolved substantive feedback remains.

CI And Merge

Watch CI until all required checks pass:

gh pr checks <pr> --repo adobe-rnd/llmo-data-retrieval-service --watch

Fix failures on the same branch, commit, and push. When CI and review gates pass, squash-merge:

gh pr merge <pr> --repo adobe-rnd/llmo-data-retrieval-service --squash --delete-branch

After merge, verify the selected issue state:

gh issue view <issue> --repo adobe-rnd/llmo-data-retrieval-service --json state,url

If the PR completed the issue but GitHub did not close it automatically, close it explicitly with a comment linking the merged PR:

gh issue close <issue> --repo adobe-rnd/llmo-data-retrieval-service \
  --comment "Completed by #<pr>."

After the selected issue is closed, update the parent epic's Markdown task-list checkbox in issue #2066. GitHub's website may send an internal bodyVersion when editing issue bodies, but the public gh/GraphQL updateIssue API does not expose that field. Do not do a blind full-body overwrite.

Use the helper script so the update is race-aware: it fetches the current body, changes only the matching unchecked task-list line, refetches before updating, reapplies the one-line patch if the epic changed, updates through GraphQL, and verifies the checkbox afterward.

python3 .codex/skills/drs-epic-task-runner/scripts/mark_epic_checkbox.py <issue> --dry-run
python3 .codex/skills/drs-epic-task-runner/scripts/mark_epic_checkbox.py <issue>

Use the phrase "mark the corresponding Markdown task-list checkbox in epic #2066 as complete" when describing this step. Preserve all other epic body content. If the helper reports that the matching checkbox is absent, already checked, ambiguous, or failed verification, do not rewrite the epic body blindly; report the exact condition and leave a comment on #2066 linking the completed issue and PR.

Before final reporting, verify the issue/PR state and summarize the selected task, branch, commits, review outcome, CI result, merge result, issue closure result, and parent epic checkbox update.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment