Use when (1) user provides a GitHub repository URL or local repo path and asks to convert it to a different format. (2) user asks to export a GitHub reposito...
---
name: convert-github-repository
description: >
Use when (1) user provides a GitHub repository URL or local repo path and asks to convert it to a different format. (2) user asks to export a GitHub repository as Markdown documentation, JSON metadata, or CSV. (3) user wants to transform repository structure into a different representation (e.g., folder tree to JSON, issue tracker to README).
license: MIT
metadata:
version: "1.0"
category: productivity
author: wangjipeng
sources:
- https://github.com/MiniMax-AI/skills
---
## Core Position
This skill converts **GitHub repository content and metadata** into target formats (Markdown documentation, JSON metadata, CSV tables, folder tree JSON). It preserves structure and content fidelity across formats — not a simple file copy, but a semantic transformation that respects GitHub's data model (repos, trees, commits, issues, PRs, releases).
Key responsibilities:
- Parse GitHub repository structure (branches, directory tree, files) using GitHub API or local `.git` directory
- Convert repository data models (issues, PRs, releases, contributors) into target format representations
- Preserve file content and encoding (UTF-8, binary detection) during format transformation
- Provide a manifest of what was converted (file count, type breakdown, size summary)
## Modes
### `/convert-github-repository --markdown`
**Repository → Markdown documentation.** Converts the entire repository into a set of Markdown files:
- `README.md` from repository root
- Each directory becomes a subdirectory with `README.md` describing its contents
- Issues exported as `issues/YYYY-MM-DD-{number}-{title}.md`
- PRs exported as `pull-requests/YYYY-MM-DD-{number}-{title}.md`
- Releases exported as `releases/v{semver}.md`
- Code files preserved in original language with syntax highlighting fences
### `/convert-github-repository --json`
**Repository → JSON metadata.** Exports repository structure and content as a JSON file:
```json
{
"repo": { "name": "...", "description": "...", "stars": N, "license": "...", "topics": [...] },
"files": [{ "path": "...", "size": N, "type": "file|dir", "sha": "..." }],
"branches": [{ "name": "...", "last_commit": "..." }],
"contributors": [{ "login": "...", "contributions": N }]
}
```
### `/convert-github-repository --csv`
**Issues + PRs → CSV.** Exports issues and pull requests as CSV rows with columns: `number, title, state, author, created_at, updated_at, labels, assignees, milestone, body_preview`.
### `/convert-github-repository --tree`
**Repository → folder tree JSON.** Outputs a tree structure representing the repository layout:
```json
{
"path": "/",
"type": "directory",
"children": [
{ "path": "src/index.js", "type": "file", "size": 1234, "language": "JavaScript" },
{ "path": "tests/", "type": "directory", "children": [...] }
]
}
```
### `/convert-github-repository --readme-to-json`
**README.md → structured JSON.** Parses a README.md and extracts: title (first H1), description (first paragraph after title), installation steps, usage examples, contributing guidelines, license.
## Execution Steps
1. **Identify repository source**
**Remote GitHub repo:** Parse URL to extract `owner` and `repo`:
```
https://github.com/owner/repo -> {owner: "owner", repo: "repo"}
```
Use GitHub API with `GITHUB_TOKEN` from env (`os.getenv("GITHUB_TOKEN")`).
**Local repository:** Verify `.git` directory exists. Use `git` CLI to extract:
- `git ls-files` for file list
- `git log --oneline` for recent commits
- `git remote get-url origin` to confirm repo identity
If neither source is available, report: "Cannot identify repository source — provide either a GitHub URL (https://github.com/owner/repo) or a local path with a .git directory."
2. **Fetch repository metadata**
For remote repos, call GitHub API:
```
GET https://api.github.com/repos/{owner}/{repo}
Authorization: Bearer {GITHUB_TOKEN}
Accept: application/vnd.github+json
```
Response contains: `full_name`, `description`, `stargazers_count`, `forks_count`, `license.spdx_id`, `topics`, `default_branch`, `created_at`, `updated_at`, `homepage`, `language`.
If the token is missing or rate-limited (403), try unauthenticated (lower rate limit) or report: "GitHub API unavailable — check GITHUB_TOKEN or try again later (rate limit: 60 req/hr unauthenticated)."
3. **Fetch repository contents**
**Get default branch tree:**
```
GET https://api.github.com/repos/{owner}/{repo}/git/trees/{default_branch}?recursive=1
```
Returns a flat list of all files with `path`, `type` (blob/tree), `size`, `sha`.
**For local repos:** Run `git ls-tree -r --name-only {branch}` to get the file list, then `git show {sha}:{path}` to read file content.
Filter out common non-essential paths:
- `node_modules/`, `.git/`, `vendor/` — skip unless specifically requested
- Binary files (images, PDFs, compiled binaries) — include in manifest but not content
- `.gitignore`-referenced files that are not committed — skip
4. **Convert to target format**
**`--markdown` conversion:**
- README.md: Preserve as-is, update relative links to point to local files
- Code files: Write with syntax-highlighting fence (```language) based on file extension mapping:
- `.js` → ` ```javascript`, `.py` → ` ```python`, `.go` → ` ```go`, `.rs` → ` ```rust`, etc.
- Directories: Create `README.md` in each directory with list of contained files
- Issues: Each issue becomes `{number}-{slug}.md` with frontmatter:
```yaml
---
number: 42
state: open
author: username
created: 2024-01-15
labels: [bug, help-wanted]
---
# Title
body text...
```
- PRs: Same format as issues, plus `merged` field and review comments
**`--json` conversion:**
- Build nested structure from flat file list (group by directory path)
- Include file content for text files (base64 encode if large > 1MB)
- For large repos (>10K files), stream output to avoid memory exhaustion
**`--csv` conversion:**
- Headers: `number,title,state,author,created_at,updated_at,labels,assignees,milestone,body_preview`
- Labels: join with `;` delimiter
- Assignees: join with `;` delimiter
- Body preview: first 200 chars, strip markdown formatting
- Quote fields that contain commas; escape internal double quotes as `""`
**`--tree` conversion:**
- Build recursive tree structure
- Detect language from extension using common mapping (`.js` → JavaScript, `.py` → Python, etc.)
- Report total file count, directory count, size breakdown by type
**`--readme-to-json` conversion:**
- Parse markdown using regex or a simple state machine:
- First `# Heading` → `title`
- Paragraphs between headings → sections (`installation`, `usage`, `contributing`, etc.)
- Code blocks → `code_examples` array
- Tables → parsed as arrays of objects
- Links `[text](url)` → collected in `links` array
5. **Validate output**
Before delivering:
- If format is JSON: parse with `json.loads()` and confirm no errors
- If format is CSV: verify all rows have same column count
- If format is Markdown: verify all files are readable UTF-8
- Check that files were not silently skipped — report count of skipped vs converted
6. **Deliver with manifest**
Return the converted output plus a manifest:
```
{
"format": "markdown",
"files_converted": 142,
"files_skipped": 3,
"skipped_reasons": [
{"path": "node_modules/package/index.js", "reason": "binary or non-text, excluded by default"},
{"path": ".git/config", "reason": "contains sensitive data"}
],
"total_size_bytes": 2048576,
"output_path": "./{repo-name}-converted/"
}
```
## Mandatory Rules
### Do not
- Do not hardcode GitHub token — use `os.getenv("GITHUB_TOKEN")`
- Do not convert binary files (images, PDFs, compiled binaries) as text — detect by extension or size > 5MB
- Do not convert files listed in `.gitignore` unless user explicitly requests `--include-ignored`
- Do not silently skip files — every skip must appear in `skipped_reasons` in the manifest
- Do not produce output that fails format validation (malformed JSON, unparseable CSV)
- Do not convert private repos without authentication — verify 401 is not returned before proceeding
### Do
- Always produce a manifest with `files_converted`, `files_skipped`, `skipped_reasons`
- Preserve file encoding — read as binary, decode as UTF-8, replace invalid bytes with `\uFFFD`
- Report the exact file count and size for transparency
- Use pagination when fetching issues/PRs (GitHub API paginates at 30/100 per page)
- For local repos, use `git` CLI — never assume raw file access is available
- Filter out common non-code directories (node_modules, .git, __pycache__, build/, dist/) by default
## Quality Bar
| Criterion | Minimum | Ideal |
|-----------|---------|-------|
| Content preservation | 100% of text file content preserved | Binary files listed in manifest, not content-lost |
| Format validity | Output passes format parser | Strict schema validation (JSON: draft-7, CSV: consistent columns) |
| Manifest completeness | Every skipped file has a reason | Every converted file listed with size and type |
| Encoding correctness | UTF-8 for all text files | Invalid bytes replaced with U+FFFD, not dropped or garbled |
| Rate limit handling | 403 triggers re-auth attempt | Proactive pagination with token refresh on 403 |
| Large repo handling | < 500MB memory for repos up to 10K files | Streaming JSON output, chunked file writes |
A good output passes the target format parser without errors, preserves all semantic content, and includes a complete manifest of what was converted and why some files were skipped.
## Good vs. Bad Examples
| Scenario | Bad | Good |
|---------|-----|------|
| Missing token | Retries unauthenticated forever | Reports "GitHub API auth required — set GITHUB_TOKEN environment variable" |
| Binary file | Tries to read as text, garbles content | Skips binary, reports in manifest: `{"path": "logo.png", "reason": "binary/image, excluded"}` |
| Large repo | Loads all files into memory, crashes | Streams output, reports "Processed 8,432 of 12,000 files (70%)" |
| Rate limited | Fails silently after 3 requests | Reports "Rate limit exceeded (403) — retry after 14:32 UTC or set GITHUB_TOKEN" |
| Missing field | Skips `homepage` when absent | Includes `"homepage": null` — no field silently dropped |
| Format error | Writes malformed JSON with trailing comma | Validates with `json.dumps()`, reports "Output invalid: unexpected token at line 847" |
| Local repo path | Assumes `~/repo` exists | Checks `.git` directory, reports "Path is not a git repository: ./myrepo" |don't have the plugin yet? install it then click "run inline in claude" again.