--- name: github-issue-from-templates description: Create GitHub issues using data-driven templates. Supports any issue type via configurable template configs. Use when the user asks to create a GitHub ticket, issue, or support ticket, or when they want to add a new issue template. --- # GitHub Issue Creation — Data-Driven Workflow Engine This skill creates GitHub issues by dynamically fetching field definitions from GitHub issue templates at runtime. Template metadata (triggers, labels, defaults, formatting rules) is stored in per-template JSON config files. Configs can be stored locally or in a GitHub repository for cross-machine and team sharing. The skill itself contains no hardcoded field definitions. ## File Structure ``` ~/.claude/skills/github-issue-from-templates/ SKILL.md # This file — generic workflow engine references/ schema.json # JSON Schema for template config files settings-schema.json # JSON Schema for settings.json ~/.claude/configs/github-issue-from-templates/ settings.json # Storage mode config — created on first run .local/ # Template configs (local mode only) — user-managed, survives skill updates *.json .cache/ # Local cache (auto-managed) *.json # Cached config files (GitHub mode) templates/ # Cached issue templates (both modes) .yml|md /// # Template configs (GitHub mode) — canonical source *.json ``` > **Why a separate directory?** The skill installation directory (`~/.claude/skills/...`) is replaced on `npx skills update`. Storing template configs in `~/.claude/configs/github-issue-from-templates/.local/` (local mode) or a GitHub repo (GitHub mode) keeps them safe across updates. The `settings.json` file always lives locally since it tells the skill where to find configs. --- ## Tool Detection Before starting the workflow, determine which GitHub tool is available: 1. **GitHub MCP** (preferred): Check if the GitHub MCP server is available by looking for MCP tools like `get_file_contents` or `create_issue`. If available, use MCP tools throughout. 2. **`gh` CLI** (fallback): If GitHub MCP is not available, verify the `gh` CLI is installed and authenticated by running `gh auth status`. If authenticated, use `gh` CLI commands throughout. 3. **Neither available**: Notify the user that either the [GitHub MCP server](https://github.com/github/github-mcp-server) or the [`gh` CLI](https://cli.github.com/) is required, and stop. Store the detected tool as the **GitHub method** (`mcp` or `cli`) and use it consistently for all GitHub operations in the workflow. > **Note:** When using GitHub repo storage, the same detected method is used for additional operations: listing directory contents, reading files, creating/updating files, and optionally creating repositories. --- ## Workflow ### Step 0: Settings & Storage Resolution Before template selection, resolve where configs are stored. 1. Check if `~/.claude/configs/github-issue-from-templates/settings.json` exists. 2. **If it exists**: Read it, validate against `references/settings-schema.json`, and resolve the storage mode: - `configStorage.type === "local"` → configs are in `~/.claude/configs/github-issue-from-templates/.local/` - `configStorage.type === "github"` → configs are cached locally in `~/.claude/configs/github-issue-from-templates/.cache/` (canonical source is the configured GitHub repo) 3. **If it does not exist**: Run the **Setup Flow** (see below). 4. **Cache management** (GitHub mode only — after resolving `configStorage.type === "github"`): - If `~/.claude/configs/github-issue-from-templates/.cache/` **does not exist** → run initial sync (see [Syncing Configs from GitHub](#syncing-configs-from-github)) to download all configs into `.cache/` - If `.cache/` **exists** → use cached files directly (no network call) 5. Store the resolved config directory path (`.local/` or `.cache/`) for use in Step 1. #### Setup Flow (First Run) Ask the user how they want to store their template configs: **Option A — Local storage:** 1. Create the directories `~/.claude/configs/github-issue-from-templates/` and `.local/` if they don't exist 2. Write `settings.json`: ```json { "configStorage": { "type": "local" } } ``` 3. Offer to create a first template config **Option B — GitHub repository storage:** 1. Ask if they have an existing repo for configs 2. **If yes**: - Gather: `owner`, `repo`, `path` (default: `configs/github-issue-from-templates/`), `branch` (default: `main`) - Validate access by listing the directory contents using the detected GitHub method (see [Syncing Configs from GitHub](#syncing-configs-from-github) for commands) - Write `settings.json`: ```json { "configStorage": { "type": "github", "owner": "", "repo": "", "path": "configs/github-issue-from-templates/", "branch": "main" } } ``` 3. **If no** — help create a new repo: - Suggest the name `github-issue-from-templates-configs` (default private) - **If MCP**: Use `create_repository` with `name`, `private: true`, `description` - **If CLI**: `gh repo create /github-issue-from-templates-configs --private --description "Template configs for github-issue-from-templates skill"` - Create the initial directory by committing a placeholder `README.md` at the configured path - Write `settings.json` as above 4. After writing `settings.json`, run the initial sync to populate `.cache/` (see [Syncing Configs from GitHub](#syncing-configs-from-github)) #### Switching Storage Modes If the user asks to change their storage mode (e.g., from local to GitHub or vice versa): 1. Read the current `settings.json` to determine the current mode 2. Ask the user if they want to **migrate** existing configs to the new location: - **Local → GitHub**: Read each `.json` config from `.local/`, commit each to the GitHub repo at the configured path via API, then populate `.cache/` from the push responses - **GitHub → Local**: Copy each `.json` config from `.cache/` to `.local/`. Remove `.cache/` 3. Update `settings.json` with the new storage configuration 4. Confirm the switch and report how many configs were migrated --- ### Step 1: Template Selection 1. **Load configs** from the resolved config directory (Step 0): - **Local**: Read all `.json` files from `~/.claude/configs/github-issue-from-templates/.local/`. If the directory does not exist or contains no config files, offer to create a first template config. - **GitHub**: Read all `.json` files from `~/.claude/configs/github-issue-from-templates/.cache/`, **excluding** `README.md`. The cache is populated during Step 0 — no network calls are needed here. If the cache is empty, offer to run a sync or add a first config. 2. For each config, compare the user's request against `triggers.keywords` (case-insensitive substring match) and `triggers.description`. 3. **Single match**: Proceed with that template. Confirm the selection with the user briefly (e.g., "I'll use the [template name] template."). 4. **Multiple matches**: Present the matching templates by `name` and `description` and ask the user to choose. 5. **No match**: Present all available templates by `name` and `description` and ask the user to choose. --- ### Syncing Configs from GitHub When `configStorage.type === "github"`, use this process to download configs from the remote repo into the local cache at `~/.claude/configs/github-issue-from-templates/.cache/`. This runs during initial setup (Step 0) and on manual sync requests. #### 1. List files in the config directory **If MCP**: Use `get_file_contents` on the directory path: ``` owner: repo: path: ref: ``` The response returns an array of file entries. Filter to `.json` files, excluding `settings.json` and `README.md`. **If CLI**: Use the GitHub contents API: ```bash gh api repos///contents/?ref= --jq '.[] | select(.name | endswith(".json")) | select(.name != "settings.json") | .name' ``` #### 2. Fetch each config file **If MCP**: Use `get_file_contents` with the full file path: ``` owner: repo: path: / ref: ``` **If CLI**: ```bash gh api repos///contents//?ref= --jq '.content' | base64 -d ``` #### 3. Save to local cache Write each fetched file to `~/.claude/configs/github-issue-from-templates/.cache/`. Create the `.cache/` directory if it doesn't exist. Parse each fetched file as JSON. Skip files that fail to parse and notify the user (see [Error Handling](#error-handling)). #### 4. Sync templates (optional) For each config that was just synced, optionally fetch the corresponding issue template and save it to `.cache/templates/.`. Create the `.cache/templates/` directory if it doesn't exist. This step is optional — templates will be fetched lazily on first use in Step 2 if skipped here. --- ### Step 2: Fetch Template from GitHub Before fetching from GitHub, check the local template cache: 1. **Check cache**: Look for `~/.claude/configs/github-issue-from-templates/.cache/templates/.` 2. **If cached** → read the file contents from cache and skip the GitHub API call 3. **If not cached** → fetch from GitHub using the detected GitHub method (below), then save the raw content to `.cache/templates/.`. Create the `.cache/templates/` directory if it doesn't exist. **If MCP**: Use `get_file_contents`: ``` owner: repo: path: ``` **If CLI**: Use `gh` to fetch the raw file content: ```bash gh api repos///contents/ --jq '.content' | base64 -d ``` Then parse based on `config.templateSource.format`: #### Format: `yml` (Form-based templates) Parse the YAML content and extract: - **Title pattern**: From the top-level `title:` field (e.g., `"[Issue Type] [Short descriptive title]"`) - **Template-level labels**: From the top-level `labels:` array - **Template-level assignees**: From the top-level `assignees:` array - **Fields**: From the `body:` array. For each entry: - Skip entries where `type: markdown` — these are instructional text, not fields - For all other entries, extract: - `id` — unique field identifier - `type` — `dropdown`, `input`, `textarea` - `attributes.label` — human-readable field name - `attributes.description` — help text for the field - `attributes.options` — available choices (for dropdowns) - `attributes.placeholder` — example/guidance text - `validations.required` — whether the field must be filled #### Format: `md` (Frontmatter + markdown templates) Parse the frontmatter (between `---` delimiters) and extract: - **Title pattern**: From `title:` (e.g., `'[A11y]: Product - Feature - Request'`) - **Template-level labels**: From `labels:` (may be a string or array) - **Template-level assignees**: From `assignees:` (may be a string or array) Parse the markdown body to identify: - **Sections**: `##` headings define major sections - **Checkbox groups**: Lines matching `- [ ] Item text` grouped under a heading or bold label - **Labeled fields**: Bold-labeled list items like `- **Team name:**` under a section - **Self-verification checklists**: Sections like "Yes, I have" contain items the skill should satisfy automatically ### Step 3: Gather Information For each extracted field, apply the following logic in order: 1. **Pre-fill from user request**: If the user already provided a value for this field in their initial message, pre-fill it and confirm during the preview step. 2. **Apply defaults**: Check `config.fieldDefaults[fieldId].value` — if present, use as the default. Also check `config.defaults` for matching keys. 3. **Check skip conditions**: Check `config.fieldSkipConditions[fieldId]` — if an `onlyWhen` condition exists and is not met, skip this field entirely. 4. **Prompt if needed**: If the field is required (`validations.required: true`) and no value has been determined, prompt the user. Use the field's `label` as the question and `description`/`placeholder` as guidance. 5. **Apply gathering notes**: Use `config.fieldDefaults[fieldId].gatheringNotes` for additional guidance on how to present or gather this field. **Gathering style**: - Be conversational — don't present a wall of questions - Batch related questions together (e.g., ask for summary and description in one turn) - For dropdowns, present the options from the template - For fields with defaults, mention the default and ask if it's correct - Skip optional fields that the user hasn't mentioned unless they're likely relevant ### Step 4: Compose Issue #### Title Build the title by substituting placeholders in the title pattern: - Use `config.title.override` if present; otherwise use the pattern extracted from the template in Step 2 - Replace placeholder text with gathered field values using reasoning (e.g., `[Issue Type]` → the value of the `issue-type` field, `[Short descriptive title]` → the `summary` field value) #### Body Render the issue body following the structure from the fetched template: - **yml templates**: Render each field as a `### Field Label` section with the gathered value. Use `no response` for empty optional sections. Maintain the exact field order from the template. - **md templates**: Reconstruct the markdown body with gathered values filled in. Check the appropriate checkboxes, fill in labeled fields, and include all sections. #### Labels Build the label set: 1. Start with `config.labels.default` 2. Merge in template-level labels (from the fetched template's `labels:` field) — avoid duplicates 3. Apply `config.labels.conditional` rules: - **`keyword`**: Check if any keyword appears in the user's request (case-insensitive) - **`fieldValue`**: Check if the specified field has the specified value - **`fieldTransform`**: Derive a label from a field value using the specified transform (e.g., `lowercase-hyphenate` converts "Document Status" to "document-status") 4. Apply any additional label logic defined in the template config's `notes` #### Assignees Merge `config.assignees.default` with template-level assignees. If `config.assignees.promptUser` is true, ask the user if they want to assign anyone else. ### Step 5: Preview & Confirm Present the composed issue to the user for review: ``` **Title**: [composed title] **Labels**: label1, label2, label3 **Assignees**: @user1, @user2 **Project**: [project board name] → [status] **Body**: [rendered body preview] ``` Ask for confirmation or edits. If the user requests changes, apply them and re-preview. ### Step 6: Create Issue Create the issue using the detected GitHub method: **If MCP**: Use `issue_write`: ``` method: create owner: repo: title: body: labels: