Skip to content

amit221/Iynx

Repository files navigation

Iynx — GitHub Contribution Agent

Cybernetic lynx head logo in blue and teal, with circuit patterns and a code symbol in one eye.

CI: GitHub Actions (see workflow file) Python 3.10–3.12 pytest Coverage: minimum 78% (pytest-cov, pyproject.toml) Branch coverage enabled (coverage.py) Ruff lint and format Docker required for agent runs

An autonomous agent that discovers trendy GitHub repos, learns contribution guidelines, fixes issues, tests in Docker, and opens PRs. Uses Cursor CLI as the primary AI engine. All repo execution (clone, npm test, etc.) runs inside Docker for safety.

Architecture

  • Host: Runs discovery (GitHub API), Docker commands, and writes bootstrap/config. Never executes repo code.
  • Docker: Cursor CLI, gh, git. Clone, fix, test, and PR creation happen inside the container.

Cursor IDE: Superpowers (optional)

Superpowers adds shared agent skills (TDD, planning, debugging workflows). In Cursor Agent chat you can run /add-plugin superpowers or install Superpowers from the marketplace.

For a local install (same layout Cursor expects under ~/.cursor/plugins/local), clone the repo and reload the editor:

# Windows (PowerShell)
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.cursor\plugins\local" | Out-Null
git clone --depth 1 https://github.com/obra/superpowers.git "$env:USERPROFILE\.cursor\plugins\local\superpowers"
# macOS / Linux
mkdir -p ~/.cursor/plugins/local
git clone --depth 1 https://github.com/obra/superpowers.git ~/.cursor/plugins/local/superpowers

Then Developer: Reload Window so rules/skills load. Update with git pull inside that clone.

Prerequisites

  • Docker (the agent image runs as user dev, UID 1000, so test stacks that refuse root—e.g. embedded Postgres—work after docker build; rebuild if you still use an older root-based image)
  • Python 3.10+
  • Cursor CLI (installed in the image)
  • CURSOR_API_KEY (from Cursor settings)
  • GITHUB_TOKEN with repo scope (for discovery and PR creation)

Setup

# Clone and enter project
cd iynx

# Create venv and install deps
python -m venv .venv
.venv\Scripts\activate   # Windows
# source .venv/bin/activate  # Linux/macOS

pip install -r requirements.txt

# Run unit tests (no Docker)
pytest tests/ -v

# With coverage (threshold in pyproject.toml)
pytest tests/ -v --cov=src --cov-report=term-missing

# Lint / format (Ruff)
ruff check src tests run.py
ruff format src tests run.py

# Copy env template and fill in secrets
copy .env.example .env
# Edit .env with CURSOR_API_KEY and GITHUB_TOKEN

# Build Docker image
docker build -t iynx-agent:latest .

Usage

# Load env
set -a && source .env && set +a   # Linux/macOS
# Or on Windows: $env:CURSOR_API_KEY="..."; $env:GITHUB_TOKEN="..."

# Run the agent (discovers repos, fixes one issue, opens PR)
python run.py

# While Docker runs (clone, Cursor, tests, `gh`), lines from the container are streamed
# to the console as `INFO` log lines prefixed with `[docker]`.
# Inside the container, scripts also emit `[iynx-docker] …` timestamp lines at each phase
# (clone, bootstrap, cursor-agent, PR) so you can follow progress; disable with IYNX_DOCKER_TRACE=0.
# Cursor phases use `--output-format stream-json` and `--stream-partial-output` by default
# so agent/tool progress appears as NDJSON lines (see env table to switch to `text` if logs are too noisy).

# Target one repo (and optional issue number). Without argv[2], the agent lists open issues and picks one it can handle (Phase 2).
python run.py obra/superpowers
python run.py obra/superpowers 849
# Or: IYNX_TARGET_REPO=obra/superpowers IYNX_TARGET_ISSUE=849 python run.py

Progress for supervising agents

While python run.py runs, the orchestrator writes structured lifecycle events so another process (or a Cursor agent watching the repo) can tell what finished or failed without parsing Docker prose.

  • Default file: .iynx-run-progress.jsonl at the project root (gitignored). Each line is one JSON object (ts, run_id, phase, status, repo, issue, detail, exit_code).
  • Human logs: Lines like [iynx] phase=phase3_implement status=started repo=owner/name issue=42 mirror the same steps in the console.
  • Override path: set IYNX_PROGRESS_JSONL to a file path. Set it to empty, 0, or false to disable file output (logs still include [iynx] lines).

Follow the log (PowerShell): Get-Content .iynx-run-progress.jsonl -Wait
Follow the log (Unix): tail -f .iynx-run-progress.jsonl

phase Meaning
discovery Repo list after filters (or skipped if none)
target_resolve Explicit owner/repo from argv/env
preflight Open-issue checks before clone
clone / bootstrap Docker clone and host bootstrap
phase1_contextphase4_pr_draft Cursor phases
verify_tests Optional post-fix test re-run (skipped if disabled in orchestrator)
pr_create gh fork / push / pr create
workflow Host/Docker timeout or unexpected error
run_complete Final row: detail is pr_created or no_pr; exit_code matches process
status Meaning
started Step began
completed Step succeeded
failed Step failed
skipped Step not applicable (e.g. no repos, verify disabled)

Process exit codes: 0 = PR created; 1 = fatal config (e.g. missing CURSOR_API_KEY); 2 = run finished without a PR (discovery empty, preflight/phase/PR failure, etc.).

PR statistics (GitHub)

Count your PRs that match an optional label and a head branch regex (default fix/issue-<n>), across all repositories, using the GitHub API. Requires GITHUB_TOKEN. A label comes from --label, IYNX_STATS_LABEL, or IYNX_PR_LABEL—or use --no-label to match author + branch only (for PRs that never had a label).

# With label (filter in GitHub search)
python stats.py --format json --label your-label
python stats.py --format card --label your-label

# No label: only author + branch head (e.g. fix/issue-123 branches without the label)
python stats.py --no-label
# alias: --format share

Use IYNX_PR_LABEL when running the agent so new PRs get the same label and show up in label-based stats. GitHub Search returns at most 1,000 items per open/closed query; if you have more matches, counts can be incomplete (a warning is printed). Private repos need a token with repo scope.

If counts stay at zero: (1) With a label filter, PRs must have that exact label—older PRs opened before IYNX_PR_LABEL was set won’t have it; use --no-label instead. (2) By default only head branches matching ^fix/issue-\d+$ count; use python stats.py --branch-regex ".*" to include any branch, or python stats.py -v for search/skip diagnostics on stderr.

Exit codes: 0 success; 1 config/usage; 2 GitHub HTTP/network error after retries.

PR review follow-up (GitHub)

After maintainers comment on an open PR, dump review threads into a local markdown file for an agent (or you) to implement fixes. Uses gh only (no direct GitHub REST in this tool). It does not run git commit or git push — you (or the agent) must commit fixes and git push origin <pr-head-branch> so the PR updates. Install and authenticate GitHub CLI on the host.

Default output: <contribution-repo>/.iynx/pr-review-feedback.md — only if that path is gitignored in the target repo; otherwise pass --output or set IYNX_PR_REVIEW_FEEDBACK_PATH. Do not commit that file.

# From inside the contribution clone (branch can be set with gh pr checkout)
cd workspace/owner-repo
python ../pr_review.py https://github.com/owner/repo/pull/42

# Or explicit repo + number (from any cwd; use --output if not in a git repo)
python pr_review.py --repo owner/repo --pr 42 -o /tmp/pr-review-feedback.md

# PowerShell example with explicit output
python pr_review.py "https://github.com/owner/repo/pull/42" --output "$env:TEMP\pr-review-feedback.md"

Clone / checkout (normalize first): from the contribution repo, run git fetch and gh pr checkout <N> (same PR as your branch). If you have no clone yet, clone the head repository from gh pr view <url> --json headRepository,baseRepository, add upstream when the PR is from a fork, then gh pr checkout.

Exit codes: 0 file written; 1 usage or local validation (e.g. default path not gitignored); 2 gh/GitHub error.

See skills/issue-fix-workflow.md § PR review follow-up and docs/superpowers/specs/2026-03-24-pr-review-followup-design.md.

Environment Variables

Variable Required Description
CURSOR_API_KEY Yes Cursor CLI API key
GITHUB_TOKEN Yes* GitHub token (repo scope) for discovery and PRs
IYNX_PR_LABEL No If set, passed to gh pr create --label so PRs are tagged for stats.py filtering
IYNX_STATS_LABEL No Override label when running python stats.py (defaults to IYNX_PR_LABEL)
IYNX_STATS_NO_LABEL No If 1/true, same as python stats.py --no-label (author + branch only; no label in search)
IYNX_STATS_BRANCH_REGEX No Override branch regex for stats.py (default matches fix/issue-<n>)
IYNX_STATS_AUTHOR No GitHub login for stats.py (default: token’s user)
IYNX_PR_REVIEW_FEEDBACK_PATH No Output path for python pr_review.py when --output is omitted (same effect as --output; use to write without a local clone). For the default .iynx/pr-review-feedback.md path, you still need a repo root and a gitignored target unless you set this to a path outside the repo
IYNX_PROGRESS_JSONL No Path to JSONL progress file; empty/0/false disables the file
IYNX_DOCKER_TTY No If 1 (default), docker run -t for streamed steps so Cursor CLI output is line-buffered to the host; set 0 if -t fails (e.g. some CI)
IYNX_DOCKER_TRACE No If 1 (default), every Docker shell step prints [iynx-docker] timestamp lines (clone, bootstrap, cursor-agent, verify, PR) so docker logs / host [docker] lines show clear phases; set 0 to silence
IYNX_DOCKER_XTRACE No If 1, runs those shells with set -x (very noisy; for deep debugging only)
IYNX_CURSOR_OUTPUT_FORMAT No Cursor --print format: stream-json (default, NDJSON as the agent runs), json, or text (final answer only; quiet logs)
IYNX_CURSOR_STREAM_PARTIAL No With stream-json, if 1 (default) passes --stream-partial-output for smaller text deltas; set 0 to disable
IYNX_DOCKER_RUN_TIMEOUT No Max seconds per docker run (clone, each Cursor phase, etc.); default 3600. Raise for huge pnpm install / long agent runs; lower to fail fast
IYNX_CURSOR_PERMISSIVE No If 1 (default), passes --yolo, --approve-mcps, and --sandbox disabled to cursor-agent so headless runs do not stall on approvals (see Cursor CLI parameters). Set 0 for stricter behavior; phases that used --force still append it when permissive is off
IYNX_CURSOR_MODEL No Overrides the default Cursor model (composer-2 in code)
IYNX_CURSOR_EXTRA_ARGS No Extra cursor-agent flags (space-separated, POSIX-quoted), appended after built-in flags

Discovery rules (stars, age, pool size, CONTRIBUTING requirement, optional post-fix test re-run) live as constants in src/orchestrator.py — edit there to tune behavior; the Cursor model default can be overridden with IYNX_CURSOR_MODEL.

*Without GITHUB_TOKEN, discovery is rate-limited (60 req/hr) and PR creation will fail.

Project Structure

iynx/
├── Dockerfile           # Cursor CLI + gh + git + jq + Node.js 22; `USER dev` (UID 1000)
├── docker-compose.yml   # Optional local dev
├── src/
│   ├── orchestrator.py  # Main loop
│   ├── discovery.py     # GitHub Search API
│   ├── github_repo_checks.py  # CONTRIBUTING + author PR checks
│   ├── bootstrap.py    # Generate .cursor-agent per repo
│   ├── workflow_progress.py  # JSONL progress for agents
│   ├── pr_stats.py     # GitHub PR stats CLI (label + branch filter)
│   ├── pr_review_followup.py  # PR review → markdown (`gh` only)
│   └── pr.py           # Fork + push + gh pr create
├── stats.py            # Entry: PR statistics (`python stats.py`)
├── pr_review.py        # Entry: PR review feedback file (`python pr_review.py`)
├── skills/
│   └── issue-fix-workflow.md
├── tests/               # pytest (discovery + GitHub checks)
├── workspace/           # Mount point (gitignored)
├── .env.example
└── README.md

Flow

  1. Discovery: Search GitHub (defaults in orchestrator.py: e.g. stars, repo age, pool size), then keep repos that have a CONTRIBUTING file and none of your prior PRs to that repo.
  2. Pick one repo: One repo is chosen uniformly at random from the filtered list (so repeated runs are not stuck on the same top search hit).
  3. Issue preflight (host, GitHub API): Require at least one open issue (PRs excluded). If none, skip without cloning. This only checks that the list is non-empty.
  4. Clone: git clone inside Docker into workspace/owner-repo/.
  5. Bootstrap: Generate iynx.cursor-agent from repo structure (Node/Python/Rust).
  6. Phase 1: Cursor writes .iynx/summary.md and .iynx/context.json (test_command, lint_command) from the contribution guide.
  7. Phase 2 (issue selection): Cursor lists open issues (gh issue list), picks one it can handle (or declines), and writes .iynx/chosen-issue.json. The host validates the pick against the API. Override: pass issue number as argv[2] or IYNX_TARGET_ISSUE to skip this phase.
  8. Phase 3 (implement): Cursor implements the fix for the selected issue, runs tests, does not commit .iynx/.
  9. Verify (optional): If VERIFY_TESTS_AFTER_FIX is enabled in orchestrator.py, Docker re-runs test_command from context.json.
  10. Phase 4 (PR draft): Cursor writes .iynx/pr-draft.json (title, body).
  11. PR: Host writes .iynx/pr-body.md; gh repo fork, push, gh pr create --body-file ….

Contributing

See CONTRIBUTING.md for setup, tests, and PR guidelines.

Security

Report security issues as described in SECURITY.md.

Safety

  • Never run npm install, pytest, or any repo scripts on the host.
  • All clone, fix, and test execution happens inside the Docker container.
  • The host only performs HTTP (discovery), Docker commands, and file writes.

About

No description, website, or topics provided.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors