> built 2026-10-14 06:08 UTC from 639fe26 (main) · ge 0.1.18. Details: build_info.json

# index.html.md

<!-- generated by epythet -->

# ge — GitHub Engineering for AI Agents

Tools and skills that let AI coding agents work on GitHub issues, PRs, and discussions intelligently.

Most GitHub engineering *can* be done from an AI agent console with `gh` and Python. But some tasks — fetching an issue with all its comments, downloading screenshots, extracting video frames, checking if the issue is stale or already fixed — burn tokens every time, with a different improvised solution each time. `ge` crystallizes these into deterministic tools with CLI and Python interfaces. More importantly, it wraps them into **AI skills** that agents can use directly, so you just say “work on issue #42” and the agent knows exactly what to do.

So that’s the order here: skills first (the intended interface), then the tools underneath (for when you want direct control).

```bash
pip install ge
ge install-skills   # register skills with Claude Code
```

## Skills — the main interface

`ge` ships Claude Code skills that teach the agent how to work on GitHub issues. After installing the package, register them globally:

```bash
ge install-skills
```

This creates symlinks in `~/.claude/skills/` pointing to the skills bundled with `ge`. From then on, tell your agent “work on THIS” — where THIS can be:

- **A GitHub URL:** `work on https://github.com/owner/repo/issues/42`
- **A bare number:** `work on issue #42` (assumes the current repo)
- **A repo+number:** `work on owner/repo#42`
- **A pre-prepared context folder:** `work on ~/.cache/ge/owner/repo/issue_42`

The agent resolves whatever you give it, **confirms** what it understood before proceeding, then:

1. **Loads or prepares** full context (body, comments, media, cross-references)
2. **Analyzes freshness** — is it stale? already fixed? has related merged PRs?
3. **Describes images** via the Claude API (or asks you to paste them as fallback)
4. **Asks you** before working on ambiguous or likely-resolved issues
5. **Works on the issue** — creates a branch, makes changes, submits a PR

Six skills are installed:

| Skill                     | Purpose                                                                 |
|---------------------------|-------------------------------------------------------------------------|
| `ge`                      | Full workflow — resolve target, confirm, prepare context, analyze, work |
| `ge-analyze`              | Quick triage — check if an issue is worth working on                    |
| `ge-context`              | Context preparation — fetch and assemble structured documents           |
| `ge-autonomous-execution` | Behaviour policy for unattended runs (decide-and-log, don’t ask)        |
| `ge-roadmap-execution`    | Drive a roadmap issue end-to-end, one task per iteration                |
| `ge-cross-repo-triage`    | Classify & resolve issues across repos (failing test → fix → PR)        |
| `ge-write-issue`          | Authoring issues/comments people read — layering, asking, assigning     |

To remove the skills: `ge uninstall-skills`

### Installing skills with `gh skill`

The skills are also installable directly from GitHub with [`gh skill`](https://cli.github.com/manual/gh_skill) — no `pip install` required (you only need [the GitHub CLI, `gh`](https://cli.github.com/)). Install any single skill into Claude Code with:

```bash
gh skill install thorwhalen/ge <name> --agent claude-code
```

For example, `gh skill install thorwhalen/ge ge --agent claude-code` installs the main workflow skill.

| Skill                     | Description                                                                                                                                                                                                                                              |
|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `ge`                      | Use when asked to work on a GitHub issue, PR, or discussion. Resolves flexible input (URL, folder, number), confirms with the user, prepares context, and guides the workflow.                                                                           |
| `ge-analyze`              | Use when you need to check whether a GitHub issue or PR is still relevant, stale, or already resolved — without preparing full context or downloading media.                                                                                             |
| `ge-context`              | Use when you need to prepare, read, or refresh full context for a GitHub issue, PR, or discussion — fetching data, downloading media, and writing structured context documents.                                                                          |
| `ge-autonomous-execution` | Behavioural policy for running unattended. Use when launched in headless / auto-permission mode and there is no user available — converts every clarifying question into a logged decision so progress continues.                                        |
| `ge-roadmap-execution`    | Execute a roadmap of tasks end-to-end using GitHub as durable memory. Take the next todo task, do it, verify it, mark it done, log decisions, and repeat.                                                                                                |
| `ge-cross-repo-triage`    | Triage and resolve a cross-repo issue backlog — Phase A classifies and orders issues; Phase B executes them one at a time (failing test → confirm red → fix → confirm green → open PR).                                                                  |
| `ge-write-issue`          | Use when AUTHORING a GitHub issue, comment, or report that a human will read — especially a non-technical stakeholder or client. Covers layering detail with `<details>`, asking a question that gets answered, and assigning so someone is on the hook. |

## Autonomous execution

`ge` can drive Claude Code through a long body of work **without interrupting you**. State lives in GitHub (issues, comments) so each session can exit, the runner relaunches a fresh one, and progress resumes — context exhaustion is no longer a failure mode.

```bash
# Drive a roadmap issue to completion (one headless `claude -p` per task).
ge run-roadmap owner/repo <ROADMAP_ISSUE>

# Triage and resolve issues across several repos.
ge run-triage <TRACKING_REPO> <TRACKING_ISSUE> "owner/a,owner/b" --phase analyze
ge run-triage <TRACKING_REPO> <TRACKING_ISSUE> "owner/a,owner/b" --phase execute
```

The runner launches `claude` with `--permission-mode auto` by default; pass `--mode bypass` if you have explicitly authorised `--dangerously-skip-permissions`. Between iterations the session is *gone* — the next one rehydrates state from GitHub.

Each spawned session sees what the runner scopes for it. It starts in `--cwd` (default: the current directory), and that directory decides whose `CLAUDE.md` loads: give a package’s checkout. A cwd outside every git checkout (a projects root, a scratch working folder) loads none of the work’s instructions, so the runner warns about it. `--add-dirs a,b` adds directories the run must edit: their skills join the listing, but their `CLAUDE.md` files do not load, so the runner tells the session to Read them first. `--read-first` names further routing documents (relative to the cwd), `--skills` the skills to load by name, and `--model` / `--effort` pass through to `claude`. The JSON result carries the scope, for diagnosing a run later.

```bash
ge run-roadmap owner/repo 12 --cwd ~/proj/pkg --add-dirs ~/proj/lib --skills pkg-dev --model sonnet --effort high
```

### The memory model

| Store           | Backing                                                                                                                                                    | Use for                                           |
|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------|
| `RoadmapStore`  | A *roadmap issue* whose body holds a markdown task list between `<!-- ge:roadmap:begin -->` / `end`. Checkboxes render as GitHub’s task-list progress bar. | Roadmap of tasks; agent-modifiable.               |
| `DecisionLog`   | Tagged comments (`<!-- ge:decision -->`) on an issue or PR.                                                                                                | Significant decisions and course-corrections.     |
| `TriageBacklog` | A *tracking issue* whose body holds a JSON block keyed by `owner/repo#N`.                                                                                  | Cross-repo triage with explicit resolution order. |

GitHub is the single source of truth; the `.ge/cache/` snapshot is hydrate-only (gitignored).

```python
import ge

mall = ge.github_memory(
    "owner/repo",
    roadmap_issue=1,
    decisions_target=1,
    triage_issue=9,
)
roadmap = mall["roadmap"]
roadmap.next_todo()  # next "- [ ]" task
roadmap.append("New task")  # adds "- [ ] New task"

mall["decisions"].append("Used X over Y", rationale="cheaper")
mall["triage"]["owner/repoA#42"] = ge.TriageEntry(
    ref="owner/repoA#42",
    verdict=ge.TriageVerdict.fixable,
    order=1,
)
```

CLI equivalents: `ge roadmap-show`, `ge roadmap-next`, `ge roadmap-set`, `ge roadmap-append`, `ge decision-log`, `ge decisions-show`, `ge triage-set`, `ge triage-show`, `ge check-requirements`.

## Tools — what the skills use under the hood

Everything below is what the skills orchestrate automatically. You can use any of it directly via CLI or Python.

### What `ge prepare` does

Fetches an issue or PR and assembles **everything** an agent needs:

- **Issue/PR body and all comments** — with media URLs rewritten to local paths
- **Images downloaded** — screenshots, mockups, error captures (auto-detected from content)
- **Video frames extracted** — via ffmpeg scene detection
- **AI image descriptions** — images described via Claude API, embedded in the context document
- **Freshness analysis** — staleness, related merged PRs, resolution signals
- **Cross-references** — commits and PRs that mention this issue

All via `gh` CLI, so private repos just work.

### Quick start

```bash
ge prepare owner/repo --number 42
ge prepare https://github.com/owner/repo/pull/7
ge analyze-issue owner/repo 42
```

### Requirements

- **`gh` CLI** — installed and authenticated (`gh auth login`)
- **Python 3.10+**
- **`ffmpeg`** — optional, for video frame extraction
- **`anthropic`** — optional, for AI-powered image descriptions (`pip install anthropic` + set `ANTHROPIC_API_KEY`)
- **ImageMagick** — optional, for clipboard montage (`brew install imagemagick` on macOS)

### CLI commands

```default
ge prepare <repo> --number <N>              Full context (auto-detects issue/PR/discussion)
ge prepare <url>                           Full context from a GitHub URL
ge prepare-discussion <repo> --number <N>  Full context for a GitHub Discussion
ge analyze-issue <repo> <N>                Freshness analysis (JSON, no download)
ge analyze-pr <repo> <N>                   PR review analysis with CI status (JSON)
ge fetch-issue <repo> <N>                  Raw issue JSON
ge fetch-pr <repo> <N>                     Raw PR JSON
ge fetch-discussion <repo> <N>             GitHub Discussion JSON
ge media <file.md>                 Download media from markdown
ge video-frames <video>            Extract frames (scene detection by default)
ge describe-images <img>...        Describe images via Claude API (vision)
ge copy-images <img>...            Create montage + copy to clipboard (macOS)
ge resolve <target>                Resolve a URL, folder, or number to structured target
ge install-skills                  Register skills with Claude Code (~/.claude/skills/)
ge uninstall-skills                Remove ge skill symlinks
```

### Python API

```python
import ge

# Prepare full context
ctx = ge.prepare("https://github.com/owner/repo/issues/42")
ctx = ge.prepare_issue("owner/repo", 42)
ctx = ge.prepare_pr("owner/repo", 7)
ctx = ge.prepare_discussion("owner/repo", 5)

# Just analysis (no download)
analysis = ge.analyze_issue("owner/repo", 42)

# Resolve flexible input
target = ge.resolve_target("#42", current_repo="owner/repo")

# Image tools
from ge.media import describe_images, copy_images_to_clipboard

text = describe_images("screenshot.png", "error.jpg")
path = copy_images_to_clipboard("img1.png", "img2.png")
```

### Image analysis

When `ge prepare` runs, it downloads images and — if `anthropic` is installed — automatically sends them to the Claude API for visual analysis. The descriptions are embedded in the context document under “Image Descriptions (AI-generated)”.

```bash
pip install anthropic
export ANTHROPIC_API_KEY=sk-ant-...
```

To disable: `ge prepare owner/repo --number 42 -d`

Standalone:

```bash
ge describe-images screenshot.png error.jpg
ge describe-images frame1.jpg frame2.jpg --prompt "What changed between these frames?"
ge copy-images screenshot1.png screenshot2.png   # montage + clipboard (macOS)
```

## Project structure

```default
ge/
├── __init__.py      # Facade: prepare(), resolve_target(), install_skills(), etc.
├── __main__.py      # CLI via cw (SSOT: _cli_commands list)
├── github.py        # GitHub API via `gh` CLI subprocess
├── media.py         # Image download, video frames, describe_images, clipboard montage
├── analysis.py      # Staleness/freshness/relevance analysis
├── context.py       # Assembles everything into context docs
├── util.py          # Internal helpers: gh wrapper, URL parsing, resolve_target
├── memory.py        # GitHub-backed memory: RoadmapStore, DecisionLog, TriageBacklog
├── run.py           # Autonomous runner: launches `claude` in a loop
└── data/skills/     # Claude Code skills (symlinked by install-skills)
    ├── ge/                     # Full workflow skill
    ├── ge-analyze/             # Triage/staleness skill
    ├── ge-context/             # Context preparation skill
    ├── ge-autonomous-execution/  # Unattended-mode behaviour policy
    ├── ge-roadmap-execution/     # Drive a roadmap issue end-to-end
    ├── ge-cross-repo-triage/     # Multi-repo triage and resolution
    └── ge-write-issue/           # Authoring issues for human readers
```

<p class="epythet-aggregates">This documentation as a single file: <a href="ge.md">ge.md</a> (Markdown, for agents).</p>


# _autosummary/ge.analysis.html.md

# ge.analysis

Issue and PR analysis: staleness, relevance, and state assessment.

Examines an issue or PR to determine if it’s still relevant, already fixed,
or needs further investigation before work begins.

### Functions

| [`analyze_issue`](_autosummary/ge.analysis.html.md#ge.analysis.analyze_issue)(repo, number)                  | Analyze an issue for staleness and current relevance.                   |
|-----------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|
| [`analyze_pr`](_autosummary/ge.analysis.html.md#ge.analysis.analyze_pr)(repo, number)                     | Analyze a PR for review context and merge readiness.                    |
| [`check_referenced_files`](_autosummary/ge.analysis.html.md#ge.analysis.check_referenced_files)(repo, file_paths, \*) | Check if files mentioned in an issue still exist on the default branch. |

### ge.analysis.analyze_issue(repo, number)

Analyze an issue for staleness and current relevance.

Returns a dict with:

> - state: open/closed
> - age_days: days since creation
> - last_activity_days: days since last comment/event
> - labels: list of label names
> - related_prs: PRs that reference this issue
> - related_commits: commits mentioning this issue
> - referenced_files: files mentioned in the issue body
> - signals: list of human-readable observations about freshness
> - recommendation: ‘proceed’, ‘investigate’, or ‘likely_resolved’
```pycon
>>> # analysis = analyze_issue('owner/repo', 42)
```

### ge.analysis.analyze_pr(repo, number)

Analyze a PR for review context and merge readiness.

Returns a dict with state, review status, CI signals, conflicts, etc.

```pycon
>>> # analysis = analyze_pr('owner/repo', 7)
```

### ge.analysis.check_referenced_files(repo, file_paths, , ref=None)

Check if files mentioned in an issue still exist on the default branch.

Returns dict mapping path -> {‘exists’: bool, ‘snippet’: str | None}.

```pycon
>>> # status = check_referenced_files('owner/repo', ['src/main.py'])
```


# _autosummary/ge.context.html.md

# ge.context

Context assembly and rendering.

Combines issue/PR data, comments, media, and analysis into a single
structured context document that an AI agent can consume.

### Functions

| [`prepare_discussion`](_autosummary/ge.context.html.md#ge.context.prepare_discussion)(repo, number, \*[, ...])        | Prepare full context for a GitHub Discussion.                 |
|-----------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
| [`prepare_issue`](_autosummary/ge.context.html.md#ge.context.prepare_issue)(repo, number, \*[, output_dir, ...]) | Prepare full context for working on a GitHub issue.           |
| [`prepare_pr`](_autosummary/ge.context.html.md#ge.context.prepare_pr)(repo, number, \*[, output_dir, ...])    | Prepare full context for working on or reviewing a GitHub PR. |
| [`render_discussion_context`](_autosummary/ge.context.html.md#ge.context.render_discussion_context)(ctx)                     | Render discussion context as a structured markdown document.  |
| [`render_issue_context`](_autosummary/ge.context.html.md#ge.context.render_issue_context)(ctx)                          | Render issue context as a structured markdown document.       |
| [`render_pr_context`](_autosummary/ge.context.html.md#ge.context.render_pr_context)(ctx)                             | Render PR context as a structured markdown document.          |

### ge.context.prepare_discussion(repo, number, , output_dir=None, download_media_flag=True, describe_media=True)

Prepare full context for a GitHub Discussion.

Fetches the discussion and its comments via GraphQL, downloads media,
and writes a context document.

```pycon
>>> # ctx = prepare_discussion('owner/repo', 5)
```

### ge.context.prepare_issue(repo, number, , output_dir=None, download_media_flag=True, describe_media=True)

Prepare full context for working on a GitHub issue.

Fetches the issue, comments, media, and performs staleness analysis.
Writes a context document and manifest to output_dir.

When `describe_media` is True (default) and the `anthropic` package
is installed with `ANTHROPIC_API_KEY` set, downloaded images are sent
to the Claude API for automated description.  The description is included
in the context document so the agent has visual context without manual
image pasting.

Returns dict with all assembled context.

```pycon
>>> # ctx = prepare_issue('owner/repo', 42)
```

### ge.context.prepare_pr(repo, number, , output_dir=None, download_media_flag=True, include_diff=True, describe_media=True)

Prepare full context for working on or reviewing a GitHub PR.

```pycon
>>> # ctx = prepare_pr('owner/repo', 7)
```

### ge.context.render_discussion_context(ctx)

Render discussion context as a structured markdown document.

### ge.context.render_issue_context(ctx)

Render issue context as a structured markdown document.

### ge.context.render_pr_context(ctx)

Render PR context as a structured markdown document.


# _autosummary/ge.github.html.md

# ge.github

GitHub data fetching via gh CLI.

Provides functions to retrieve issues, PRs, comments, timeline events,
related commits, and referenced code – all through the gh CLI so that
private repo access is handled by the user’s existing gh auth.

### Functions

| [`find_related_commits`](_autosummary/ge.github.html.md#ge.github.find_related_commits)(repo, issue_number, \*)   | Find recent commits that reference this issue number.                   |
|-------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|
| [`find_related_prs`](_autosummary/ge.github.html.md#ge.github.find_related_prs)(repo, issue_number)           | Find PRs that reference or close this issue.                            |
| [`get_comments`](_autosummary/ge.github.html.md#ge.github.get_comments)(repo, number)                     | Fetch all comments on an issue or PR.                                   |
| [`get_commit_status`](_autosummary/ge.github.html.md#ge.github.get_commit_status)(repo, ref)                   | Fetch the combined commit status (CI checks) for a git ref.             |
| [`get_default_branch`](_autosummary/ge.github.html.md#ge.github.get_default_branch)(repo)                       | Get the default branch name for a repo.                                 |
| [`get_discussion`](_autosummary/ge.github.html.md#ge.github.get_discussion)(repo, number)                   | Fetch a GitHub Discussion via GraphQL (discussions aren't in REST API). |
| [`get_file_at_ref`](_autosummary/ge.github.html.md#ge.github.get_file_at_ref)(repo, path, \*[, ref])         | Fetch a file's content at a specific git ref (branch, tag, sha).        |
| [`get_issue`](_autosummary/ge.github.html.md#ge.github.get_issue)(repo, number)                        | Fetch a GitHub issue with body, labels, assignees, state, etc.          |
| [`get_pr`](_autosummary/ge.github.html.md#ge.github.get_pr)(repo, number)                           | Fetch a GitHub pull request with body, diff stats, merge state, etc.    |
| [`get_pr_diff`](_autosummary/ge.github.html.md#ge.github.get_pr_diff)(repo, number)                      | Fetch the raw diff of a PR.                                             |
| [`get_pr_files`](_autosummary/ge.github.html.md#ge.github.get_pr_files)(repo, number)                     | Fetch the list of files changed in a PR.                                |
| [`get_review_comments`](_autosummary/ge.github.html.md#ge.github.get_review_comments)(repo, number)              | Fetch inline review comments on a PR (code-level comments).             |
| [`get_reviews`](_autosummary/ge.github.html.md#ge.github.get_reviews)(repo, number)                      | Fetch PR reviews (approved, changes requested, etc.).                   |
| [`get_timeline`](_autosummary/ge.github.html.md#ge.github.get_timeline)(repo, number)                     | Fetch the issue/PR timeline (events: referenced, closed, labeled, ...). |

### ge.github.find_related_commits(repo, issue_number, , limit=20)

Find recent commits that reference this issue number.

```pycon
>>> # commits = find_related_commits('owner/repo', 42)
```

### ge.github.find_related_prs(repo, issue_number)

Find PRs that reference or close this issue.

Uses the timeline API to find cross-referenced PRs, and also searches
for PRs mentioning the issue number.

```pycon
>>> # prs = find_related_prs('owner/repo', 42)
```

### ge.github.get_comments(repo, number)

Fetch all comments on an issue or PR.

```pycon
>>> # comments = get_comments('owner/repo', 42)
```

### ge.github.get_commit_status(repo, ref)

Fetch the combined commit status (CI checks) for a git ref.

```pycon
>>> # status = get_commit_status('owner/repo', 'abc123')
```

### ge.github.get_default_branch(repo)

Get the default branch name for a repo.

```pycon
>>> # branch = get_default_branch('owner/repo')
```

### ge.github.get_discussion(repo, number)

Fetch a GitHub Discussion via GraphQL (discussions aren’t in REST API).

```pycon
>>> # disc = get_discussion('owner/repo', 5)
```

### ge.github.get_file_at_ref(repo, path, , ref='HEAD')

Fetch a file’s content at a specific git ref (branch, tag, sha).

```pycon
>>> # content = get_file_at_ref('owner/repo', 'src/main.py', ref='main')
```

### ge.github.get_issue(repo, number)

Fetch a GitHub issue with body, labels, assignees, state, etc.

```pycon
>>> # issue = get_issue('owner/repo', 42)
```

### ge.github.get_pr(repo, number)

Fetch a GitHub pull request with body, diff stats, merge state, etc.

```pycon
>>> # pr = get_pr('owner/repo', 7)
```

### ge.github.get_pr_diff(repo, number)

Fetch the raw diff of a PR.

```pycon
>>> # diff = get_pr_diff('owner/repo', 7)
```

### ge.github.get_pr_files(repo, number)

Fetch the list of files changed in a PR.

```pycon
>>> # files = get_pr_files('owner/repo', 7)
```

### ge.github.get_review_comments(repo, number)

Fetch inline review comments on a PR (code-level comments).

```pycon
>>> # review_comments = get_review_comments('owner/repo', 7)
```

### ge.github.get_reviews(repo, number)

Fetch PR reviews (approved, changes requested, etc.).

```pycon
>>> # reviews = get_reviews('owner/repo', 7)
```

### ge.github.get_timeline(repo, number)

Fetch the issue/PR timeline (events: referenced, closed, labeled, …).

This gives us cross-references, commits that mention the issue, etc.

```pycon
>>> # events = get_timeline('owner/repo', 42)
```


# _autosummary/ge.html.md

# ge

ge: GitHub Engineering tools for AI agents.

Reduces boilerplate when working on GitHub issues and PRs with AI coding agents.
Fetches context, downloads media, checks freshness, and assembles everything
into a structured document the agent can consume.

Usage:

```default
import ge

# Prepare full context for an issue
ctx = ge.prepare_issue('owner/repo', 42)

# Prepare full context for a PR
ctx = ge.prepare_pr('owner/repo', 7)

# Or from a full URL
ctx = ge.prepare('https://github.com/owner/repo/issues/42')
```

### Functions

| [`prepare`](_autosummary/ge.html.md#ge.prepare)(url_or_spec[, number, output_dir, ...])    | Prepare context from a GitHub URL or repo+number.                       |
|-----------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------|
| [`prepare_issue`](_autosummary/ge.html.md#ge.prepare_issue)(repo, number, \*[, output_dir, ...]) | Prepare full context for working on a GitHub issue.                     |
| [`prepare_pr`](_autosummary/ge.html.md#ge.prepare_pr)(repo, number, \*[, output_dir, ...])    | Prepare full context for working on or reviewing a GitHub PR.           |
| [`prepare_discussion`](_autosummary/ge.html.md#ge.prepare_discussion)(repo, number, \*[, ...])        | Prepare full context for a GitHub Discussion.                           |
| [`analyze_issue`](_autosummary/ge.html.md#ge.analyze_issue)(repo, number)                        | Analyze an issue for staleness and current relevance.                   |
| [`analyze_pr`](_autosummary/ge.html.md#ge.analyze_pr)(repo, number)                           | Analyze a PR for review context and merge readiness.                    |
| [`get_issue`](_autosummary/ge.html.md#ge.get_issue)(repo, number)                            | Fetch a GitHub issue with body, labels, assignees, state, etc.          |
| [`get_pr`](_autosummary/ge.html.md#ge.get_pr)(repo, number)                               | Fetch a GitHub pull request with body, diff stats, merge state, etc.    |
| [`get_comments`](_autosummary/ge.html.md#ge.get_comments)(repo, number)                         | Fetch all comments on an issue or PR.                                   |
| [`get_pr_diff`](_autosummary/ge.html.md#ge.get_pr_diff)(repo, number)                          | Fetch the raw diff of a PR.                                             |
| [`get_discussion`](_autosummary/ge.html.md#ge.get_discussion)(repo, number)                       | Fetch a GitHub Discussion via GraphQL (discussions aren't in REST API). |
| [`find_related_prs`](_autosummary/ge.html.md#ge.find_related_prs)(repo, issue_number)               | Find PRs that reference or close this issue.                            |
| [`find_related_commits`](_autosummary/ge.html.md#ge.find_related_commits)(repo, issue_number, \*)       | Find recent commits that reference this issue number.                   |
| [`process_all_media`](_autosummary/ge.html.md#ge.process_all_media)(markdown[, output_dir])          | Download all media and extract video frames.                            |
| [`extract_video_frames`](_autosummary/ge.html.md#ge.extract_video_frames)(video_path, \*[, ...])        | Extract representative frames from a video file using ffmpeg.           |
| [`describe_images`](_autosummary/ge.html.md#ge.describe_images)(\*image_paths[, prompt, ...])      | Use the Claude API to describe images, returning a text description.    |
| [`copy_images_to_clipboard`](_autosummary/ge.html.md#ge.copy_images_to_clipboard)(\*image_paths[, ...])     | Create a montage of images and copy it to the macOS clipboard.          |
| [`resolve_target`](_autosummary/ge.html.md#ge.resolve_target)(user_input, \*[, current_repo])     | Resolve flexible user input into a structured target for ge workflows.  |
| [`install_skills`](_autosummary/ge.html.md#ge.install_skills)(\*[, target_dir])                   | Symlink ge skills into ~/.claude/skills/ for global discovery.          |
| [`uninstall_skills`](_autosummary/ge.html.md#ge.uninstall_skills)(\*[, target_dir])                 | Remove ge skill symlinks from ~/.claude/skills/.                        |
| [`github_memory`](_autosummary/ge.html.md#ge.github_memory)(repo, \*[, roadmap_issue, ...])      | Bundle the three stores into a "mall" dict keyed by name.               |
| [`check_requirements`](_autosummary/ge.html.md#ge.check_requirements)(\*[, require_project_scope])    | Verify external prerequisites for the memory layer.                     |

### Classes

| [`RoadmapStore`](_autosummary/ge.html.md#ge.RoadmapStore)(repo, issue_number, \*[, cache_dir])   | A `MutableMapping[task_id, TaskRecord]` over a roadmap issue.                              |
|------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
| [`DecisionLog`](_autosummary/ge.html.md#ge.DecisionLog)(repo, target_number, \*[, cache_dir])   | Append-only iterable of [`Decision`](_autosummary/ge.html.md#ge.Decision) records. |
| [`TriageBacklog`](_autosummary/ge.html.md#ge.TriageBacklog)(repo, tracking_issue, \*[, ...])      | Cross-repo triage backlog.                                                                 |
| [`TaskState`](_autosummary/ge.html.md#ge.TaskState)(\*values)                                 | Roadmap task lifecycle state.                                                              |
| [`TaskRecord`](_autosummary/ge.html.md#ge.TaskRecord)(id, title[, state, metadata])            | A single roadmap task.                                                                     |
| [`TriageVerdict`](_autosummary/ge.html.md#ge.TriageVerdict)(\*values)                             | Triage classification for an issue.                                                        |
| [`TriageEntry`](_autosummary/ge.html.md#ge.TriageEntry)(ref, verdict[, order, ...])             | A cross-repo triage backlog entry.                                                         |
| [`Decision`](_autosummary/ge.html.md#ge.Decision)(summary[, rationale, metadata, ...])       | A logged decision (rendered as a tagged comment on an issue/PR).                           |

### *class* ge.Decision(summary, rationale='', metadata=<factory>, created_at=None, url=None)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

A logged decision (rendered as a tagged comment on an issue/PR).

### *class* ge.DecisionLog(repo, target_number, , cache_dir=None)

Bases: [`Iterable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable)

Append-only iterable of [`Decision`](_autosummary/ge.html.md#ge.Decision) records.

Backed by issue/PR comments tagged with `<!-- ge:decision -->` on a
single target (issue or PR). Iteration fetches all comments from
GitHub, filters by tag, and yields parsed decisions in chronological
order.

Significant choices and course-corrections should be appended here so
they survive into future sessions. Micro-decisions belong in local
scratch and should be summarised at handover.

#### append(summary, , rationale='', metadata=None)

Post a tagged comment recording this decision.

Returns the [`Decision`](_autosummary/ge.html.md#ge.Decision) (with `url` and `created_at`
populated from the GitHub response on success).

* **Return type:**
  [`Decision`](_autosummary/ge.memory.html.md#ge.memory.Decision)

### *class* ge.RoadmapStore(repo, issue_number, , cache_dir=None)

Bases: [`MutableMapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableMapping)

A `MutableMapping[task_id, TaskRecord]` over a roadmap issue.

Iteration yields task ids in roadmap order. Mutations rewrite the
roadmap issue body in place; the local cache is updated on success.

Initialise via [`github_memory()`](_autosummary/ge.html.md#ge.github_memory) or directly with `(repo, issue)`.
The roadmap issue body should contain (or will be augmented with) a
block:

```default
<!-- ge:roadmap:begin -->
- [ ] Task one
- [x] Task two
<!-- ge:roadmap:end -->
```

Tasks outside the block are ignored.

#### append(title, , state=TaskState.todo)

Add a new task to the end of the roadmap.

* **Return type:**
  [`TaskRecord`](_autosummary/ge.memory.html.md#ge.memory.TaskRecord)

#### hydrate()

Refresh the local cache from GitHub and return the current task list.

* **Return type:**
  [`list`](https://docs.python.org/3/builtins/stdtypes.html#list)[[`TaskRecord`](_autosummary/ge.memory.html.md#ge.memory.TaskRecord)]

#### next_todo()

Return the first `todo` task in roadmap order, or None.

* **Return type:**
  [`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`TaskRecord`](_autosummary/ge.memory.html.md#ge.memory.TaskRecord)]

### *class* ge.TaskRecord(id, title, state=TaskState.todo, metadata=<factory>)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

A single roadmap task.

`id` is a stable slug derived from the title. `metadata` holds any
extra agent-attached fields (e.g. links to PRs).

### *class* ge.TaskState(\*values)

Bases: [`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Enum`](https://docs.python.org/3/library/enum.html#enum.Enum)

Roadmap task lifecycle state.

### *class* ge.TriageBacklog(repo, tracking_issue, , cache_dir=None)

Bases: [`MutableMapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableMapping)

Cross-repo triage backlog.

Keys are issue refs of the form `"owner/repo#N"`; values are
[`TriageEntry`](_autosummary/ge.html.md#ge.TriageEntry). Iteration order is the agent-chosen resolution
order (the `order` field).

Backed by a *tracking issue* whose body holds a JSON block between
`<!-- ge:triage:begin -->` / `end`. This keeps the implementation
free of GraphQL/Project dependencies while preserving full cross-repo
semantics — fully-qualified refs do the heavy lifting.

Switching to a GitHub Project backend later is a swap of this class’s
I/O methods; the public interface stays the same.

### *class* ge.TriageEntry(ref, verdict, order=0, rationale='', metadata=<factory>)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

A cross-repo triage backlog entry.

### *class* ge.TriageVerdict(\*values)

Bases: [`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Enum`](https://docs.python.org/3/library/enum.html#enum.Enum)

Triage classification for an issue.

### ge.analyze_issue(repo, number)

Analyze an issue for staleness and current relevance.

Returns a dict with:

> - state: open/closed
> - age_days: days since creation
> - last_activity_days: days since last comment/event
> - labels: list of label names
> - related_prs: PRs that reference this issue
> - related_commits: commits mentioning this issue
> - referenced_files: files mentioned in the issue body
> - signals: list of human-readable observations about freshness
> - recommendation: ‘proceed’, ‘investigate’, or ‘likely_resolved’
```pycon
>>> # analysis = analyze_issue('owner/repo', 42)
```

### ge.analyze_pr(repo, number)

Analyze a PR for review context and merge readiness.

Returns a dict with state, review status, CI signals, conflicts, etc.

```pycon
>>> # analysis = analyze_pr('owner/repo', 7)
```

### ge.check_requirements(, require_project_scope=False)

Verify external prerequisites for the memory layer.

Returns a dict `{"ok": bool, "gh": ..., "auth": ..., "scopes": [...],
"missing": [...]}`. Raises `EnvironmentError` with actionable
install instructions if a hard requirement is missing.

Set `require_project_scope=True` if you plan to swap the triage
backend to a GitHub Project.

* **Return type:**
  [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)

### ge.copy_images_to_clipboard(\*image_paths, tile='3x', geometry='800x600+10+10', montage_path=None)

Create a montage of images and copy it to the macOS clipboard.

Combines multiple images into a single grid image using ImageMagick,
then copies it to the clipboard via `osascript` so the user can
paste it into Claude Code with Cmd+V.

* **Parameters:**
  * **\*image_paths** – Paths to image files to combine.
  * **tile** – Montage tile layout (e.g. ‘3x’ for 3 columns).
  * **geometry** – Tile geometry (size+padding).
  * **montage_path** – Where to save the montage file. Defaults to
    `/tmp/ge_montage.png`.
* **Returns:**
  Path to the montage file.
* **Return type:**
  [*str*](https://docs.python.org/3/builtins/stdtypes.html#str)

```pycon
>>> # path = copy_images_to_clipboard('img1.png', 'img2.png')
```

### ge.describe_images(\*image_paths, prompt='Describe what you see in these images in detail. If they appear to be screenshots of a bug or UI issue, describe the problem visible.', model='claude-sonnet-4-5-20250514', max_tokens=4096)

Use the Claude API to describe images, returning a text description.

Requires the `anthropic` package and a valid `ANTHROPIC_API_KEY`.

* **Parameters:**
  * **\*image_paths** – One or more paths to image files.
  * **prompt** – The text prompt sent alongside the images.
  * **model** – Claude model to use for vision analysis.
  * **max_tokens** – Maximum tokens in the response.
* **Returns:**
  The model’s textual description of the images.
* **Return type:**
  [*str*](https://docs.python.org/3/builtins/stdtypes.html#str)

```pycon
>>> # description = describe_images('screenshot.png', 'error.jpg')
```

### ge.extract_video_frames(video_path, , n_frames=5, output_dir=None, mode='scene', scene_threshold=0.3)

Extract representative frames from a video file using ffmpeg.

Two modes are available:

- `mode='scene'` (default): Uses ffmpeg’s scene change detection filter
  to extract frames where visual content actually changes. Better for bug
  reproduction videos where you want one frame per distinct UI state.
  `scene_threshold` controls sensitivity (0.0–1.0, lower = more frames).
  Falls back to ‘uniform’ if scene detection yields no frames.
- `mode='uniform'`: Extracts `n_frames` evenly-spaced frames.

Returns list of paths to extracted frame images (JPEG).

```pycon
>>> # frames = extract_video_frames('demo.mp4')
>>> # frames = extract_video_frames('demo.mp4', mode='uniform', n_frames=5)
```

### ge.find_related_commits(repo, issue_number, , limit=20)

Find recent commits that reference this issue number.

```pycon
>>> # commits = find_related_commits('owner/repo', 42)
```

### ge.find_related_prs(repo, issue_number)

Find PRs that reference or close this issue.

Uses the timeline API to find cross-referenced PRs, and also searches
for PRs mentioning the issue number.

```pycon
>>> # prs = find_related_prs('owner/repo', 42)
```

### ge.get_comments(repo, number)

Fetch all comments on an issue or PR.

```pycon
>>> # comments = get_comments('owner/repo', 42)
```

### ge.get_discussion(repo, number)

Fetch a GitHub Discussion via GraphQL (discussions aren’t in REST API).

```pycon
>>> # disc = get_discussion('owner/repo', 5)
```

### ge.get_issue(repo, number)

Fetch a GitHub issue with body, labels, assignees, state, etc.

```pycon
>>> # issue = get_issue('owner/repo', 42)
```

### ge.get_pr(repo, number)

Fetch a GitHub pull request with body, diff stats, merge state, etc.

```pycon
>>> # pr = get_pr('owner/repo', 7)
```

### ge.get_pr_diff(repo, number)

Fetch the raw diff of a PR.

```pycon
>>> # diff = get_pr_diff('owner/repo', 7)
```

### ge.github_memory(repo, , roadmap_issue=None, decisions_target=None, triage_issue=None, cache_dir=None)

Bundle the three stores into a “mall” dict keyed by name.

Each store is only constructed if its target was provided. `roadmap`
requires `roadmap_issue`; `decisions` requires `decisions_target`
(the issue or PR number where decisions are logged); `triage`
requires `triage_issue` (the tracking issue holding the JSON block).

* **Return type:**
  [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`object`](https://docs.python.org/3/builtins/functions.html#object)]

```pycon
>>> # mall = github_memory('owner/repo', roadmap_issue=1, decisions_target=1)
```

### ge.install_skills(, target_dir=None)

Symlink ge skills into ~/.claude/skills/ for global discovery.

Creates symlinks from `target_dir/<skill>` to the skill folders bundled
with the ge package.  Existing correct symlinks are left alone; a warning
is printed if a symlink points elsewhere.

* **Parameters:**
  **target_dir** – Destination directory.  Defaults to `~/.claude/skills/`.

### ge.prepare(url_or_spec, number=None, , output_dir=None, describe_media=True, \*\*kwargs)

Prepare context from a GitHub URL or repo+number.

Automatically detects whether it’s an issue, PR, or discussion.

When `describe_media` is True (default) and the `anthropic` package
is available, downloaded images are described via the Claude API so
the agent gets visual context without manual image pasting.

```pycon
>>> # ctx = ge.prepare('https://github.com/owner/repo/issues/42')
>>> # ctx = ge.prepare('owner/repo', 42)
>>> # ctx = ge.prepare('https://github.com/owner/repo/discussions/5')
```

### ge.prepare_discussion(repo, number, , output_dir=None, download_media_flag=True, describe_media=True)

Prepare full context for a GitHub Discussion.

Fetches the discussion and its comments via GraphQL, downloads media,
and writes a context document.

```pycon
>>> # ctx = prepare_discussion('owner/repo', 5)
```

### ge.prepare_issue(repo, number, , output_dir=None, download_media_flag=True, describe_media=True)

Prepare full context for working on a GitHub issue.

Fetches the issue, comments, media, and performs staleness analysis.
Writes a context document and manifest to output_dir.

When `describe_media` is True (default) and the `anthropic` package
is installed with `ANTHROPIC_API_KEY` set, downloaded images are sent
to the Claude API for automated description.  The description is included
in the context document so the agent has visual context without manual
image pasting.

Returns dict with all assembled context.

```pycon
>>> # ctx = prepare_issue('owner/repo', 42)
```

### ge.prepare_pr(repo, number, , output_dir=None, download_media_flag=True, include_diff=True, describe_media=True)

Prepare full context for working on or reviewing a GitHub PR.

```pycon
>>> # ctx = prepare_pr('owner/repo', 7)
```

### ge.process_all_media(markdown, output_dir=None)

Download all media and extract video frames.

Returns a dict with:

> - url_map, manifest, rewritten_markdown (from download_media)
> - video_frames: {video_local_path: [frame_paths]}
> - images: list of local image paths (for user to paste into agent)
> - all_visual_files: combined list of images + video frames
```pycon
>>> # result = process_all_media(issue_body)
```

### ge.resolve_target(user_input, , current_repo=None)

Resolve flexible user input into a structured target for ge workflows.

Accepts any of these forms:

> - A GitHub URL: `https://github.com/owner/repo/issues/42`
> - A folder path to pre-prepared context: `~/.cache/ge/owner/repo/issue_42`
> - A bare number: `42` or `#42` (uses *current_repo*)
> - `owner/repo#42` or `owner/repo/42`
> - `owner/repo 42`

Returns a dict with:

> - `repo`: `"owner/repo"` (or None if only a folder was given)
> - `number`: int
> - `kind`: `"issue"` | `"pr"` | `"discussion"` | None
> - `context_dir`: path to pre-prepared context (str or None)
> - `context_md`: path to the context markdown file (str or None)
> - `has_prepared_context`: bool — whether context files already exist
> - `source`: how the input was resolved (`"url"`, `"folder"`,
>   `"number"`, `"repo_number"`)
```pycon
>>> r = resolve_target('https://github.com/owner/repo/issues/42')
>>> r['repo'], r['number'], r['kind'], r['source']
('owner/repo', 42, 'issue', 'url')
```

```pycon
>>> r = resolve_target('#42', current_repo='owner/repo')
>>> r['repo'], r['number'], r['source']
('owner/repo', 42, 'number')
```

```pycon
>>> r = resolve_target('owner/repo#42')
>>> r['repo'], r['number'], r['source']
('owner/repo', 42, 'repo_number')
```

### ge.uninstall_skills(, target_dir=None)

Remove ge skill symlinks from ~/.claude/skills/.

Only removes symlinks that point back into the ge package’s skill
directory.  Non-symlinks and symlinks owned by other packages are
left untouched.

* **Parameters:**
  **target_dir** – Directory to remove from.  Defaults to `~/.claude/skills/`.

### Modules

| [`analysis`](_autosummary/ge.analysis.html.md#module-ge.analysis)   | Issue and PR analysis: staleness, relevance, and state assessment.   |
|--------------------------------------------------------------------------------|----------------------------------------------------------------------|
| [`context`](_autosummary/ge.context.html.md#module-ge.context)     | Context assembly and rendering.                                      |
| [`github`](_autosummary/ge.github.html.md#module-ge.github)       | GitHub data fetching via gh CLI.                                     |
| [`media`](_autosummary/ge.media.html.md#module-ge.media)         | Media download and video frame extraction.                           |
| [`memory`](_autosummary/ge.memory.html.md#module-ge.memory)       | GitHub-backed durable memory for autonomous agent execution.         |
| [`run`](_autosummary/ge.run.html.md#module-ge.run)             | Autonomous-execution runner — Layer 3.                               |
| [`util`](_autosummary/ge.util.html.md#module-ge.util)           | Internal utilities: gh CLI wrapper, subprocess helpers, URL parsing. |


# _autosummary/ge.media.html.md

# ge.media

Media download and video frame extraction.

Downloads images and videos from GitHub issues/PRs, handling private repo
auth via gh token. Extracts representative frames from videos using ffmpeg.
Provides AI-powered image description and clipboard montage utilities.

### Functions

| [`copy_images_to_clipboard`](_autosummary/ge.media.html.md#ge.media.copy_images_to_clipboard)(\*image_paths[, ...])   | Create a montage of images and copy it to the macOS clipboard.       |
|---------------------------------------------------------------------------------------------------|----------------------------------------------------------------------|
| [`describe_images`](_autosummary/ge.media.html.md#ge.media.describe_images)(\*image_paths[, prompt, ...])    | Use the Claude API to describe images, returning a text description. |
| [`download_media`](_autosummary/ge.media.html.md#ge.media.download_media)(markdown[, output_dir, ...])      | Download all media referenced in markdown to a local directory.      |
| [`extract_video_frames`](_autosummary/ge.media.html.md#ge.media.extract_video_frames)(video_path, \*[, ...])      | Extract representative frames from a video file using ffmpeg.        |
| [`process_all_media`](_autosummary/ge.media.html.md#ge.media.process_all_media)(markdown[, output_dir])        | Download all media and extract video frames.                         |

### ge.media.copy_images_to_clipboard(\*image_paths, tile='3x', geometry='800x600+10+10', montage_path=None)

Create a montage of images and copy it to the macOS clipboard.

Combines multiple images into a single grid image using ImageMagick,
then copies it to the clipboard via `osascript` so the user can
paste it into Claude Code with Cmd+V.

* **Parameters:**
  * **\*image_paths** – Paths to image files to combine.
  * **tile** – Montage tile layout (e.g. ‘3x’ for 3 columns).
  * **geometry** – Tile geometry (size+padding).
  * **montage_path** – Where to save the montage file. Defaults to
    `/tmp/ge_montage.png`.
* **Returns:**
  Path to the montage file.
* **Return type:**
  [*str*](https://docs.python.org/3/builtins/stdtypes.html#str)

```pycon
>>> # path = copy_images_to_clipboard('img1.png', 'img2.png')
```

### ge.media.describe_images(\*image_paths, prompt='Describe what you see in these images in detail. If they appear to be screenshots of a bug or UI issue, describe the problem visible.', model='claude-sonnet-4-5-20250514', max_tokens=4096)

Use the Claude API to describe images, returning a text description.

Requires the `anthropic` package and a valid `ANTHROPIC_API_KEY`.

* **Parameters:**
  * **\*image_paths** – One or more paths to image files.
  * **prompt** – The text prompt sent alongside the images.
  * **model** – Claude model to use for vision analysis.
  * **max_tokens** – Maximum tokens in the response.
* **Returns:**
  The model’s textual description of the images.
* **Return type:**
  [*str*](https://docs.python.org/3/builtins/stdtypes.html#str)

```pycon
>>> # description = describe_images('screenshot.png', 'error.jpg')
```

### ge.media.download_media(markdown, output_dir=None, , download_images=True, download_videos=True)

Download all media referenced in markdown to a local directory.

Returns a dict with:

> - url_map: {original_url: local_path} for successful downloads
> - manifest: list of dicts with url, local_path, kind, alt, status
> - rewritten_markdown: markdown with URLs replaced by local paths
```pycon
>>> # result = download_media('![img](https://example.com/a.png)', '/tmp/media')
```

### ge.media.extract_video_frames(video_path, , n_frames=5, output_dir=None, mode='scene', scene_threshold=0.3)

Extract representative frames from a video file using ffmpeg.

Two modes are available:

- `mode='scene'` (default): Uses ffmpeg’s scene change detection filter
  to extract frames where visual content actually changes. Better for bug
  reproduction videos where you want one frame per distinct UI state.
  `scene_threshold` controls sensitivity (0.0–1.0, lower = more frames).
  Falls back to ‘uniform’ if scene detection yields no frames.
- `mode='uniform'`: Extracts `n_frames` evenly-spaced frames.

Returns list of paths to extracted frame images (JPEG).

```pycon
>>> # frames = extract_video_frames('demo.mp4')
>>> # frames = extract_video_frames('demo.mp4', mode='uniform', n_frames=5)
```

### ge.media.process_all_media(markdown, output_dir=None)

Download all media and extract video frames.

Returns a dict with:

> - url_map, manifest, rewritten_markdown (from download_media)
> - video_frames: {video_local_path: [frame_paths]}
> - images: list of local image paths (for user to paste into agent)
> - all_visual_files: combined list of images + video frames
```pycon
>>> # result = process_all_media(issue_body)
```


# _autosummary/ge.memory.html.md

# ge.memory

GitHub-backed durable memory for autonomous agent execution.

Three stores exposed as `collections.abc` facades. GitHub is the single
source of truth; a thin per-store local snapshot under `.ge/cache/` is a
hydrate-only cache (disposable, never authoritative).

- [`RoadmapStore`](_autosummary/ge.memory.html.md#ge.memory.RoadmapStore) — `MutableMapping[task_id, TaskRecord]` over a
  *roadmap issue* whose body holds a markdown task list between
  `<!-- ge:roadmap:begin -->` / `<!-- ge:roadmap:end -->` markers.
- [`DecisionLog`](_autosummary/ge.memory.html.md#ge.memory.DecisionLog) — append-only `Iterable[Decision]` backed by
  decision-tagged comments on an issue or PR.
- [`TriageBacklog`](_autosummary/ge.memory.html.md#ge.memory.TriageBacklog) — `MutableMapping[issue_ref, TriageVerdict]`
  spanning multiple repos. Backed by a tracking issue containing a fenced
  JSON block (`<!-- ge:triage:begin -->` / `end`). Cross-repo refs use
  `"owner/repo#N"`.

The [`github_memory()`](_autosummary/ge.memory.html.md#ge.memory.github_memory) factory bundles these into a mall.

## Design notes

- Writes go to GitHub *first*; the local cache is updated as a side-effect on
  successful round-trip. A failed write does **not** poison the cache.
- `check_requirements` verifies `gh` is installed, authenticated, and
  carries the `project` scope (needed if the user later switches the
  triage backend to a GitHub Project).

### Functions

| [`github_memory`](_autosummary/ge.memory.html.md#ge.memory.github_memory)(repo, \*[, roadmap_issue, ...])   | Bundle the three stores into a "mall" dict keyed by name.   |
|--------------------------------------------------------------------------------------------------|-------------------------------------------------------------|
| [`check_requirements`](_autosummary/ge.memory.html.md#ge.memory.check_requirements)(\*[, require_project_scope]) | Verify external prerequisites for the memory layer.         |

### Classes

| [`TaskState`](_autosummary/ge.memory.html.md#ge.memory.TaskState)(\*values)                               | Roadmap task lifecycle state.                                                              |
|----------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
| [`TaskRecord`](_autosummary/ge.memory.html.md#ge.memory.TaskRecord)(id, title[, state, metadata])          | A single roadmap task.                                                                     |
| [`TriageVerdict`](_autosummary/ge.memory.html.md#ge.memory.TriageVerdict)(\*values)                           | Triage classification for an issue.                                                        |
| [`TriageEntry`](_autosummary/ge.memory.html.md#ge.memory.TriageEntry)(ref, verdict[, order, ...])           | A cross-repo triage backlog entry.                                                         |
| [`Decision`](_autosummary/ge.memory.html.md#ge.memory.Decision)(summary[, rationale, metadata, ...])     | A logged decision (rendered as a tagged comment on an issue/PR).                           |
| [`RoadmapStore`](_autosummary/ge.memory.html.md#ge.memory.RoadmapStore)(repo, issue_number, \*[, cache_dir]) | A `MutableMapping[task_id, TaskRecord]` over a roadmap issue.                              |
| [`DecisionLog`](_autosummary/ge.memory.html.md#ge.memory.DecisionLog)(repo, target_number, \*[, cache_dir]) | Append-only iterable of [`Decision`](_autosummary/ge.memory.html.md#ge.memory.Decision) records. |
| [`TriageBacklog`](_autosummary/ge.memory.html.md#ge.memory.TriageBacklog)(repo, tracking_issue, \*[, ...])    | Cross-repo triage backlog.                                                                 |

### *class* ge.memory.Decision(summary, rationale='', metadata=<factory>, created_at=None, url=None)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

A logged decision (rendered as a tagged comment on an issue/PR).

### *class* ge.memory.DecisionLog(repo, target_number, , cache_dir=None)

Bases: [`Iterable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable)

Append-only iterable of [`Decision`](_autosummary/ge.memory.html.md#ge.memory.Decision) records.

Backed by issue/PR comments tagged with `<!-- ge:decision -->` on a
single target (issue or PR). Iteration fetches all comments from
GitHub, filters by tag, and yields parsed decisions in chronological
order.

Significant choices and course-corrections should be appended here so
they survive into future sessions. Micro-decisions belong in local
scratch and should be summarised at handover.

#### append(summary, , rationale='', metadata=None)

Post a tagged comment recording this decision.

Returns the [`Decision`](_autosummary/ge.memory.html.md#ge.memory.Decision) (with `url` and `created_at`
populated from the GitHub response on success).

* **Return type:**
  [`Decision`](_autosummary/ge.memory.html.md#ge.memory.Decision)

### *class* ge.memory.RoadmapStore(repo, issue_number, , cache_dir=None)

Bases: [`MutableMapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableMapping)

A `MutableMapping[task_id, TaskRecord]` over a roadmap issue.

Iteration yields task ids in roadmap order. Mutations rewrite the
roadmap issue body in place; the local cache is updated on success.

Initialise via [`github_memory()`](_autosummary/ge.memory.html.md#ge.memory.github_memory) or directly with `(repo, issue)`.
The roadmap issue body should contain (or will be augmented with) a
block:

```default
<!-- ge:roadmap:begin -->
- [ ] Task one
- [x] Task two
<!-- ge:roadmap:end -->
```

Tasks outside the block are ignored.

#### append(title, , state=TaskState.todo)

Add a new task to the end of the roadmap.

* **Return type:**
  [`TaskRecord`](_autosummary/ge.memory.html.md#ge.memory.TaskRecord)

#### hydrate()

Refresh the local cache from GitHub and return the current task list.

* **Return type:**
  [`list`](https://docs.python.org/3/builtins/stdtypes.html#list)[[`TaskRecord`](_autosummary/ge.memory.html.md#ge.memory.TaskRecord)]

#### next_todo()

Return the first `todo` task in roadmap order, or None.

* **Return type:**
  [`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`TaskRecord`](_autosummary/ge.memory.html.md#ge.memory.TaskRecord)]

### *class* ge.memory.TaskRecord(id, title, state=TaskState.todo, metadata=<factory>)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

A single roadmap task.

`id` is a stable slug derived from the title. `metadata` holds any
extra agent-attached fields (e.g. links to PRs).

### *class* ge.memory.TaskState(\*values)

Bases: [`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Enum`](https://docs.python.org/3/library/enum.html#enum.Enum)

Roadmap task lifecycle state.

### *class* ge.memory.TriageBacklog(repo, tracking_issue, , cache_dir=None)

Bases: [`MutableMapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableMapping)

Cross-repo triage backlog.

Keys are issue refs of the form `"owner/repo#N"`; values are
[`TriageEntry`](_autosummary/ge.memory.html.md#ge.memory.TriageEntry). Iteration order is the agent-chosen resolution
order (the `order` field).

Backed by a *tracking issue* whose body holds a JSON block between
`<!-- ge:triage:begin -->` / `end`. This keeps the implementation
free of GraphQL/Project dependencies while preserving full cross-repo
semantics — fully-qualified refs do the heavy lifting.

Switching to a GitHub Project backend later is a swap of this class’s
I/O methods; the public interface stays the same.

### *class* ge.memory.TriageEntry(ref, verdict, order=0, rationale='', metadata=<factory>)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

A cross-repo triage backlog entry.

### *class* ge.memory.TriageVerdict(\*values)

Bases: [`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Enum`](https://docs.python.org/3/library/enum.html#enum.Enum)

Triage classification for an issue.

### ge.memory.check_requirements(, require_project_scope=False)

Verify external prerequisites for the memory layer.

Returns a dict `{"ok": bool, "gh": ..., "auth": ..., "scopes": [...],
"missing": [...]}`. Raises `EnvironmentError` with actionable
install instructions if a hard requirement is missing.

Set `require_project_scope=True` if you plan to swap the triage
backend to a GitHub Project.

* **Return type:**
  [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)

### ge.memory.github_memory(repo, , roadmap_issue=None, decisions_target=None, triage_issue=None, cache_dir=None)

Bundle the three stores into a “mall” dict keyed by name.

Each store is only constructed if its target was provided. `roadmap`
requires `roadmap_issue`; `decisions` requires `decisions_target`
(the issue or PR number where decisions are logged); `triage`
requires `triage_issue` (the tracking issue holding the JSON block).

* **Return type:**
  [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`object`](https://docs.python.org/3/builtins/functions.html#object)]

```pycon
>>> # mall = github_memory('owner/repo', roadmap_issue=1, decisions_target=1)
```


# _autosummary/ge.run.html.md

# ge.run

Autonomous-execution runner — Layer 3.

Launches `claude` in headless mode in a loop, one invocation per work
unit. State persists between iterations *only* through GitHub-backed
memory ([`ge.memory`](_autosummary/ge.memory.html.md#module-ge.memory)); each session starts with a fresh context
window and rehydrates from the roadmap / triage backlog.

Two entry points:

- [`run_roadmap()`](_autosummary/ge.run.html.md#ge.run.run_roadmap) — drive a roadmap issue end to end.
- [`run_triage()`](_autosummary/ge.run.html.md#ge.run.run_triage)  — drive a cross-repo triage backlog (Phase A or B).

The permission-mode choice (`auto` vs `bypass`) lives here because
SKILL.md files cannot set `--permission-mode` — that is a launch-time
flag on `claude`.

What a spawned session can see is decided here too ([`SessionScope`](_autosummary/ge.run.html.md#ge.run.SessionScope)):
the cwd picks which `CLAUDE.md` files load (the cwd’s and its ancestors’), an
`--add-dir` contributes its skills but never its `CLAUDE.md`, and each one
widens the skill listing toward the budget past which descriptions are dropped
silently. So the runner warns when the cwd is outside every package checkout,
adds directories only when asked, puts any add-dir’s `CLAUDE.md` and the requested routing
documents into the prompt as the first Read, names the skills to load, and
returns the scope with the run so a later diagnosis can see it.

Token / context exhaustion is *not* a failure: it ends the current
session; the loop’s next iteration just rehydrates and continues. A
session terminated by the permission-denial cap *is* a failure: the
runner retries once and then escalates.

### Functions

| [`package_root`](_autosummary/ge.run.html.md#ge.run.package_root)([path])                              | The git checkout holding *path* (default: the current directory), or `None`.   |
|----------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------|
| [`run_roadmap`](_autosummary/ge.run.html.md#ge.run.run_roadmap)(repo, roadmap_issue, \*[, mode, ...]) | Drive a roadmap issue to completion.                                           |
| [`run_triage`](_autosummary/ge.run.html.md#ge.run.run_triage)(repos, \*, tracking_repo, ...[, ...])  | Drive a cross-repo triage backlog.                                             |

### Classes

| [`SessionResult`](_autosummary/ge.run.html.md#ge.run.SessionResult)(iteration, returncode, stdout, ...)   | Outcome of a single `claude` invocation.                          |
|------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------|
| [`SessionScope`](_autosummary/ge.run.html.md#ge.run.SessionScope)(cwd[, add_dirs, read_first, ...])      | What one spawned `claude` session can see, decided by the runner. |

### *class* ge.run.SessionResult(iteration, returncode, stdout, stderr, duration_sec)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

Outcome of a single `claude` invocation.

#### *property* killed_by_denial_cap *: [bool](https://docs.python.org/3/builtins/functions.html#bool)*

did Claude Code terminate due to the denial cap?

* **Type:**
  Heuristic

### *class* ge.run.SessionScope(cwd, add_dirs=(), read_first=(), skills=(), model=None, effort=None, warnings=())

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

What one spawned `claude` session can see, decided by the runner.

- `cwd`: where the session starts, so whose `CLAUDE.md` loads.
- `add_dirs`: further directories the run must *edit*, passed as
  `--add-dir`. Each one adds its skills to the listing; none adds its
  `CLAUDE.md`, which is why those become first Reads.
- `read_first`: routing documents the prompt tells the session to Read
  before anything else (absolute paths).
- `skills`: skills to load by name, beyond the runner’s own. A skill named
  in the prompt is invocable even when the listing shows it name-only.
- `model` / `effort`: `claude --model` / `--effort`, when set.
- `warnings`: why this scope may not load the work’s instructions.

```pycon
>>> scope = SessionScope(cwd=Path("/w/pkg"), add_dirs=(Path("/w/lib"),), model="opus")
>>> scope.cli_args()
('--add-dir', '/w/lib', '--model', 'opus')
```

#### as_dict()

JSON-ready, for the run’s result and its decision log.

* **Return type:**
  [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)

#### *classmethod* build(, cwd=None, add_dirs=(), read_first=(), skills=(), model=None, effort=None)

A scope from loose arguments, checked before anything is spawned.

`cwd` defaults to the current directory. Directories must exist; relative
add-dirs resolve against the current directory, relative `read_first`
paths against the scope’s cwd (both become absolute). Every add-dir’s
instruction file is added to `read_first`. A cwd outside every git
checkout is kept, with a warning (`OUTSIDE_A_CHECKOUT`).

* **Return type:**
  [`SessionScope`](_autosummary/ge.run.html.md#ge.run.SessionScope)

#### cli_args()

The `claude` flags this scope adds (after `-p` and the permission flags).

* **Return type:**
  [`tuple`](https://docs.python.org/3/builtins/stdtypes.html#tuple)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`...`](https://docs.python.org/3/builtins/constants.html#Ellipsis)]

#### prompt_preamble()

The lines that open the prompt: the first Reads, then the skills to load.

* **Return type:**
  [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)

### ge.run.package_root(path=None)

The git checkout holding *path* (default: the current directory), or `None`.

A session’s `CLAUDE.md` comes from its cwd and the cwd’s ancestors, so any
directory inside a package checkout loads the package’s instructions (and a
subdirectory adds its own). A directory outside every checkout, such as a
projects root or a scratch folder, loads none of the work’s.

* **Return type:**
  [`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path)]

### ge.run.run_roadmap(repo, roadmap_issue, , mode='auto', decisions_target=None, max_sessions=50, cwd=None, add_dirs=(), read_first=(), skills=(), model=None, effort=None, on_session=None)

Drive a roadmap issue to completion.

Each iteration is one headless `claude` invocation that performs
exactly one roadmap step. The loop terminates when:

- no todo tasks remain (`status='completed'`),
- two consecutive sessions hit the denial cap (`status='escalated_denial_cap'`),
- a session exits non-zero for another reason (`status='escalated_error'`),
- or `max_sessions` is reached.

* **Parameters:**
  * **repo** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – `"owner/repo"` containing the roadmap issue.
  * **roadmap_issue** ([`int`](https://docs.python.org/3/builtins/functions.html#int)) – the roadmap issue number.
  * **mode** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – `"auto"` (default) or `"bypass"`.
  * **decisions_target** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`int`](https://docs.python.org/3/builtins/functions.html#int)]) – issue/PR number to log decisions on. Defaults
    to `roadmap_issue`.
  * **max_sessions** ([`int`](https://docs.python.org/3/builtins/functions.html#int)) – safety ceiling on total iterations.
  * **cwd** (`Union`[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path), [`None`](https://docs.python.org/3/builtins/constants.html#None)]) – where the spawned `claude` starts, so whose `CLAUDE.md` it
    loads. Defaults to the current directory. A cwd outside every git
    checkout (a projects root, a scratch folder) loads no package’s
    instructions: it is kept, and named in the scope’s `warnings`.
  * **add_dirs** ([`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str) | [`Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path)]) – directories the run must edit besides `cwd` (`--add-dir`);
    their `CLAUDE.md` files become first Reads, since an add-dir’s
    never loads.
  * **read_first** ([`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – routing documents the session must Read first.
  * **skills** ([`Iterable`](https://docs.python.org/3/library/typing.html#typing.Iterable)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – skills to load by name, beyond `ge-autonomous-execution` and
    `ge-roadmap-execution`.
  * **model** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – `claude --model` / `--effort`.
  * **effort** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – `claude --model` / `--effort`.
  * **on_session** – optional callable invoked with each [`SessionResult`](_autosummary/ge.run.html.md#ge.run.SessionResult).
* **Return type:**
  [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)

The result carries `scope` ([`SessionScope.as_dict()`](_autosummary/ge.run.html.md#ge.run.SessionScope.as_dict)), so a later
diagnosis can see what the sessions could see.

### ge.run.run_triage(repos, , tracking_repo, tracking_issue, phase='analyze', mode='auto', max_sessions=50, cwd=None, add_dirs=(), read_first=(), skills=(), model=None, effort=None, on_session=None)

Drive a cross-repo triage backlog.

`phase='analyze'` runs Phase A (classify + order); a single
iteration is normally enough. `phase='execute'` runs Phase B
(one fix-and-PR per iteration); the loop terminates when no
unactioned `fixable` entries remain.

The triage backlog lives in `tracking_repo#tracking_issue` (see
[`ge.memory.TriageBacklog`](_autosummary/ge.memory.html.md#ge.memory.TriageBacklog)). `cwd`, `add_dirs`, `read_first`,
`skills`, `model` and `effort` scope each spawned session, as for
[`run_roadmap()`](_autosummary/ge.run.html.md#ge.run.run_roadmap); the result carries `scope`.

* **Return type:**
  [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)


# _autosummary/ge.util.html.md

# ge.util

Internal utilities: gh CLI wrapper, subprocess helpers, URL parsing.

### Functions

| [`check_ffmpeg`](_autosummary/ge.util.html.md#ge.util.check_ffmpeg)()                                 | Check if ffmpeg is available.                                                 |
|-------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------|
| [`default_output_dir`](_autosummary/ge.util.html.md#ge.util.default_output_dir)(repo, number[, kind])       | Compute the default output directory under the user's cache.                  |
| [`ensure_dir`](_autosummary/ge.util.html.md#ge.util.ensure_dir)(path)                               | Create directory if it doesn't exist, return Path.                            |
| [`extract_media_urls`](_autosummary/ge.util.html.md#ge.util.extract_media_urls)(markdown)                   | Extract image and video URLs from GitHub-flavored markdown.                   |
| [`gh_api`](_autosummary/ge.util.html.md#ge.util.gh_api)(endpoint, \*[, method, params, ...])    | Call the GitHub REST API via gh CLI.                                          |
| [`gh_auth_token`](_autosummary/ge.util.html.md#ge.util.gh_auth_token)()                                | Get the current gh auth token for authenticated downloads.                    |
| [`parse_github_url`](_autosummary/ge.util.html.md#ge.util.parse_github_url)(url)                          | Parse a full GitHub issue/PR/discussion URL into (owner, repo, number, kind). |
| [`parse_issue_url`](_autosummary/ge.util.html.md#ge.util.parse_issue_url)(url)                           | Parse a full GitHub issue/PR/discussion URL into (owner, repo, number, kind). |
| [`parse_repo_spec`](_autosummary/ge.util.html.md#ge.util.parse_repo_spec)(repo_spec)                     | Parse 'owner/repo' or full GitHub URL into (owner, repo).                     |
| [`resolve_target`](_autosummary/ge.util.html.md#ge.util.resolve_target)(user_input, \*[, current_repo]) | Resolve flexible user input into a structured target for ge workflows.        |
| [`rewrite_media_refs`](_autosummary/ge.util.html.md#ge.util.rewrite_media_refs)(markdown, url_to_local)     | Replace media URLs in markdown with local file paths.                         |

### ge.util.check_ffmpeg()

Check if ffmpeg is available.

### ge.util.default_output_dir(repo, number, kind='issue')

Compute the default output directory under the user’s cache.

Returns `~/.cache/ge/<owner>/<repo>/<kind>_<number>`, e.g.
`~/.cache/ge/thorwhalen/dol/issue_42`.

```pycon
>>> import os; home = os.path.expanduser('~')
>>> p = default_output_dir('thorwhalen/dol', 42, 'issue')
>>> str(p) == os.path.join(home, '.cache', 'ge', 'thorwhalen', 'dol', 'issue_42')
True
```

### ge.util.ensure_dir(path)

Create directory if it doesn’t exist, return Path.

### ge.util.extract_media_urls(markdown)

Extract image and video URLs from GitHub-flavored markdown.

Returns list of dicts with keys: url, alt, kind (‘image’, ‘video’, or ‘unknown’).

```pycon
>>> urls = extract_media_urls('![screenshot](https://example.com/img.png)')
>>> urls[0]['kind']
'image'
```

### ge.util.gh_api(endpoint, , method='GET', params=None, paginate=False, raw=False)

Call the GitHub REST API via gh CLI.

```pycon
>>> # gh_api('repos/octocat/Hello-World/issues/1')
```

### ge.util.gh_auth_token()

Get the current gh auth token for authenticated downloads.

```pycon
>>> # token = gh_auth_token()
```

### ge.util.parse_github_url(url)

Parse a full GitHub issue/PR/discussion URL into (owner, repo, number, kind).

```pycon
>>> parse_github_url('https://github.com/thorwhalen/dol/issues/42')
('thorwhalen', 'dol', 42, 'issue')
>>> parse_github_url('https://github.com/thorwhalen/dol/pull/7')
('thorwhalen', 'dol', 7, 'pr')
>>> parse_github_url('https://github.com/thorwhalen/dol/discussions/5')
('thorwhalen', 'dol', 5, 'discussion')
```

### ge.util.parse_issue_url(url)

Parse a full GitHub issue/PR/discussion URL into (owner, repo, number, kind).

```pycon
>>> parse_github_url('https://github.com/thorwhalen/dol/issues/42')
('thorwhalen', 'dol', 42, 'issue')
>>> parse_github_url('https://github.com/thorwhalen/dol/pull/7')
('thorwhalen', 'dol', 7, 'pr')
>>> parse_github_url('https://github.com/thorwhalen/dol/discussions/5')
('thorwhalen', 'dol', 5, 'discussion')
```

### ge.util.parse_repo_spec(repo_spec)

Parse ‘owner/repo’ or full GitHub URL into (owner, repo).

```pycon
>>> parse_repo_spec('thorwhalen/dol')
('thorwhalen', 'dol')
>>> parse_repo_spec('https://github.com/thorwhalen/dol')
('thorwhalen', 'dol')
>>> parse_repo_spec('https://github.com/thorwhalen/dol/issues/42')
('thorwhalen', 'dol')
```

### ge.util.resolve_target(user_input, , current_repo=None)

Resolve flexible user input into a structured target for ge workflows.

Accepts any of these forms:

> - A GitHub URL: `https://github.com/owner/repo/issues/42`
> - A folder path to pre-prepared context: `~/.cache/ge/owner/repo/issue_42`
> - A bare number: `42` or `#42` (uses *current_repo*)
> - `owner/repo#42` or `owner/repo/42`
> - `owner/repo 42`

Returns a dict with:

> - `repo`: `"owner/repo"` (or None if only a folder was given)
> - `number`: int
> - `kind`: `"issue"` | `"pr"` | `"discussion"` | None
> - `context_dir`: path to pre-prepared context (str or None)
> - `context_md`: path to the context markdown file (str or None)
> - `has_prepared_context`: bool — whether context files already exist
> - `source`: how the input was resolved (`"url"`, `"folder"`,
>   `"number"`, `"repo_number"`)
```pycon
>>> r = resolve_target('https://github.com/owner/repo/issues/42')
>>> r['repo'], r['number'], r['kind'], r['source']
('owner/repo', 42, 'issue', 'url')
```

```pycon
>>> r = resolve_target('#42', current_repo='owner/repo')
>>> r['repo'], r['number'], r['source']
('owner/repo', 42, 'number')
```

```pycon
>>> r = resolve_target('owner/repo#42')
>>> r['repo'], r['number'], r['source']
('owner/repo', 42, 'repo_number')
```

### ge.util.rewrite_media_refs(markdown, url_to_local)

Replace media URLs in markdown with local file paths.

```pycon
>>> md = '![bug](https://example.com/bug.png)'
>>> rewrite_media_refs(md, {'https://example.com/bug.png': 'media/bug.png'})
'![bug](media/bug.png)'
```


# about-this-build.html.md

<!-- generated by epythet -->

# About this build

This documentation was built on **2026-10-14 06:08 UTC** from commit <a href="https://github.com/thorwhalen/ge/commit/639fe26d9b2c7ff4e242690c01619d6adcfcdd36"><code>639fe26</code></a> on branch <code>main</code>, for **ge 0.1.18** (from <code>pyproject.toml</code>).

#### NOTE
Nothing suggests a mismatch: the tree was clean at the commit above, and the documented version is the one on PyPI.

## Source

|                     |                                                                                                                                                      |
|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------|
| Commit              | <a href="https://github.com/thorwhalen/ge/commit/639fe26d9b2c7ff4e242690c01619d6adcfcdd36"><code>639fe26d9b2c7ff4e242690c01619d6adcfcdd36</code></a> |
| Branch              | <code>main</code>                                                                                                                                    |
| Tags at this commit | <code>0.1.18</code>                                                                                                                                  |
| Working tree        | clean                                                                                                                                                |
| Remote              | <code>https://github.com/thorwhalen/ge</code>                                                                                                        |

## Continuous integration

|              |                                                                                            |
|--------------|--------------------------------------------------------------------------------------------|
| Repository   | <code>thorwhalen/ge</code>                                                                 |
| Run          | <a href="https://github.com/thorwhalen/ge/actions/runs/38520282592">38520282592</a>        |
| Ref          | <code>refs/heads/main</code>                                                               |
| Event commit | <code>b25aaa6daab2feef0cb6077922496b2758073bb1</code> (in the history of the built commit) |

## Tools

|          |         |
|----------|---------|
| epythet  | 0.2.17  |
| Sphinx   | 9.1.0   |
| docutils | 0.22.4  |
| Python   | 3.12.15 |

## Configuration as resolved

|               |                                                                   |
|---------------|-------------------------------------------------------------------|
| theme         | <code>auto</code> (Sphinx theme <code>sphinxawesome_theme</code>) |
| accent        | <code>#356708</code>                                              |
| api_generator | <code>autosummary</code>                                          |
| ignore        | <code>tests/</code>, <code>scrap/</code>, <code>examples/</code>  |
| agent_outputs | <code>true</code>                                                 |
| aggregates    | <code>md</code>                                                   |
| ai_artifacts  | <code>true</code>                                                 |

## Package on PyPI

Latest release: <a href="https://pypi.org/project/ge/0.1.18/">0.1.18</a>, the same as the documented version.

## Reproduce

```bash
git clone https://github.com/thorwhalen/ge && cd ge
git checkout 639fe26d9b2c7ff4e242690c01619d6adcfcdd36
pip install "epythet==0.2.17"
epythet quickstart . --ignore tests/ scrap/ examples/
```

The same data, for machines: <a href="build_info.json"><code>build_info.json</code></a> (schema version 1).


# ai-agents.html.md

<!-- generated by epythet -->

# For AI agents

`ge` ships artifacts for coding agents alongside its code. This page lists
them, says where each lives in the repository, and points at the
machine-readable copies of this documentation.

## Skills

Skills are folders holding a `SKILL.md` (the [Agent Skills](https://agentskills.io) format): a description that tells an agent when to use it and a body with the procedure. Install one into your agent with `gh skill` (any host: `--agent claude-code`, `copilot`, `cursor`, `codex`, `gemini`), or use the copy bundled in the wheel.

### `ge`

Use when asked to work on a GitHub issue, PR, or discussion. Resolves flexible input (URL, folder, number), confirms with the user, prepares context, and guides the workflow.

```bash
gh skill install thorwhalen/ge ge --agent claude-code
```

Source: [`ge/data/skills/ge`](https://github.com/thorwhalen/ge/tree/HEAD/ge/data/skills/ge) (bundled with the pip package).

### `ge-analyze`

Use when you need to check whether a GitHub issue or PR is still relevant, stale, or already resolved — without preparing full context or downloading media.

```bash
gh skill install thorwhalen/ge ge-analyze --agent claude-code
```

Source: [`ge/data/skills/ge-analyze`](https://github.com/thorwhalen/ge/tree/HEAD/ge/data/skills/ge-analyze) (bundled with the pip package).

### `ge-autonomous-execution`

Behavioural policy for running unattended. Use when you have been launched in headless / auto-permission mode (e.g. by `ge run-roadmap` or `ge run-triage`) and there is no user available to answer questions. Converts every clarifying question into a logged decision so progress continues.

```bash
gh skill install thorwhalen/ge ge-autonomous-execution --agent claude-code
```

Source: [`ge/data/skills/ge-autonomous-execution`](https://github.com/thorwhalen/ge/tree/HEAD/ge/data/skills/ge-autonomous-execution) (bundled with the pip package).

### `ge-context`

Use when you need to prepare, read, or refresh full context for a GitHub issue, PR, or discussion — fetching data, downloading media, and writing structured context documents.

```bash
gh skill install thorwhalen/ge ge-context --agent claude-code
```

Source: [`ge/data/skills/ge-context`](https://github.com/thorwhalen/ge/tree/HEAD/ge/data/skills/ge-context) (bundled with the pip package).

### `ge-cross-repo-triage`

Triage and resolve a cross-repo issue backlog. Use when launched by `ge run-triage` against multiple repos — Phase A classifies and orders issues into a `TriageBacklog`; Phase B executes them one at a time (failing test → confirm red → fix → confirm green → open PR, then stop). Stops at PR; merging is a separate later phase.

```bash
gh skill install thorwhalen/ge ge-cross-repo-triage --agent claude-code
```

Source: [`ge/data/skills/ge-cross-repo-triage`](https://github.com/thorwhalen/ge/tree/HEAD/ge/data/skills/ge-cross-repo-triage) (bundled with the pip package).

### `ge-roadmap-execution`

Execute a roadmap of tasks end-to-end using GitHub as durable memory. Use when launched against a roadmap issue (typically by `ge run-roadmap`) — take the next todo task, do it, verify it, mark it done, log decisions, and repeat. Survives context exhaustion because each work unit rehydrates from GitHub.

```bash
gh skill install thorwhalen/ge ge-roadmap-execution --agent claude-code
```

Source: [`ge/data/skills/ge-roadmap-execution`](https://github.com/thorwhalen/ge/tree/HEAD/ge/data/skills/ge-roadmap-execution) (bundled with the pip package).

### `ge-write-issue`

Use when AUTHORING a GitHub issue, comment, or report that a human will read — especially a non-technical stakeholder, a client, or someone outside the repo. Covers what goes in an issue versus a comment, how to layer detail so the reader can stop early, how to ask a question that actually gets answered, and how to assign it so someone is on the hook. Triggers on: ‘open an issue for’, ‘post this to the issue’, ‘ask the client’, ‘report this back’, ‘write it up as an issue’, ‘file a question’, ‘let them know’, or any request to communicate findings outward through GitHub. This is about WRITING issues; ge-analyze and ge-context are about WORKING ON issues that already exist.

```bash
gh skill install thorwhalen/ge ge-write-issue --agent claude-code
```

Source: [`ge/data/skills/ge-write-issue`](https://github.com/thorwhalen/ge/tree/HEAD/ge/data/skills/ge-write-issue) (bundled with the pip package).

The bundled skills are also on disk after `pip install ge`, under the package’s `data/skills/` directory; link them into an agent without network access with `skill link-skills <that directory>`.

## Instruction files

Files agents read before working in this repository.

- [`.claude/CLAUDE.md`](https://github.com/thorwhalen/ge/tree/HEAD/.claude/CLAUDE.md): read by Claude Code

## Machine-readable documentation

This site publishes the same documentation in forms that fit an agent’s context window:

- [`llms.txt`](https://thorwhalen.github.io/ge/llms.txt): an index of every page with a one-line description ([llms.txt](https://llmstxt.org) format)
- [`ge.md`](https://thorwhalen.github.io/ge/ge.md): the whole documentation as one Markdown file
- `<page>.html.md`: a rendered Markdown twin of every page, advertised from each page’s `<head>` with `<link rel="alternate" type="text/markdown">`
- [`objects.inv`](https://thorwhalen.github.io/ge/objects.inv): the Sphinx inventory: a symbol-to-URL index (`sphobjinv convert plain objects.inv -`)


# api.html.md

# API reference

| [`ge`](_autosummary/ge.html.md#module-ge)   | ge: GitHub Engineering tools for AI agents.   |
|-----------------------------------------------------------------|-----------------------------------------------|


