Claude Skill

yeet

Imported from paulrberg/agent-skills/skills/yeet.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download paulrberg-agent-skills-skills_yeet-913232a.zip · 37 KB
Part of paulrberg/agent-skills — 42 skills

Install

skills CLI npx skills add https://github.com/PaulRBerg/agent-skills/tree/main/skills/yeet
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install paulrberg-agent-skills@llmmart
Git git clone https://github.com/PaulRBerg/agent-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole paulrberg/agent-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

GitHub Contribution Workflows

This skill is coordination-exempt: skip the ai-coord gate for its declared work.

Create or update GitHub contributions from repository evidence, using the matching workflow's templates, idempotency rules, and Paul's writing voice.

Prerequisites

Use the first required read-only gh command in each workflow as authentication validation. Resolve <skill-dir> once to the absolute directory containing this SKILL.md. The yeet-context.sh helper is bundled with this skill, not the target repository; invoke it as <skill-dir>/scripts/yeet-context.sh and never search for it in the target repository. Prefer the helper when the workflow needs repository, template, discussion, label, or issue/PR thread context.

For YAML issue forms, invoke <skill-dir>/scripts/issue-form.py. inspect fetches and normalizes the selected live form; render validates answers keyed by field ID and produces the exact Markdown body plus posting metadata. The helper never selects a template, writes answers or titles, performs an external-disclosure review, or posts externally. All issue creation and update workflows apply references/context.md > Issue Metadata Permissions before resolving or passing metadata. Rendered template metadata describes requested values, not the viewer's authority to apply them.

For pull request workflows, also verify:

  • Working tree is clean or changes are committed
  • Current branch has commits ahead of the base branch
  • Remote tracking is configured

Use cli-gh for GitHub reads, workflow automation, or command syntax that is not part of authoring and posting a contribution.

Workflows

Each workflow is fully documented in its reference file. Load the appropriate reference based on user intent.

Workflow Trigger Reference
Create PR "create PR", "open PR", "yeet a PR" references/create-pr.md
Update PR "update PR", "edit PR" references/update-pr.md
Create Issue "create issue", "file issue" (generic repo) references/create-issue.md
Update Issue "update issue", "edit issue", "relabel issue" references/update-issue.md
Claude Code Issue "Claude Code issue", "report bug in CC" references/issue-claude-code.md
Codex CLI Issue "Codex issue", "report bug in Codex" references/issue-codex-cli.md
Sablier Issue "Sablier issue", "sablier-labs issue" references/issue-sablier.md
Comment on Issue "comment on issue", "reply on issue", "post a comment" references/comment-issue.md
Create Discussion "create discussion", "start discussion" references/create-discussion.md
Update Discussion "update discussion", "edit discussion" references/update-discussion.md
Comment Discussion "comment on discussion", "reply on discussion", "edit discussion comment" references/comment-discussion.md

Each workflow reference links only the shared context, writing, or posting guidance it needs. Post directly when the user requested creation or update; do not add a confirmation gate. After a failed write, run the linked idempotency check before any retry.

Never check an external template attestation unless repository or user evidence verifies it. If a required attestation or field cannot be verified, ask for that missing fact rather than inventing agreement. Agent-status decoration belongs outside the authored contribution; add emoji to a PR, issue, discussion, or comment only when the user's content or the thread's register calls for it.

Completion

Complete when the requested contribution exists in its final authored state and the returned GitHub URL has been verified. For updates/comments, report the changed artifact once. Determine the outcome from readback, not the command's exit status: creation can succeed before a metadata mutation fails.

Use ### 🚀 <artifact> created, ### ✅ <artifact> updated, ### ✅ Comment posted, or ### ✅ Comment updated, followed by one Markdown link containing the repository, number, and title or action. Add a compact field list only when base, draft state, reviewers, labels, or changed fields matter. For verified partial success, report the created or updated artifact and identify omitted or failed metadata. Use ### ⛔ <artifact> not <action> only for confirmed noncompletion; if readback is inconclusive, report the outcome as unverified. Include the concrete error, idempotency result, and next action. Keep gh output, JSON, diagnostics, template fields, URLs, and authored contribution text exact and undecorated.

Files (agent-skills)
  • agents
    • openai.yaml 42 B
      policy:
        allow_implicit_invocation: true
      
  • fixtures
    • issue-form.yml 956 B
      name: Bug report
      description: Report a synthetic bug
      title: "[BUG] "
      labels:
        - bug
      type: Bug
      body:
        - type: input
          attributes:
            label: Optional context
        - type: input
          id: summary
          attributes:
            label: Summary
            description: State what broke.
          validations:
            required: true
        - type: textarea
          id: logs
          attributes:
            label: Logs
            render: shell
        - type: dropdown
          id: operating-system
          attributes:
            label: Operating system
            options:
              - macOS
              - Linux
            multiple: false
          validations:
            required: true
        - type: dropdown
          id: affected-surfaces
          attributes:
            label: Affected surfaces
            options:
              - CLI
              - Extension
            multiple: true
        - type: checkboxes
          id: terms
          attributes:
            label: Attestations
            options:
              - label: I searched for duplicates
                required: true
              - label: I can provide more details
      
  • references
    • comment-discussion.md 2.7 KB
      # Discussion Comment Workflow
      
      Add a top-level comment, reply to a discussion comment, or edit an existing comment with `gh discussion comment`. Load
      [posting.md](posting.md) before the write.
      
      ## Validate and Parse the Target
      
      Requires authenticated GitHub CLI >= 2.97.0. Use the first discussion read as auth validation and resolve `<skill-dir>`
      to the absolute directory containing the owning `SKILL.md`.
      
      Accept these forms:
      
      - `owner/repo#123 <comment>`
      - `owner/repo 123 <comment>`
      - `#123 <comment>` or `123 <comment>` (infer the repository from `origin`)
      - `https://github.com/owner/repo/discussions/123 <comment>`
      - A discussion comment URL or node ID for a reply or edit
      
      Everything after the target is the comment context. Reject a missing target or empty body with a precise error. A
      discussion number or URL targets a top-level comment. A comment URL or node ID targets a reply by default. Use `--edit`
      only with an existing comment URL or node ID; deletion is not part of this workflow.
      
      ## Read Context
      
      For a discussion target, fetch the discussion and recent comments before writing:
      
      ```bash
      gh discussion view <number-or-url> --repo "<owner>/<repo>" --json number,url,title,body,comments
      ```
      
      For a comment target, fetch its thread and the parent discussion:
      
      ```bash
      gh discussion view <comment-url-or-id> --repo "<owner>/<repo>" --json number,url,comments
      ```
      
      Match the thread's register, avoid duplicate or unnecessary pings, and follow `writing.md > Informal Tone`.
      
      ## Review and Post
      
      Run `posting.md > External-disclosure Review` on the exact comment body before posting. Use `--body-file` for multiline
      content (or a quoted heredoc when supplying `--body`):
      
      ```bash
      # Top-level comment
      gh discussion comment <discussion-number-or-url> --repo "<owner>/<repo>" --body-file "<comment-file>"
      
      # Reply to a comment
      gh discussion comment <comment-url-or-id> --repo "<owner>/<repo>" --body-file "<reply-file>"
      
      # Edit a comment or reply
      gh discussion comment <comment-url-or-id> --repo "<owner>/<repo>" --edit --body-file "<new-body-file>"
      ```
      
      Use `posting.md > Error Handling and Idempotency` after any nonzero, timeout, or connection-loss result. Reread the
      discussion or comment thread and treat a matching authored body as a possible partial success; do not post a duplicate.
      Verify the resulting comment URL or body with `gh discussion view`, then display the `### ✅ Comment posted` or
      `### ✅ Comment updated` receipt from `SKILL.md`.
      
      ## Examples
      
      ```text
      owner/repo#123 "This also reproduces on macOS."
      https://github.com/owner/repo/discussions/123#discussioncomment-456 "Here's the missing reproduction step."
      https://github.com/owner/repo/discussions/123#discussioncomment-456 --edit "Updated with the verified command."
      ```
      
    • comment-issue.md 5.9 KB
      # Issue Comment Workflow
      
      Post a comment on an existing GitHub issue (or PR — on GitHub's data model, a PR is an issue with extras, so
      `gh issue comment` works for both). Use the same informal, conversational tone as `create-issue.md`.
      
      ## Validate Prerequisites
      
      See `context.md > Auth Validation`. The issue context read below is the auth check.
      
      ## Parse Arguments
      
      Expected forms:
      
      - `{owner}/{repo}#{number} {comment context}`
      - `{owner}/{repo} {number} {comment context}`
      - `#{number} {comment context}` (infer repo from working directory)
      - `{number} {comment context}` (infer repo from working directory)
      - `{url} {comment context}` (parse owner/repo/number from the issue URL)
      
      Rules:
      
      - IF first token matches `https://github.com/{owner}/{repo}/issues/{number}` or `.../pull/{number}`: parse owner, repo,
        number from URL
      - ELSE IF first token matches `{owner}/{repo}#{number}`: split on `#`
      - ELSE IF first token matches `{owner}/{repo}`: use it as repository; next token must be the issue number (strip leading
        `#`)
      - ELSE IF first token matches `#?{number}`: use it as issue number, infer repo from the local `origin` remote via
        `<skill-dir>/scripts/yeet-context.sh issue`
      - ELSE: ERROR "Couldn't figure out the issue. Pass `owner/repo#123` or a GitHub issue URL."
      
      Everything after the issue identifier is the **comment context** — the user's description of what they want to say. May
      be empty if the user just wants a canned reaction (e.g., "+1", "same here").
      
      ## Fetch Issue Context
      
      Always read the issue before writing the comment — never generate a reply based on the user's context alone, because
      tone/terminology should match the thread.
      
      ```bash
      <skill-dir>/scripts/yeet-context.sh issue "{owner}/{repo}" {number}
      ```
      
      Resolve `<skill-dir>` to the absolute directory containing the owning `SKILL.md`. Before writing, load
      `posting.md > External-disclosure Review` and review the exact comment body.
      
      Analyze:
      
      - The issue title and body — what's actually being discussed
      - The latest `comments(last: 5)` nodes — what's the current state of the conversation
      - Who's been participating — don't ping people who are already in the thread
      - Whether the issue is open or closed — adjust tone accordingly (closed issues may need "reopen?" framing)
      - Any labels hinting at the issue type (bug, feature, question)
      
      ## Generate Comment Body
      
      See `writing.md > Informal Tone` — same rules apply. Write like a colleague chiming in on a thread, not a changelog
      entry.
      
      ### Guidelines
      
      - **Lead with the point.** If it's a reproduction, show it. If it's a "+1", say so and explain what specifically bit
        you. If it's a proposed fix, link or paste it.
      - **Match the thread's register.** If the thread is technical and terse, don't be fluffy. If it's collaborative and
        exploratory, don't be curt.
      - **No AI throat-clearing.** Skip "Great question!", "Thanks for filing this!", "Just chiming in here...". Go straight
        to substance.
      - **No fake enthusiasm.** Don't over-promise ("I'll dig into this right away") unless the user explicitly said so.
      - **Cite specifics.** If you reference code, link to it (see `writing.md > Link Formatting`). If you reference a commit
        or PR, link it.
      - **Use admonitions sparingly.** They are almost never needed in a comment; reserve them for genuine warnings.
      
      ### Comment Shapes
      
      Pick the shape that fits the context. Don't force structure onto short comments.
      
      **Short reply** (most comments — default to this):
      
      ```markdown
      Hitting this too on {platform}. Repro: {minimal steps}.
      ```
      
      **Repro report**:
      
      ````markdown
      Reproduced on {platform} with {version}. Steps:
      
      1. {step}
      2. {step}
      3. {step}
      
      Expected: {...}
      
      Actual: {...}
      
      Relevant log:
      
      ```
      {log snippet}
      ```
      ````
      
      **Proposed solution**:
      
      ```markdown
      Looked into this — the issue is in [`{path}`](https://github.com/{owner}/{repo}/blob/main/{path}#L{line}) where {short
      explanation}.
      
      One fix: {short description}. PR: #{number} (if applicable).
      ```
      
      **Follow-up question**:
      
      ```markdown
      Quick question on this — {specific question}. Context: {one sentence of why you're asking}.
      ```
      
      **Closing update** (when you fixed/resolved something and need to comment):
      
      ```markdown
      Fixed in {PR or commit link}. {One sentence on the root cause if non-obvious.}
      ```
      
      ### Platform / Environment
      
      If the comment includes environment info, follow `context.md > Platform String Normalization`. Don't paste raw `uname`
      output.
      
      ### File / Code References
      
      Follow `writing.md > Link Formatting`. Prefer permalinks (commit SHA) over branch links when citing specific lines,
      since branch links rot.
      
      ## Post the Comment
      
      ```bash
      gh issue comment {number} \
        --repo "{owner}/{repo}" \
        --body "$(cat <<'EOF'
      {comment body}
      EOF
      )"
      ```
      
      See `writing.md > HEREDOC Syntax` for why the quoted `'EOF'` matters.
      
      Display the verified anchored URL with the `### ✅ Comment posted` receipt from `SKILL.md`.
      
      The URL with the comment anchor is returned by `gh` on success — parse it from the output.
      
      On failure: follow `posting.md > Error Handling and Idempotency`; reread the issue comments before any retry and do not
      post a duplicate.
      
      ## Editing a Prior Comment
      
      If the user asks to "edit my last comment" or "update the comment I just posted", use `gh issue comment --edit-last`
      (operates on the most recent comment by the authenticated user on that issue):
      
      ```bash
      gh issue comment {number} \
        --repo "{owner}/{repo}" \
        --edit-last \
        --body "$(cat <<'EOF'
      {new body}
      EOF
      )"
      ```
      
      ## Examples
      
      ```bash
      # Infer repo from cwd, comment on issue 42
      42 "can repro on macOS Tahoe, same stack trace"
      
      # Explicit repo via owner/repo#number
      vercel/next.js#12345 "+1, also hitting this in 15.0.3"
      
      # Full URL
      https://github.com/facebook/react/issues/99999 "proposed fix in PR #100000"
      
      # Short +1 (user leaves context empty — generate a minimal acknowledgment)
      sablier-labs/command-center#10
      
      # Edit the last comment
      --edit-last vercel/next.js#12345 "actually, repro is flaky — only fires on cold cache"
      ```
      
    • context.md 5.6 KB
      # Contribution Context
      
      Load only the sections linked by the active workflow.
      
      ## Auth Validation
      
      Do not run unconditional `gh auth status`. Treat the first required read-only `gh` command as auth validation. Resolve
      the bundled helper relative to the skill directory, never the target repository:
      
      Resolve `<skill-dir>` to the absolute directory containing the owning `SKILL.md` before running these commands:
      
      ```sh
      <skill-dir>/scripts/yeet-context.sh repo "<owner>/<repo>" [--issue-templates] [--discussion-templates] [--discussion-categories]
      <skill-dir>/scripts/yeet-context.sh issue "<owner>/<repo>" <number>
      <skill-dir>/scripts/yeet-context.sh labels "<owner>/<repo>"
      ```
      
      If it fails with an auth error, stop with: `Run gh auth login first`.
      
      ## Repository Context
      
      Collect repository context once and reuse it: authenticated login, repository identity and permission, default branch,
      and only the templates/categories required by the workflow.
      
      ## Issue Metadata Permissions
      
      Filter metadata for every issue create/edit command using cached `repository.viewerPermission` before fetching labels,
      issue types, or other metadata IDs. Apply these defaults unless existing action-specific capability evidence says
      otherwise:
      
      | Metadata mutation                                  | Required repository permission            |
      | -------------------------------------------------- | ----------------------------------------- |
      | Labels, assignees, parent/sub-issues, dependencies | `TRIAGE`, `WRITE`, `MAINTAIN`, or `ADMIN` |
      | Issue type (`--type`, `--remove-type`), milestone  | `WRITE`, `MAINTAIN`, or `ADMIN`           |
      
      The type/milestone default follows GitHub's [issue API permissions](https://docs.github.com/en/rest/issues/issues). For
      relationships across repositories, check the required permission in each affected repository.
      
      With `READ`, missing, or unknown permission, omit privileged metadata flags. Issue authorship, a live template's
      `type: Bug`, and permission to edit the title/body do not grant metadata permission. Do not use a write as a permission
      probe or fetch metadata that will be omitted. Continue permitted content work and briefly report skipped metadata;
      explicitly requested metadata that cannot be applied remains incomplete.
      
      Project access is independent of repository permission. Apply template-only project entries only when project write
      access is already known; for an explicitly requested project, resolve its access only if needed. Report skipped entries
      without blocking issue creation. A permission denial invalidates the corresponding cached capability; do not retry the
      denied mutation without new authorization evidence.
      
      ## Fetch Repo Labels
      
      Fetch labels only after the permission check allows them and the workflow needs template labels, requested label edits,
      or semantic labels in an owner-managed repository.
      
      Treat the live `name` and `description` list as authoritative. Match intent, use the smallest set, respect template
      labels, and never invent labels. Skip maintainer workflow labels such as `good first issue`, `needs triage`,
      `duplicate`, or `stale` on creation. An empty list is a valid no-label result; a failed fetch is an error.
      
      ## Template Metadata and Issue Forms
      
      Issue-form YAML may define assignees, labels, type, and projects, but `gh issue create --body-file` does not execute the
      form. Render the relevant fields into Markdown and pass only metadata allowed by `Issue Metadata Permissions`. Apply
      permitted project metadata only after creation with `gh project item-add`; a failed project add leaves a created issue
      and must not trigger issue recreation. Do not combine `--template` with `--body` or `--body-file`.
      
      ## Platform String Normalization
      
      Use `scripts/get-macos-version.sh` for macOS fields. Skip environment details for repositories owned by the
      authenticated viewer or `sablier-labs` unless the user explicitly asks; preserve required upstream template enums.
      
      ## Image Uploads
      
      This workflow applies to issue and discussion creation and updates. Parse repeated `--image <path>` arguments and the
      optional `--image-release` flag. Resolve every path to a readable local file, preserve argument order, and run an
      external-disclosure review on the files before uploading them.
      
      GitHub has no public attachment upload API. Try these paths in order:
      
      1. If `gh img` is installed, run `gh img --repo "<owner>/<repo>" <paths...>` and capture its Markdown output.
      2. If `gh img` is unavailable or clearly fails before upload, use `gh attach <paths...> -R "<owner>/<repo>" --markdown`
         only when `gh extension list` identifies the command as `sudosubin/gh-attach`. Other `gh attach` extensions have
         incompatible interfaces; do not guess their flags or install an extension automatically.
      3. Use a release asset only when `--image-release` explicitly authorizes that separate external mutation.
      
      Advance to the next path only when the prior uploader is unavailable or clearly failed before uploading anything. A
      nonzero exit with any asset output is an ambiguous partial upload: stop and report the uploaded paths and failure rather
      than retrying and creating duplicates. Continue only after every requested image produced usable Markdown; otherwise
      leave an existing artifact unchanged or stop before creating a new one. Do not create a placeholder artifact just to
      upload an image.
      
      Place the Markdown in the user-requested field or section when specified. Otherwise prefer a live template field for
      images, reproduction material, or uploads; failing that, append to an existing `## Images` section or create that
      section at the end. Preserve the rest of an existing body verbatim and keep images in input order.
      
    • create-discussion.md 4.5 KB
      # Discussion Creation Workflow
      
      Create a GitHub discussion with the native `gh discussion` command. Load [posting.md](posting.md) before the write.
      
      ## Validate Prerequisites
      
      Requires authenticated GitHub CLI >= 2.97.0. See `context.md > Auth Validation`; the repository-context read is the auth
      check. Handle image options through `context.md > Image Uploads`, then run `posting.md > External-disclosure Review` on
      the final title, body, labels, and attachments.
      
      ## Parse Repository Argument
      
      - If the first token matches `owner/repo`, use it as the repository.
      - Otherwise infer the repository from the local `origin` remote via `<skill-dir>/scripts/yeet-context.sh repo`.
      - Error if no repository can be inferred and none was supplied.
      
      Parse `--check`; resolve `<skill-dir>` as the absolute directory containing the owning `SKILL.md`.
      
      ## Collect Repository Context
      
      Fetch repository identity, live categories, and discussion-template entries once:
      
      ```bash
      <skill-dir>/scripts/yeet-context.sh repo "<owner>/<repo>" --discussion-categories --discussion-templates
      ```
      
      Cache `repository.discussionCategories.nodes` and `repository.discussionTemplateTree.entries`. Treat every live
      category's `name`, `slug`, and `description` as authoritative. Do not use a static category table.
      
      ## Check for Similar Discussions (Optional)
      
      If `--check` is present, search title/body terms in every state and show matches without adding a confirmation gate:
      
      ```bash
      gh discussion list --repo "<owner>/<repo>" --state all --search "<key terms>" \
        --limit 10 --json number,title,url,closed
      ```
      
      Display matches under `### 🔎 Similar discussions`, say `Creation is continuing`, and continue. If the create command
      later fails, follow `posting.md > Error Handling and Idempotency` before retrying; never recreate a possible match.
      
      ## Select Discussion Category
      
      Infer a category from the user's title and description by matching the live `name`, `slug`, and `description`. A
      supplied category may match its exact name or slug. If no category matches, or more than one category is plausible, stop
      and ask the user to choose from the live candidates with their descriptions. Never default to a guessed or invented
      category.
      
      ## Check for Discussion Templates
      
      Keep live template-tree entries ending in `.yml` or `.yaml`. A discussion form filename normally matches the category
      slug. If multiple templates match, stop and ask the user to choose; if none matches, use the selected category without a
      form.
      
      For a selected form, fetch it from the default branch:
      
      ```bash
      gh api repos/<owner>/<repo>/contents/.github/DISCUSSION_TEMPLATE/<template-name> --jq '.content' | base64 -d
      ```
      
      Parse `title`, `labels`, and `body`. If labels are declared, fetch the live label set with
      `<skill-dir>/scripts/yeet-context.sh labels "<owner>/<repo>"`; preserve declared labels whose exact live names match,
      deduplicate, and pass them with `--label`. Stop for a declared label that is not live rather than inventing it. For
      `textarea`/`input`, render a section header; for `dropdown`, select only a live option; for `checkboxes`, check an
      attestation only when repository or user evidence verifies it. Stop for any required checkbox whose attestation cannot
      be verified. Skip `markdown` fields.
      
      ## Generate Title and Body
      
      See `writing.md > Informal Tone`.
      
      If the form supplies a title prefix, prepend it. Otherwise write a clear 5–10-word title. Render form fields as
      `### <field label>` sections. Without a form, use:
      
      ```markdown
      ## Context
      
      [What is this discussion about?]
      
      ## Discussion Points
      
      [Key points or questions]
      
      ## Additional Context
      
      [Background information, if applicable]
      ```
      
      If images were requested, complete `context.md > Image Uploads` before the disclosure review.
      
      ## Create and Verify the Discussion
      
      Use the live category name or slug and the native command:
      
      ```bash
      gh discussion create --repo "<owner>/<repo>" \
        --category "<category-name-or-slug>" \
        --title "<title>" \
        --body-file "<body-file>" \
        --label "<template-labels>"
      ```
      
      Omit `--label` when no labels apply. Read the returned URL (or list the distinctive title in `--state all`) to verify
      the discussion, then display the `### 🚀 Discussion created` receipt from `SKILL.md`. On any ambiguous result, follow
      `posting.md` and do not rerun creation.
      
      ## Examples
      
      ```text
      # Simple discussion in current repository
      "Proposal for adding dark mode support"
      
      # Explicit repository
      PaulRBerg/dotfiles "Ideas for improving the zsh setup"
      
      # With a non-blocking duplicate search
      --check "How to configure custom routes"
      ```
      
    • create-issue.md 4.6 KB
      # Issue Creation Workflow
      
      Create a GitHub issue from repository evidence and the selected live template.
      
      ## Context and Selection
      
      Parse an optional leading `owner/repo`; otherwise infer the current repository. Repo-specific workflows supply their
      fixed target and never infer it. Fetch authenticated context once:
      
      ```sh
      <skill-dir>/scripts/yeet-context.sh repo "<owner>/<repo>" --issue-templates
      ```
      
      Resolve `<skill-dir>` to the absolute directory containing the owning `SKILL.md`. Cache viewer login, permission,
      default branch, and template entries. Parse `--check`; handle image options through `context.md > Image Uploads`. With
      `--check`, search similar issues in all states and show results without adding a confirmation gate.
      
      Select the best template from the user's intent. This is an agent decision. Prefer YAML when a suitable YAML and
      Markdown template coexist. Exclude `config.yml`.
      
      ## YAML Issue Forms
      
      Inspect the selected live form; do not mirror its schema in prose:
      
      ```sh
      uv run "<skill-dir>/scripts/issue-form.py" inspect \
        --repo "<owner>/<repo>" --template "<name>.yml" > <form.json>
      ```
      
      The form JSON reports title prefix, assignees, labels, projects, issue type, field IDs, descriptions, render modes,
      defaults, dropdown options, upload constraints, multi-select behavior, required flags, and checkbox attestations.
      Compose answers in a separate JSON object keyed by field ID. Choose dropdown values only from the reported options. A
      required checkbox may be set true only when user or repository evidence verifies the attestation; ask for an
      unverifiable required fact.
      
      Render locally:
      
      ```sh
      uv run "<skill-dir>/scripts/issue-form.py" render \
        --form <form.json> --answers <answers.json> > <rendered.json>
      ```
      
      The renderer rejects missing required values, invalid dropdowns, unknown IDs, and unverified required checkboxes. Use
      its body exactly; filter its posting metadata through `context.md > Issue Metadata Permissions`. The agent still owns
      answer wording, the external-disclosure review, title text after the live prefix, semantic labels, and the external
      post.
      
      For a selected Markdown template, fetch it live and populate its existing structure. If no template applies, use the
      smallest useful `Problem`, `Solution`, and optional affected-files structure. Do not use `gh issue create --template`
      with an automated body.
      
      For checklist-style issues, mirror the user's stated structure literally: a single list unless the user requested
      sections, preserving their stated ordering and casing. Never introduce unasked groupings such as Completed/Planned.
      
      ## Labels, Type, and Title
      
      Apply `context.md > Issue Metadata Permissions` before resolving metadata. Add semantic labels only when the owner is
      the viewer or `sablier-labs`, after matching against the live label set; never invent labels.
      
      For YAML, prepend the rendered `posting.titlePrefix` and pass permitted `posting.assignees`, labels, and
      `posting.issueType` when present. Preserve every applicable project entry; merge permitted live template labels with
      agent-selected semantic labels and deduplicate. Write a concise title from the actual issue. For explicit issue
      metadata, accept `--type`, `--parent`, `--blocked-by`, and `--blocking` using current `gh issue create` flags; validate
      referenced issue numbers or URLs before posting.
      
      An explicitly requested project title may use `gh issue create --project` when project access allows it. Do not pass
      issue-form `projects` through that flag. After the issue URL is verified, apply each permitted form project entry with
      `posting.md > Project-Template Metadata` and `gh project item-add`.
      
      Start with the content-only command, which is also the normal `READ` path:
      
      ```bash
      gh issue create --repo "<owner>/<repo>" --title "<title>" --body-file "<body-file>"
      ```
      
      Append metadata flags only when values are present and the permission check permits them. In particular, a rendered
      `posting.issueType: "Bug"` with `viewerPermission: "READ"` must not produce `--type Bug`.
      
      ## Images and Posting
      
      If images were requested, complete `context.md > Image Uploads` before creating the issue.
      
      Run `posting.md > External-disclosure Review` on the title, rendered body, labels, type, project identifiers, metadata,
      and attachments. Then post with `gh issue create --repo`, `--title`, and `--body-file`, adding `--assignee`, `--label`,
      `--type`, `--parent`, `--blocked-by`, and `--blocking` only when applicable. Post directly because creation was
      requested. On failure, follow `posting.md > Error Handling and Idempotency` before any retry.
      
      Finish with the verified URL and the `### 🚀 Issue created` receipt from `SKILL.md`.
      
    • create-pr.md 3.1 KB
      # Pull Request Workflow
      
      Create GitHub pull requests with semantic change analysis, intelligent defaults, and minimal friction.
      
      ## Validate Prerequisites
      
      See `context.md > Auth Validation` for GitHub authentication. The repository context read below is the auth check.
      
      **Collect repo context once** (also confirms the repo and remote exist):
      
      ```bash
      <skill-dir>/scripts/yeet-context.sh repo
      ```
      
      Resolve `<skill-dir>` to the absolute directory containing the owning `SKILL.md`. Before pushing or creating the PR,
      load `posting.md > External-disclosure Review` and review the exact title, body, reviewers, labels, and branch contents
      that will be published.
      
      Use `repository.defaultBranchRef.name` as the default base branch unless args specify a base.
      
      ## Parse Arguments Naturally
      
      - "draft" or "--draft" → draft mode
      - "test-plan" or "--test-plan" → include test plan section
      - "to X" or "base=X" → target branch X (default: repo default branch)
      - If no base was passed, use `repository.defaultBranchRef.name` from repo context
      - "review=X" or "reviewers=X" → add reviewer(s)
      - Quoted text → custom title
      - Everything else → additional context for description
      
      ## Fetch Base And Check Commits
      
      Fetch the selected base branch only:
      
      ```bash
      git fetch origin "+refs/heads/$base_branch:refs/remotes/origin/$base_branch"
      ```
      
      - Commits ahead: `git rev-list --count origin/$base_branch..HEAD 2>/dev/null`
      - IF 0 commits: ERROR "No commits to create PR from"
      
      ## Semantic Change Analysis
      
      Follow the process in `writing.md > Semantic Change Analysis`. Write the title and body in the voice from
      `writing.md > Informal Tone`.
      
      **Test Plan** (only if `--test-plan` flag): Add a "## Test Plan" section with testing/validation approach, manual steps,
      or checklist.
      
      **Identify reviewers:**
      
      - Check for CODEOWNERS file: `git ls-files | rg CODEOWNERS`
      - If exists, extract owners for changed files
      - Otherwise use git blame for frequent contributors
      - Combine with reviewers from arguments
      
      Use an admonition only for a material warning that would otherwise be missed.
      
      **Issue linking:** If an issue number was referenced in the conversation (e.g., "fixes #42", "for issue #100"), append
      `Closes #NUMBER` to the PR body so the issue auto-closes on merge.
      
      ## Check for Existing PR
      
      ```bash
      BRANCH=$(git branch --show-current)
      gh pr list --state all --head "$BRANCH" --json number,url --jq '.[0]' 2>/dev/null
      ```
      
      IF existing PR found: ERROR "PR already exists for this branch: $URL". Do not create or update.
      
      ## Create New PR
      
      **Push branch:**
      
      ```bash
      git push -u origin "$BRANCH"
      ```
      
      **Create PR:**
      
      ```bash
      gh pr create \
        --title "$generated_title" \
        --body "$generated_body" \
        --base "$base_branch" \
        $(test "$draft_mode" = "true" && echo "--draft") \
        $(test -n "$reviewers" && echo "--reviewer $reviewers")
      ```
      
      Display the verified URL with the `### 🚀 PR created` receipt from `SKILL.md`.
      
      On failure: check the specific error (auth, branch protection, validation) and follow
      [posting.md > Error Handling and Idempotency](posting.md#error-handling-and-idempotency) — run the idempotency check
      before any retry.
      
    • issue-claude-code.md 2.4 KB
      # Claude Code Issue Workflow
      
      Create an issue in `anthropics/claude-code`. Every `gh` command must use that repository; never infer it from the
      working directory.
      
      ## Live Form Workflow
      
      1. Fetch authenticated repository/template context with
         `<skill-dir>/scripts/yeet-context.sh repo anthropics/claude-code --issue-templates`. Resolve `<skill-dir>` to the
         absolute directory containing the owning `SKILL.md`.
      2. Select the live YAML form matching the user's intent: bug, feature request, documentation, or model behavior. Ask
         only when that semantic choice is genuinely ambiguous.
      3. Run `<skill-dir>/scripts/issue-form.py inspect --repo anthropics/claude-code --template <selected.yml>` and compose
         answers keyed by the returned field IDs. Do not compare SHAs or consult a static template mirror.
      4. Gather only environment facts requested by the live form. `claude --version` supplies the Claude Code version.
         Normalize OS and terminal values to exact live dropdown options; place precise versions in a free-text field when
         useful. Infer Anthropic API, Bedrock, or Vertex only from environment/user evidence.
      5. Mark a required checkbox true only when its attestation is verified. Render through
         `<skill-dir>/scripts/issue-form.py render`; resolve every reported missing/invalid answer before posting.
      6. Run `posting.md > External-disclosure Review` on the agent-authored answers and rendered body. Compose a concise
         title after the live prefix. Filter live template metadata through `context.md > Issue Metadata Permissions`.
      7. Post with `gh issue create --repo anthropics/claude-code --title ... --body-file ...`, adding permitted labels and
         issue type from rendered metadata. On failure, follow `posting.md > Error Handling and Idempotency`. If permitted
         rendered metadata includes `projects`, apply each entry after creation with `posting.md > Project-Template Metadata`;
         a project-add failure is partial completion and never an issue-recreation trigger.
      
      The helper owns form parsing and exact body structure. The agent owns template selection, environment interpretation,
      answer/title writing, the external-disclosure review, and posting. Follow `posting.md > Error Handling and Idempotency`
      after any ambiguous result; never recreate a possible partial issue.
      
      For a comment on an existing issue, use `posting.md > Comment on Existing Issue` with this fixed repository. Finish a
      successful creation with the verified URL and the `### 🚀 Issue created` receipt.
      
    • issue-codex-cli.md 2.5 KB
      # Codex Issue Workflow
      
      Create an issue in `openai/codex`. Every `gh` command must use that repository; never infer it from the working
      directory.
      
      ## Live Form Workflow
      
      1. Fetch authenticated repository/template context with
         `<skill-dir>/scripts/yeet-context.sh repo openai/codex --issue-templates`. Resolve `<skill-dir>` to the absolute
         directory containing the owning `SKILL.md`.
      2. Select the live YAML form matching the affected surface and intent: app, extension, CLI, generic bug, feature, or
         docs. Template selection and whether an existing discussion/issue is a better target remain agent judgments.
      3. Run `<skill-dir>/scripts/issue-form.py inspect --repo openai/codex --template <selected.yml>`. Do not compare SHAs or
         consult a static template mirror.
      4. Compose answers keyed by returned field IDs. Gather only requested environment facts: `codex --version`, relevant
         extension/app version, OS, shell/terminal, model/config, reproduction, logs, and regression range. Match every
         dropdown and multi-select to live options exactly.
      5. Mark a required checkbox true only when repository/user evidence verifies the attestation. Render through
         `<skill-dir>/scripts/issue-form.py render`; resolve all missing or invalid answers before posting.
      6. Run `posting.md > External-disclosure Review` on the title, answers, logs, config, and rendered body. Remove
         credentials, unsuitable private paths or repository names, and unrelated transcript material. Compose a concise title
         after the live prefix.
      7. Post with `gh issue create --repo openai/codex --title ... --body-file ...`, adding labels/type from rendered
         metadata only when cached permission allows them. Follow `posting.md` idempotency handling before retrying any
         failure. If rendered metadata includes `projects`, apply each entry after creation with
         `posting.md > Project-Template Metadata`; a project-add failure is partial completion and never an issue-recreation
         trigger.
      
      The helper owns form parsing and exact body structure. The agent owns template selection, answer/title composition,
      environment interpretation, the external-disclosure review, semantic labels, and posting. Follow
      `posting.md > Error Handling and Idempotency` after any ambiguous result; never recreate a possible partial issue.
      
      For a comment on an existing issue, use `posting.md > Comment on Existing Issue` with this fixed repository. Finish a
      successful creation with the verified URL and the `### 🚀 Issue created` receipt.
      
    • issue-sablier.md 2.3 KB
      # Sablier Issue Workflow
      
      Create issues in `sablier-labs/*` repositories. Labels are always applied (user is org owner). Sablier repos don't use
      GitHub issue templates.
      
      ## Validate Prerequisites
      
      See `context.md > Auth Validation`. The label fetch below is the auth check.
      
      ## Parse Repository Argument
      
      The **first token** is the repo name (without org prefix) → `sablier-labs/{repo_name}`. Remove it from arguments;
      remaining text is the issue description. Parse `--check` and handle it per
      `posting.md > Error Handling and Idempotency`.
      
      Example: `lockup "Bug in cliff streams"` → `repository = sablier-labs/lockup`
      
      ## Apply Labels
      
      Sablier repos are owner-managed — labels always apply. Follow `context.md > Fetch Repo Labels` to fetch the live label
      set once and pick labels semantically. Scope labels (e.g., `scope: frontend`, `scope: evm`) are discovered organically
      from the fetched list for repos that define them — no special-case for `command-center`.
      
      ## Generate Title and Body
      
      ### Title
      
      Clear, concise summary (5-10 words).
      
      ### Body
      
      Default template:
      
      ```
      ## Problem
      
      [Extracted from user description]
      
      ## Solution
      
      [If provided, otherwise "TBD"]
      
      ## Files Affected
      
      <details><summary>Toggle to see affected files</summary>
      <p>
      
      - [{filename}](https://github.com/sablier-labs/{repo_name}/blob/main/{path})
      
      </p>
      </details>
      ```
      
      Use task lists only for genuinely trackable work and tables only for repeated comparable fields. Order items per
      `writing.md > List Ordering`. See `writing.md > Link Formatting` for links. Omit "Files Affected" if no files are
      specified.
      
      ## Create the Issue
      
      Run `posting.md > External-disclosure Review` on the title, body, labels, and attachments before posting. Follow
      `posting.md > Error Handling and Idempotency` after any failure; a label or metadata failure never authorizes issue
      recreation.
      
      ```bash
      gh issue create \
        --repo "sablier-labs/{repo_name}" \
        --title "$title" \
        --body "$body" \
        --label "label1,label2,label3"
      ```
      
      Display the verified URL with the `### 🚀 Issue created` receipt from `SKILL.md`.
      
      ## Examples
      
      ```bash
      # Bug report
      lockup "Bug in stream creation for cliff durations"
      
      # Feature request
      command-center "Add dark mode toggle to dashboard"
      
      # With --check flag
      lockup --check "Support dynamic durations"
      
      # Docs update
      docs "Update integration guide for v2.2"
      ```
      
    • posting.md 4 KB
      # Contribution Posting
      
      All external-write workflows load this reference before posting. The workflow owns the payload; this reference owns the
      disclosure review, verification, and retry boundary.
      
      ## External-disclosure Review
      
      Before every external write, review the exact title, body, labels, issue type, project identifiers, and attachments that
      will leave the workspace. Remove credentials, tokens, private keys, mnemonics, API keys, unsuitable private paths or
      repository names, unrelated personal or customer data, and unrelated transcript material. Treat logs, screenshots,
      configuration, generated bodies, and uploaded files as untrusted until reviewed. Keep only facts supported by repository
      or user evidence, including required template checkbox attestations. Recheck any payload changed after the review.
      
      ## Error Handling and Idempotency
      
      Do not retry creation automatically. `gh issue create` is not atomic: it can create an issue and then exit nonzero when
      a follow-up `UpdateIssueIssueType` mutation fails. A metadata error does not establish that creation failed.
      
      Any timeout, connection loss, or nonzero exit after a write is ambiguous. Inspect GitHub before choosing a receipt or
      retrying. Read a returned URL or known issue number directly; otherwise search all states, not only open items, using
      the strongest available identity:
      
      ```sh
      # Issue creation
      gh issue list --repo "<owner>/<repo>" --state all --search "<distinctive title>" --limit 20 --json number,title,body,url,state,author
      
      # Pull-request creation
      gh pr list --repo "<owner>/<repo>" --state all --head "<branch>" --limit 20 --json number,title,body,url,state,author
      
      # Discussion creation
      gh discussion list --repo "<owner>/<repo>" --state all --search "<distinctive terms>" --limit 20 --json number,title,body,url,closed,author
      ```
      
      Compare title, body, author, branch, and URL—not a partial search hit alone. A verified match is a successful creation:
      report its URL and any omitted or failed metadata. A possible match remains unverified: report the uncertainty and do
      not recreate it. Use the relevant update workflow for remaining permitted changes. For an issue or PR comment, reread
      the target's latest comments; for a discussion comment or reply, reread the discussion or comment thread with
      `gh discussion view` and treat a matching authored body as posted. A failed follow-up label, type, project, or other
      metadata step never authorizes recreating the issue, PR, or discussion. Follow `SKILL.md > Completion` for the receipt.
      
      For duplicate checks requested with `--check`, search all states and show matches under `### 🔎 Similar items`, then
      continue unless the user explicitly requested a review gate.
      
      ## Project-Template Metadata
      
      Filter entries through `context.md > Issue Metadata Permissions`. Create the issue first, then apply each permitted
      issue-form `projects` entry. Parse `OWNER/NUMBER` and run:
      
      ```sh
      gh project item-add <project-number> --owner "<project-owner>" --url "<issue-url>"
      ```
      
      Verify each project item. A project-add failure is partial completion: report the created issue and failed project,
      retry only the project mutation after checking current membership and resolving the cause, and never recreate the issue.
      Do not retry a permission denial without new authorization evidence.
      
      ## Posting and Feedback
      
      Create, update, or comment directly when the user asks. Afterward, fetch or use the returned URL and report what changed
      using the receipt contract in `SKILL.md`. For a successful creation, verify the returned URL by reading the created
      item. For updates and comments, reread the target and report the changed artifact once. On error, report the verified
      outcome, failed step, idempotency check, and next action.
      
      ## Comment on Existing Issue
      
      Review the exact comment body under `External-disclosure Review`, then post:
      
      ```sh
      gh issue comment <number> --repo "<owner>/<repo>" --body "$(cat <<'EOF'
      <comment>
      EOF
      )"
      ```
      
      Return the issue URL after the comment is posted. On an ambiguous result, reread the issue comments before retrying.
      
    • update-discussion.md 4.7 KB
      # Discussion Update Workflow
      
      Update an existing GitHub discussion's title, body, category, labels, or images with `gh discussion edit`. Comment
      editing belongs to `comment-discussion.md`; `gh discussion` has no close or reopen command, so state changes are out of
      scope. Load [posting.md](posting.md) before the write.
      
      ## Validate Prerequisites
      
      The installed GitHub CLI must be authenticated and >= 2.97.0, with `gh discussion view` and `gh discussion edit`
      available. These commands are in preview; if either is unavailable, stop and tell the user to upgrade GitHub CLI rather
      than inventing a GraphQL fallback. The discussion read below is the authentication check.
      
      ## Parse Arguments
      
      Accept these forms:
      
      - `{owner}/{repo}#{number} {update instructions}`
      - `{owner}/{repo} {number} {update instructions}`
      - `#{number} {update instructions}` (infer the repository from the current directory)
      - `{number} {update instructions}` (infer the repository from the current directory)
      - `https://github.com/{owner}/{repo}/discussions/{number} {update instructions}`
      
      For a URL, parse the owner, repository, and number. For `owner/repo#number`, split on `#`. For `owner/repo number`, use
      the first two tokens. For a local number, resolve the repository with `<skill-dir>/scripts/yeet-context.sh repo`.
      Resolve `<skill-dir>` to the absolute directory containing the owning `SKILL.md`. Reject other targets with:
      `Couldn't figure out the discussion. Pass owner/repo#123 or a GitHub discussion URL.` Everything after the target is the
      natural-language update instruction; parse repeated `--image <path>` and optional `--image-release` through
      `context.md > Image Uploads`.
      
      ## Fetch Discussion Context
      
      Always read the discussion before editing:
      
      ```sh
      gh discussion view <number> --repo "<owner>/<repo>" \
        --json number,url,title,body,category,labels
      ```
      
      Keep the existing body verbatim unless the user asks to rewrite or append to it.
      
      ## Interpret and Validate Updates
      
      Multiple intents may apply at once:
      
      | Intent          | Cue or input                       | `gh discussion edit` flag              |
      | --------------- | ---------------------------------- | -------------------------------------- |
      | Update title    | "title", "rename"                  | `--title`                              |
      | Regenerate body | "description", "body", "rewrite"   | `--body-file`                          |
      | Append to body  | "add to body", "append"            | `--body-file` with existing + appended |
      | Change category | "category"                         | `--category`                           |
      | Add labels      | "label X", "tag as X", "add label" | `--add-label`                          |
      | Remove labels   | "unlabel", "remove label"          | `--remove-label`                       |
      | Add images      | `--image <path>`                   | `--body-file` after shared upload      |
      
      If no update instruction remains, stop with: `Tell me what to update — title, body, category, labels, or images.`
      
      For a body rewrite, follow `writing.md > Informal Tone` and preserve recognizable template structure. For an append,
      retain the current body byte-for-byte, add a blank line, then add the requested content. If images were requested,
      complete `context.md > Image Uploads` and treat its result as the body update; when combined with a rewrite, place the
      images in the regenerated body.
      
      For a category change, fetch live categories with:
      
      ```sh
      <skill-dir>/scripts/yeet-context.sh repo "<owner>/<repo>" --discussion-categories
      ```
      
      Match the requested category against the live name or slug and reject an unknown category. For label additions, follow
      `context.md > Fetch Repo Labels`; reject unknown labels and do not create them. Removal may name only labels currently
      present on the discussion.
      
      ## Execute and Verify
      
      Build one non-interactive command containing every requested edit:
      
      ```sh
      gh discussion edit <number> --repo "<owner>/<repo>" \
        [--title "<title>"] \
        [--body-file "<body-file>"] \
        [--category "<category>"] \
        [--add-label "<label1,label2>"] \
        [--remove-label "<label3>"]
      ```
      
      After the edit, repeat the context read and verify every requested field. Display its URL with the
      `### ✅ Discussion updated` receipt from `SKILL.md` and one line naming the changed fields.
      
      On failure, read the discussion again and follow `posting.md > Error Handling and Idempotency` before any retry. Do not
      retry automatically; report the concrete error, observed state, and next action.
      
      ## Examples
      
      ```text
      # Rename and recategorize
      owner/repo#42 "rename to 'CLI roadmap', category Ideas"
      
      # Append images without rewriting the existing body
      #42 --image ./before.png --image ./after.png "append these comparisons"
      
      # Replace the body and add a label
      https://github.com/owner/repo/discussions/42 "rewrite body and add label documentation"
      ```
      
    • update-issue.md 8.7 KB
      # Issue Update Workflow
      
      Update an existing GitHub issue — title, body, labels, assignees, or state. Mirrors `update-pr.md` in spirit: regenerate
      content semantically when asked, otherwise apply targeted edits.
      
      ## Validate Prerequisites
      
      See `context.md > Auth Validation`. The issue context read below is the auth check.
      
      ## Parse Arguments
      
      Expected forms (same as `comment-issue.md`):
      
      - `{owner}/{repo}#{number} {update instructions}`
      - `{owner}/{repo} {number} {update instructions}`
      - `#{number} {update instructions}` (infer repo from working directory)
      - `{number} {update instructions}` (infer repo from working directory)
      - `{url} {update instructions}` (parse owner/repo/number from the issue URL)
      
      Rules:
      
      - IF first token matches `https://github.com/{owner}/{repo}/issues/{number}` or `.../pull/{number}`: parse owner, repo,
        number from URL
      - ELSE IF first token matches `{owner}/{repo}#{number}`: split on `#`
      - ELSE IF first token matches `{owner}/{repo}`: use it as repository; next token must be the issue number (strip leading
        `#`)
      - ELSE IF first token matches `#?{number}`: use it as issue number, infer repo from the local `origin` remote via
        `<skill-dir>/scripts/yeet-context.sh issue`
      - ELSE: ERROR "Couldn't figure out the issue. Pass `owner/repo#123` or a GitHub issue URL."
      
      Everything after the issue identifier is the **update instructions** — natural-language description of what to change.
      
      ## Fetch Issue Context
      
      Always read the issue before editing — never regenerate based on the user's instructions alone.
      
      ```bash
      <skill-dir>/scripts/yeet-context.sh issue "{owner}/{repo}" {number}
      ```
      
      Resolve `<skill-dir>` to the absolute directory containing the owning `SKILL.md`. Before any write, load
      `posting.md > External-disclosure Review` and `posting.md > Error Handling and Idempotency`.
      
      ## Interpret Update Instructions
      
      Parse the instructions naturally — multiple intents may apply at once:
      
      | Intent            | Cue words                                          | `gh issue edit` flag                                 |
      | ----------------- | -------------------------------------------------- | ---------------------------------------------------- |
      | Update title      | "title", "rename", quoted text passed as new title | `--title`                                            |
      | Regenerate body   | "description", "body", "rewrite"                   | `--body`                                             |
      | Append to body    | "add to body", "append"                            | `--body` (preserve existing + append)                |
      | Add images        | `--image <path>`                                   | `--body` after shared image upload                   |
      | Add labels        | "label X", "tag as X", "add label"                 | `--add-label`                                        |
      | Remove labels     | "unlabel", "remove label"                          | `--remove-label`                                     |
      | Set issue type    | "type X", "classify as X"                          | `--type`                                             |
      | Remove issue type | "remove type"                                      | `--remove-type`                                      |
      | Set parent        | "make sub-issue of X", "parent X"                  | `--parent`                                           |
      | Remove parent     | "remove parent"                                    | `--remove-parent`                                    |
      | Add sub-issues    | "add sub-issue X"                                  | `--add-sub-issue`                                    |
      | Remove sub-issues | "remove sub-issue X"                               | `--remove-sub-issue`                                 |
      | Add blocked-by    | "blocked by X"                                     | `--add-blocked-by`                                   |
      | Remove blocked-by | "remove blocked-by X"                              | `--remove-blocked-by`                                |
      | Add blocking      | "blocking X"                                       | `--add-blocking`                                     |
      | Remove blocking   | "remove blocking X"                                | `--remove-blocking`                                  |
      | Assign user       | "assign X", "assign to X", `@user`                 | `--add-assignee`                                     |
      | Unassign user     | "unassign X"                                       | `--remove-assignee`                                  |
      | Set milestone     | "milestone X"                                      | `--milestone`                                        |
      | Close             | "close", "resolve"                                 | `gh issue close` (separate command)                  |
      | Close duplicate   | "duplicate of X", "close as duplicate"             | `gh issue close --duplicate-of X --reason duplicate` |
      | Reopen            | "reopen"                                           | `gh issue reopen` (separate command)                 |
      
      If user provides only an issue identifier with no instructions, ERROR: "Tell me what to update — title, body, images,
      labels, assignees, or state."
      
      ## Regenerate Title or Body
      
      Only when the user explicitly asks for regeneration ("rewrite the body", "fix the title").
      
      Follow `create-issue.md`'s template and body rules and `writing.md > Informal Tone`. Preserve any existing template
      structure (sections, admonitions, file links). If the issue uses a YAML template's section headers, keep them.
      
      For appends, preserve the existing body verbatim, then append the new content with a separator (blank line) — do not
      rewrite or echo the full existing body to the user.
      
      ## Images
      
      If images were requested, complete `context.md > Image Uploads` before editing the issue. Treat the resulting body as
      the body update and combine it with any other requested edits in one `gh issue edit` command. If another instruction
      regenerates the body, place the images in that regenerated body rather than the superseded original.
      
      ## Validate Labels Before Adding
      
      If adding labels, fetch the repo's label set per `context.md > Fetch Repo Labels` and confirm the requested labels exist
      (case-sensitive match on `name`). Skip this read for non-label edits.
      
      IF a requested label doesn't exist: ERROR with the list of valid label names. Do not auto-create labels.
      
      For owner-managed repos (owner = `viewer.login` from issue context or `sablier-labs`), when the user asks for a label by
      intent rather than exact name ("tag this as a bug"), match semantically against the fetched `name + description` pairs
      per the rubric in `context.md > Fetch Repo Labels`.
      
      ## Execute Update
      
      ```bash
      # Title only
      gh issue edit {number} --repo "{owner}/{repo}" --title "$new_title"
      
      # Body only
      gh issue edit {number} --repo "{owner}/{repo}" --body "$(cat <<'EOF'
      {new body}
      EOF
      )"
      
      # Labels
      gh issue edit {number} --repo "{owner}/{repo}" \
        --add-label "label1,label2" \
        --remove-label "label3"
      
      # Assignees
      gh issue edit {number} --repo "{owner}/{repo}" \
        --add-assignee "user1" \
        --remove-assignee "user2"
      
      # Type, hierarchy, and dependency metadata
      gh issue edit {number} --repo "{owner}/{repo}" --type "Bug"
      gh issue edit {number} --repo "{owner}/{repo}" --parent 100
      gh issue edit {number} --repo "{owner}/{repo}" --add-sub-issue 123
      gh issue edit {number} --repo "{owner}/{repo}" --add-blocked-by 200 --add-blocking 300
      
      # Combined edit
      gh issue edit {number} --repo "{owner}/{repo}" \
        --title "$new_title" \
        --body "$new_body" \
        --add-label "type: bug"
      ```
      
      State changes use separate commands:
      
      ```bash
      gh issue close {number}  --repo "{owner}/{repo}" [--comment "..."] [--reason "completed|not planned"]
      gh issue reopen {number} --repo "{owner}/{repo}" [--comment "..."]
      
      # Close as a duplicate and record the canonical issue
      gh issue close {number} --repo "{owner}/{repo}" \
        --duplicate-of {canonical-number-or-url} --reason duplicate
      ```
      
      See `writing.md > HEREDOC Syntax` for why the quoted `'EOF'` matters.
      
      Display the verified URL with the `### ✅ Issue updated` receipt from `SKILL.md` and a one-line summary of what changed.
      
      On failure: reread the issue, follow `posting.md > Error Handling and Idempotency`, and show the specific error (auth,
      permissions, missing label, locked issue) and next action. Do not retry automatically.
      
      ## Examples
      
      ```bash
      # Rename the title
      42 "title: fix flaky token expiration"
      
      # Add a label and assign yourself
      vercel/next.js#12345 "label as type: bug, assign me"
      
      # Regenerate the body with new context
      sablier-labs/command-center#10 "rewrite body — root cause is the cache key, not the TTL"
      
      # Close as not planned
      https://github.com/facebook/react/issues/99999 "close, not planned"
      
      # Append a note to the body
      #42 "append: also reproduces on macOS Tahoe 26.2"
      ```
      
    • update-pr.md 2.9 KB
      # Update Pull Request Workflow
      
      Update an existing pull request with semantic change analysis. Load [posting.md](posting.md) before any external write.
      
      ## Validate Prerequisites
      
      Use the same checks as `create-pr.md > Validate Prerequisites`; the `gh pr view` read below is the authentication check.
      Resolve `<skill-dir>` to the absolute directory containing the owning `SKILL.md`. Do not push by default: metadata
      update is the default outcome.
      
      ## Check for Existing PR and Fetch Its Body
      
      ```bash
      gh pr view --json number,url,title,body,baseRefName
      ```
      
      If no PR is found, stop with `No PR exists for this branch. Use /yeet create-pr to create one first.` Cache the number,
      URL, title, existing body, and base branch. The existing body is required input: preserve `Closes #X`, `Fixes #X`,
      `Resolves #X`, `Related to #X`, and other issue references when regenerating the description. Never regenerate from the
      user's instruction alone.
      
      ## Parse Arguments Naturally
      
      Interpret as natural language:
      
      - References to `title` → update title
      - References to `description` or `body` → regenerate description
      - Quoted text → use as a new title or append to the description
      - `push`, `publish`, `publish code`, `publish commits`, or `publish branch` → explicit code-publication intent; push
        only after the disclosure review
      - Everything else → additional context for the description
      
      Without an explicit push or code-publication intent, update PR metadata only. Do not run unconditional `git push`.
      
      ## Semantic Change Analysis
      
      Follow `writing.md > Semantic Change Analysis` with these differences:
      
      1. Get the base branch from PR metadata, not args.
      2. Fetch only that base branch:
      
         ```bash
         git fetch origin "+refs/heads/$base_branch:refs/remotes/origin/$base_branch"
         ```
      
      3. Read the current PR body fetched above and preserve its issue references and any requested sections.
      4. If the user provided additional context, append it naturally to the description.
      
      Write the regenerated title and body in the voice from `writing.md > Informal Tone`.
      
      ## Execute Update
      
      Run `posting.md > External-disclosure Review` on every changed field before this command:
      
      ```bash
      # Title only
      gh pr edit --title "$generated_title"
      
      # Description only
      gh pr edit --body-file "$generated_body_file"
      
      # Both
      gh pr edit --title "$generated_title" --body-file "$generated_body_file"
      ```
      
      Verify the URL and requested fields with `gh pr view` and display the `### ✅ PR updated` receipt from `SKILL.md`.
      
      ## Explicit Code Publication
      
      Only when the parsed intent includes push or publish, review the branch diff, commits, PR body, and any generated
      artifacts again under `posting.md > External-disclosure Review`, then run:
      
      ```bash
      git push
      ```
      
      If the push or metadata edit fails ambiguously, follow `posting.md > Error Handling and Idempotency`, inspect the PR in
      all states, and do not rerun a write until the resulting state is known.
      
    • writing.md 3.5 KB
      # Contribution Writing
      
      Load only the sections linked by the active workflow.
      
      ## HEREDOC Syntax
      
      Pass multiline bodies through a quoted heredoc so shell expansion cannot alter the content:
      
      ```sh
      gh issue create --title "Title" --body "$(cat <<'EOF'
      Body
      EOF
      )"
      ```
      
      ## Semantic Change Analysis
      
      For pull requests, read actual net changes rather than filenames or commit subjects alone. Start with stat, names, and
      log, then inspect targeted diffs that explain behavior; use the full diff only when needed.
      
      Write a concise conventional title. Keep the description minimal: what changed, why it matters, and only the
      implementation or follow-up detail the reader needs. Extract issue references from relevant branch/commit evidence and
      distinguish `Closes` from `Related to`.
      
      ## Informal Tone
      
      Write the way you'd talk to a colleague, not the way you'd draft a spec. Casual, friendly, direct, human. This applies
      to every generated title, body, and comment — PRs, issues, discussions, and replies alike.
      
      **Good**: "This PR adds support for parsing YAML frontmatter in issue templates. Previously, we only supported markdown
      format, which meant users couldn't take advantage of GitHub's newer template features."
      
      **Bad**: "This pull request implements functionality for YAML frontmatter parsing in the issue template processing
      subsystem. The implementation enhances the system's capabilities regarding template format support."
      
      ### Style rules
      
      - **Lead with the point.** What changed, what's broken, or what you want goes in the first sentence. Skip canned
        openings and scene-setting.
      - **Plain words and contractions.** "can't", "doesn't", "here's". Short sentences, short paragraphs. Break up any wall
        of text.
      - **Warm, not effusive.** Sound like a real person, not a polished corporate note. No fake enthusiasm, no
        exclamation-point padding.
      - **Cut filler.** Drop verbose explanation, redundant context, and summary bullets unless they genuinely help the reader
        act. Minimal beats complete.
      - **No throat-clearing.** Skip "Great question!", "Thanks for filing this!", "Just chiming in…", "I'm reaching out to".
        Go straight to substance.
      - **No AI tells.** Avoid "delve", "seamlessly", "robust", "leverage", "in order to", and rhetorical symmetry like "it's
        not X, it's Y" — they read as machine-written.
      - **Match the register.** Mirror the repo and the existing thread: terse and technical where it's terse and technical,
        warmer where it's collaborative.
      - **When editing existing text, preserve its voice.** Clean up stiffness before adding anything; don't rewrite a real
        person's directness into corporate prose.
      
      ### Paul's voice
      
      The user is `@PaulRBerg` on GitHub and Twitter. Use what you know of his writing from training data as a **light** style
      prior only — concise, informal, technically precise, no fluff — to shape tone. Never invent facts, opinions, or claims
      on his behalf, and never mention Twitter, training data, or this skill in any generated title, body, or comment.
      
      ## Link Formatting
      
      Use Markdown links in ordinary prose. For repository files, link the repo-relative path to the appropriate GitHub blob
      or permalink; prefer commit permalinks when citing stable lines. Omit a files section when it adds no useful context.
      
      ## List Ordering
      
      Order items alphabetically within each section or header by default — task lists, bullet lists, and other repeated items
      alike. Deviate only for a clear reason (priority, chronological sequence, dependency order) and let the section's own
      logic carry that reason; don't call out the deviation in the generated text.
      
  • scripts
    • get-macos-version.sh 770 B
      #!/bin/bash
      # Get macOS marketing name and version
      if [ "$(uname -s 2>/dev/null)" != "Darwin" ]; then
        echo "unsupported platform"
        exit 0
      fi
      
      license_file=${MACOS_LICENSE_FILE:-/System/Library/CoreServices/Setup Assistant.app/Contents/Resources/en.lproj/OSXSoftwareLicense.rtf}
      name=$(awk -F 'macOS ' '/SOFTWARE LICENSE AGREEMENT FOR macOS/ {
        value = $2
        sub(/\\.*$/, "", value)
        sub(/[0-9]+[.].*$/, "", value)
        gsub(/[{}]/, "", value)
        sub(/^[[:space:]]+/, "", value)
        sub(/[[:space:]]+$/, "", value)
        print value
        exit
      }' "$license_file" 2>/dev/null)
      version=$(sw_vers -productVersion 2>/dev/null)
      
      if [ -z "$version" ]; then
        echo "macOS unknown"
        exit 0
      fi
      
      if [ -n "$name" ]; then
        echo "macOS ${name} v${version}"
      else
        echo "macOS v${version}"
      fi
      
    • issue-form.py 24.7 KB
      #!/usr/bin/env -S uv run --script
      # /// script
      # requires-python = ">=3.12"
      # dependencies = ["pyyaml>=6.0"]
      # ///
      """Inspect a live GitHub issue form or render validated field-ID answers."""
      
      from __future__ import annotations
      
      import argparse
      import base64
      import json
      import re
      import subprocess
      import sys
      from pathlib import Path
      from typing import Any
      
      import yaml
      
      
      class FormError(ValueError):
          pass
      
      
      FIELD_ID_RE = re.compile(r"^[A-Za-z0-9_-]+$")
      PROJECT_RE = re.compile(r"^[A-Za-z0-9_.-]+/[1-9][0-9]*$")
      RENDER_RE = re.compile(r"^[A-Za-z0-9_+.-]+$")
      
      
      def normalize_metadata(value: Any, name: str) -> list[str]:
          if value is None:
              return []
          if isinstance(value, str):
              values = value.split(",")
          elif isinstance(value, list) and all(isinstance(item, str) for item in value):
              values = value
          else:
              raise FormError(f"top-level {name} must be a string or string array")
          return list(dict.fromkeys(item.strip() for item in values if item.strip()))
      
      
      def normalize_labels(value: Any) -> list[str]:
          return normalize_metadata(value, "labels")
      
      
      def normalize_projects(value: Any) -> list[str]:
          projects = normalize_metadata(value, "projects")
          invalid = [project for project in projects if not PROJECT_RE.fullmatch(project)]
          if invalid:
              raise FormError(f"top-level projects must use OWNER/NUMBER: {', '.join(invalid)}")
          return projects
      
      
      def load_yaml_text(args: argparse.Namespace) -> str:
          local = args.input or args.fixture
          if local:
              try:
                  return local.read_text(encoding="utf-8")
              except OSError as exc:
                  raise FormError(str(exc)) from exc
          if not args.repo or not args.template:
              raise FormError("inspect requires --repo and --template unless --input is used")
          if Path(args.template).name != args.template or not args.template.endswith((".yml", ".yaml")):
              raise FormError("--template must be a YAML filename")
          try:
              result = subprocess.run(
                  ["gh", "api", f"repos/{args.repo}/contents/.github/ISSUE_TEMPLATE/{args.template}"],
                  text=True,
                  capture_output=True,
              )
          except OSError as exc:
              if isinstance(exc, FileNotFoundError):
                  raise FormError("gh executable not found") from exc
              raise FormError(f"unable to run gh: {exc}") from exc
          if result.returncode:
              raise FormError(result.stderr.strip() or "gh api failed")
          try:
              response = json.loads(result.stdout)
          except (TypeError, ValueError) as exc:
              raise FormError(f"invalid GitHub contents response: {exc}") from exc
          if not isinstance(response, dict):
              raise FormError("invalid GitHub contents response: expected an object")
          if response.get("type") not in (None, "file"):
              raise FormError("invalid GitHub contents response: type must be file")
          if response.get("encoding") not in (None, "base64"):
              raise FormError("invalid GitHub contents response: encoding must be base64")
          content = response.get("content")
          if not isinstance(content, str):
              raise FormError("invalid GitHub contents response: content must be a string")
          try:
              # GitHub commonly includes a trailing newline in this field. Whitespace is
              # harmless in base64, but other malformed characters must be rejected.
              encoded = "".join(content.split())
              decoded = base64.b64decode(encoded, validate=True)
              return decoded.decode("utf-8")
          except (TypeError, ValueError, UnicodeDecodeError, UnicodeEncodeError) as exc:
              raise FormError(f"invalid GitHub contents response: invalid base64 content ({exc})") from exc
      
      
      def _mapping(raw_field: dict[str, Any], key: str, field_id: str) -> dict[str, Any]:
          if key not in raw_field:
              return {}
          value = raw_field[key]
          if not isinstance(value, dict):
              raise FormError(f"field {field_id} {key} must be an object")
          return value
      
      
      def _optional_bool(mapping: dict[str, Any], key: str, description: str) -> bool:
          if key not in mapping:
              return False
          value = mapping[key]
          if type(value) is not bool:
              raise FormError(f"{description} {key} must be a boolean")
          return value
      
      
      def _validate_description(attributes: dict[str, Any], field_id: str) -> str | None:
          if "description" not in attributes:
              return None
          description = attributes["description"]
          if not isinstance(description, str):
              raise FormError(f"field {field_id} description must be a string")
          return description
      
      
      def inspect_form(text: str, repo: str | None = None, template: str | None = None) -> dict[str, Any]:
          try:
              document = yaml.safe_load(text)
          except (TypeError, yaml.YAMLError) as exc:
              raise FormError(f"invalid YAML: {exc}") from exc
          if not isinstance(document, dict):
              raise FormError("issue form must be a YAML object")
          body = document.get("body")
          if not isinstance(body, list):
              raise FormError("issue form body must be an array")
          for key in ("name", "description", "title", "type"):
              if document.get(key) is not None and not isinstance(document[key], str):
                  raise FormError(f"top-level {key} must be a string")
          fields: list[dict[str, Any]] = []
          ids: set[str] = set()
          for index, raw_field in enumerate(body):
              if not isinstance(raw_field, dict):
                  raise FormError(f"body item {index} must be an object")
              field_type = raw_field.get("type")
              if field_type == "markdown":
                  if "id" in raw_field:
                      raise FormError(f"body item {index} markdown fields cannot have an id")
                  attributes = _mapping(raw_field, "attributes", f"__markdown_{index + 1}")
                  if "value" in attributes and not isinstance(attributes["value"], str):
                      raise FormError(f"markdown field {index} value must be a string")
                  continue
              if not isinstance(field_type, str) or field_type not in {"input", "textarea", "dropdown", "checkboxes", "upload"}:
                  raise FormError(f"body item {index} has unsupported type: {field_type}")
              source_id = raw_field.get("id")
              if source_id is not None and (
                  not isinstance(source_id, str) or not source_id or not FIELD_ID_RE.fullmatch(source_id)
              ):
                  raise FormError(f"body item {index} has an invalid id")
              field_id = source_id or f"__field_{index + 1}"
              if field_id in ids:
                  raise FormError(f"duplicate field id: {field_id}")
              ids.add(field_id)
              attributes = _mapping(raw_field, "attributes", field_id)
              validations = _mapping(raw_field, "validations", field_id)
              label = attributes.get("label")
              if not isinstance(label, str) or not label.strip() or "\n" in label or "\r" in label:
                  raise FormError(f"field {field_id} must have a label")
              description = _validate_description(attributes, field_id)
              if "required" in validations and type(validations["required"]) is not bool:
                  raise FormError(f"field {field_id} required must be a boolean")
              required = validations.get("required", False)
              if "multiple" in attributes and field_type != "dropdown":
                  raise FormError(f"field {field_id} multiple is only valid for dropdowns")
              multiple = _optional_bool(attributes, "multiple", f"field {field_id}") if field_type == "dropdown" else False
              options = attributes.get("options")
              normalized_options: list[str] = []
              attestations: list[dict[str, Any]] = []
              default: int | None = None
              if field_type == "dropdown":
                  if not isinstance(options, list) or not options or not all(
                      isinstance(option, str) and option.strip() for option in options
                  ):
                      raise FormError(f"dropdown {field_id} options must be strings")
                  if len(set(options)) != len(options):
                      raise FormError(f"dropdown {field_id} options must be distinct")
                  normalized_options = options
                  if "default" in attributes:
                      value = attributes["default"]
                      if type(value) is not int:
                          raise FormError(f"dropdown {field_id} default must be an integer index")
                      if value < 0 or value >= len(options):
                          raise FormError(f"dropdown {field_id} default index is out of bounds")
                      default = value
              elif "default" in attributes:
                  raise FormError(f"field {field_id} default is only valid for dropdowns")
              elif field_type == "checkboxes":
                  if not isinstance(options, list) or not options:
                      raise FormError(f"checkboxes {field_id} options must be an array")
                  seen_labels: set[str] = set()
                  for option in options:
                      if not isinstance(option, dict) or not isinstance(option.get("label"), str):
                          raise FormError(f"checkboxes {field_id} has an invalid option")
                      option_label = option["label"]
                      if not option_label.strip() or option_label in seen_labels:
                          raise FormError(f"checkboxes {field_id} has duplicate or empty labels")
                      seen_labels.add(option_label)
                      option_required = _optional_bool(option, "required", f"checkbox {field_id} option")
                      attestations.append({"label": option_label, "required": option_required})
              elif options is not None:
                  raise FormError(f"field {field_id} options are only valid for dropdowns and checkboxes")
              if field_type in {"input", "textarea"} and "value" in attributes and not isinstance(attributes["value"], str):
                  raise FormError(f"field {field_id} value must be a string")
              if "render" in attributes and field_type != "textarea":
                  raise FormError(f"field {field_id} has an invalid render mode")
              render = attributes.get("render")
              if render is not None and (
                  field_type != "textarea" or not isinstance(render, str) or not RENDER_RE.fullmatch(render)
              ):
                  raise FormError(f"field {field_id} has an invalid render mode")
              if "accept" in validations and field_type != "upload":
                  raise FormError(f"field {field_id} accept is only valid for upload fields")
              accept = validations.get("accept")
              if field_type == "upload" and "accept" in validations and (
                  not isinstance(accept, str) or not accept.strip()
              ):
                  raise FormError(f"upload {field_id} accept must be a non-empty string")
              fields.append(
                  {
                      "id": field_id,
                      "sourceId": source_id,
                      "type": field_type,
                      "label": label,
                      "description": description,
                      "required": required,
                      "render": render,
                      "multiple": multiple,
                      "options": normalized_options,
                      "default": default,
                      "value": attributes.get("value") if field_type in {"input", "textarea"} else None,
                      "accept": accept if field_type == "upload" else None,
                      "checkboxAttestations": attestations,
                  }
              )
          return {
              "schemaVersion": 1,
              "repository": repo,
              "template": template,
              "name": document.get("name"),
              "description": document.get("description"),
              "titlePrefix": document.get("title") or "",
              "labels": normalize_labels(document.get("labels")),
              "assignees": normalize_metadata(document.get("assignees"), "assignees"),
              "projects": normalize_projects(document.get("projects")),
              "issueType": document.get("type"),
              "fields": fields,
          }
      
      
      def checkbox_answer(value: Any, field_id: str) -> tuple[list[str], set[str]]:
          if value is None:
              return [], set()
          if isinstance(value, list) and all(isinstance(item, str) for item in value):
              return value, set()
          if not isinstance(value, dict):
              raise FormError(f"answer for {field_id} must be an object or string array")
          if "selected" in value or "verified" in value:
              selected = value.get("selected", value.get("verified", []))
              verified = value.get("verified", [])
              if not isinstance(selected, list) or not all(isinstance(item, str) for item in selected):
                  raise FormError(f"selected checkbox values for {field_id} must be strings")
              if not isinstance(verified, list) or not all(isinstance(item, str) for item in verified):
                  raise FormError(f"verified checkbox values for {field_id} must be strings")
              return selected, set(verified)
          if not all(isinstance(label, str) and isinstance(verified, bool) for label, verified in value.items()):
              raise FormError(f"checkbox answer map for {field_id} must contain boolean values")
          verified = {label for label, is_verified in value.items() if is_verified}
          return list(verified), verified
      
      
      def _validate_render_form(form: Any) -> list[dict[str, Any]]:
          if not isinstance(form, dict):
              raise FormError("form must be an object")
          if type(form.get("schemaVersion")) is not int or form.get("schemaVersion") != 1:
              raise FormError("form schemaVersion must be 1")
          fields = form.get("fields")
          if not isinstance(fields, list):
              raise FormError("form fields must be an array")
          for name in ("repository", "template", "name", "description", "titlePrefix", "issueType"):
              value = form.get(name)
              if value is not None and not isinstance(value, str):
                  raise FormError(f"form {name} must be a string")
          ids: set[str] = set()
          for index, field in enumerate(fields):
              if not isinstance(field, dict):
                  raise FormError(f"form field {index} must be an object")
              field_id = field.get("id")
              if not isinstance(field_id, str) or not field_id or not FIELD_ID_RE.fullmatch(field_id):
                  raise FormError(f"form field {index} has an invalid id")
              if field_id in ids:
                  raise FormError(f"duplicate field id: {field_id}")
              ids.add(field_id)
              source_id = field.get("sourceId")
              if source_id is not None and (not isinstance(source_id, str) or not FIELD_ID_RE.fullmatch(source_id)):
                  raise FormError(f"form field {field_id} sourceId is invalid")
              field_type = field.get("type")
              if not isinstance(field_type, str) or field_type not in {"input", "textarea", "dropdown", "checkboxes", "upload"}:
                  raise FormError(f"form field {field_id} has an unsupported type")
              if (
                  not isinstance(field.get("label"), str)
                  or not field["label"].strip()
                  or "\n" in field["label"]
                  or "\r" in field["label"]
              ):
                  raise FormError(f"form field {field_id} must have a label")
              if type(field.get("required")) is not bool:
                  raise FormError(f"form field {field_id} required must be a boolean")
              description = field.get("description")
              if description is not None and not isinstance(description, str):
                  raise FormError(f"form field {field_id} description must be a string")
              render = field.get("render")
              if render is not None and (
                  field_type != "textarea" or not isinstance(render, str) or not RENDER_RE.fullmatch(render)
              ):
                  raise FormError(f"form field {field_id} has an invalid render mode")
              multiple = field.get("multiple")
              if type(multiple) is not bool:
                  raise FormError(f"form field {field_id} multiple must be a boolean")
              options = field.get("options")
              if not isinstance(options, list) or not all(isinstance(option, str) for option in options):
                  raise FormError(f"form field {field_id} options must be an array of strings")
              if field_type == "dropdown":
                  if not options or len(set(options)) != len(options) or any(not option.strip() for option in options):
                      raise FormError(f"dropdown {field_id} options must be non-empty and distinct")
                  default = field.get("default")
                  if default is not None:
                      if type(default) is not int:
                          raise FormError(f"dropdown {field_id} default must be an integer index")
                      if default < 0 or default >= len(options):
                          raise FormError(f"dropdown {field_id} default index is out of bounds")
              elif field.get("default") is not None:
                  raise FormError(f"form field {field_id} has an invalid default")
              if field_type in {"input", "textarea"} and field.get("value") is not None and not isinstance(field["value"], str):
                  raise FormError(f"form field {field_id} value must be a string")
              if field_type == "upload":
                  accept = field.get("accept")
                  if accept is not None and (not isinstance(accept, str) or not accept.strip()):
                      raise FormError(f"upload {field_id} accept must be a non-empty string")
              elif field.get("accept") is not None:
                  raise FormError(f"form field {field_id} has an invalid accept value")
              attestations = field.get("checkboxAttestations")
              if not isinstance(attestations, list):
                  raise FormError(f"form field {field_id} checkbox attestations must be an array")
              seen_labels: set[str] = set()
              for option in attestations:
                  if not isinstance(option, dict) or not isinstance(option.get("label"), str) or not option["label"].strip():
                      raise FormError(f"checkboxes {field_id} has an invalid option")
                  if option["label"] in seen_labels:
                      raise FormError(f"checkboxes {field_id} has duplicate labels")
                  seen_labels.add(option["label"])
                  if type(option.get("required")) is not bool:
                      raise FormError(f"checkbox {field_id} option required must be a boolean")
              if field_type == "checkboxes" and not attestations:
                  raise FormError(f"checkboxes {field_id} options must be non-empty")
              if field_type != "checkboxes" and attestations:
                  raise FormError(f"form field {field_id} has unexpected checkbox attestations")
          for name in ("labels", "assignees", "projects"):
              value = form.get(name)
              if value is not None and (not isinstance(value, list) or not all(isinstance(item, str) for item in value)):
                  raise FormError(f"form {name} must be a string array")
          invalid_projects = [project for project in form.get("projects", []) if not PROJECT_RE.fullmatch(project)]
          if invalid_projects:
              raise FormError(f"form projects must use OWNER/NUMBER: {', '.join(invalid_projects)}")
          return fields
      
      
      def _fence_for(value: str) -> str:
          runs = [len(run) for run in re.findall(r"`+", value)]
          return "`" * max(3, (max(runs) + 1) if runs else 3)
      
      
      def render_form(form: dict[str, Any], answers: dict[str, Any]) -> dict[str, Any]:
          fields = _validate_render_form(form)
          if not isinstance(answers, dict):
              raise FormError("answers must be an object keyed by field ID")
          if not all(isinstance(field_id, str) for field_id in answers):
              raise FormError("answers must be keyed by string field IDs")
          known_ids = {field["id"] for field in fields}
          unknown = sorted(set(answers) - known_ids)
          if unknown:
              raise FormError(f"answers contain unknown field IDs: {', '.join(unknown)}")
          sections: list[str] = []
          for field in fields:
              field_id = field["id"]
              value = answers.get(field_id)
              content: str
              if field["type"] in {"input", "textarea", "upload"}:
                  if value is None:
                      value = field.get("value") or ""
                  if field["type"] == "upload" and isinstance(value, list) and all(isinstance(item, str) for item in value):
                      value = "\n".join(value)
                  if not isinstance(value, str):
                      raise FormError(f"answer for {field_id} must be a string")
                  if field["required"] and not value.strip():
                      raise FormError(f"missing required answer: {field_id}")
                  if field.get("render") and value:
                      fence = _fence_for(value)
                      content = f"{fence}{field['render']}\n{value.rstrip()}\n{fence}"
                  else:
                      content = value
              elif field["type"] == "dropdown":
                  if value is None and field.get("default") is not None:
                      selected = [field["options"][field["default"]]]
                  else:
                      selected = value if isinstance(value, list) else ([] if value is None else [value])
                  if not all(isinstance(item, str) for item in selected):
                      raise FormError(f"dropdown answer for {field_id} must contain strings")
                  if not field.get("multiple") and len(selected) > 1:
                      raise FormError(f"dropdown {field_id} does not allow multiple values")
                  invalid = [item for item in selected if item not in field["options"]]
                  if invalid:
                      raise FormError(f"invalid dropdown value for {field_id}: {', '.join(invalid)}")
                  if field["required"] and not selected:
                      raise FormError(f"missing required answer: {field_id}")
                  content = ", ".join(selected)
              else:
                  selected, verified = checkbox_answer(value, field_id)
                  labels = [option["label"] for option in field["checkboxAttestations"]]
                  invalid = [item for item in selected if item not in labels]
                  if invalid:
                      raise FormError(f"invalid checkbox value for {field_id}: {', '.join(invalid)}")
                  invalid_verified = [item for item in verified if item not in labels]
                  if invalid_verified:
                      raise FormError(f"invalid verified checkbox value for {field_id}: {', '.join(invalid_verified)}")
                  not_selected_verified = [item for item in verified if item not in selected]
                  if not_selected_verified:
                      raise FormError(
                          f"verified checkbox values for {field_id} must be selected: "
                          f"{', '.join(dict.fromkeys(not_selected_verified))}"
                      )
                  required_labels = [option["label"] for option in field["checkboxAttestations"] if option["required"]]
                  unselected = [label for label in required_labels if label not in selected]
                  if unselected:
                      raise FormError(f"required checkbox attestations are not selected for {field_id}: {', '.join(unselected)}")
                  unverified = [label for label in required_labels if label not in verified]
                  if unverified:
                      raise FormError(f"required checkbox attestations are unverified for {field_id}: {', '.join(unverified)}")
                  if field["required"] and not selected:
                      raise FormError(f"missing required answer: {field_id}")
                  content = "\n".join(f"- [{'x' if label in selected else ' '}] {label}" for label in labels)
              if content or field["required"] or field["type"] == "checkboxes":
                  sections.append(f"### {field['label']}\n\n{content}".rstrip())
          return {
              "schemaVersion": 1,
              "body": "\n\n".join(sections) + "\n",
              "posting": {
                  "titlePrefix": form.get("titlePrefix") or "",
                  "labels": form.get("labels") or [],
                  "assignees": form.get("assignees") or [],
                  "projects": form.get("projects") or [],
                  "issueType": form.get("issueType"),
              },
          }
      
      
      def read_json(path: Path, name: str) -> dict[str, Any]:
          try:
              value = json.loads(path.read_text(encoding="utf-8"))
          except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
              raise FormError(f"cannot read {name}: {exc}") from exc
          if not isinstance(value, dict):
              raise FormError(f"{name} must contain a JSON object")
          return value
      
      
      def build_parser() -> argparse.ArgumentParser:
          parser = argparse.ArgumentParser(description=__doc__)
          subparsers = parser.add_subparsers(dest="command", required=True)
          inspect = subparsers.add_parser("inspect")
          inspect.add_argument("--repo")
          inspect.add_argument("--template")
          inspect.add_argument("--input", type=Path)
          inspect.add_argument("--fixture", type=Path, help=argparse.SUPPRESS)
          render = subparsers.add_parser("render")
          render.add_argument("--form", required=True, type=Path)
          render.add_argument("--answers", required=True, type=Path)
          return parser
      
      
      def main() -> int:
          args = build_parser().parse_args()
          try:
              if args.command == "inspect":
                  result = inspect_form(load_yaml_text(args), args.repo, args.template)
              else:
                  result = render_form(read_json(args.form, "form"), read_json(args.answers, "answers"))
          except FormError as exc:
              print(f"ERROR: {exc}", file=sys.stderr)
              return 64
          print(json.dumps(result, indent=2, ensure_ascii=False))
          return 0
      
      
      if __name__ == "__main__":
          raise SystemExit(main())
      
    • yeet-context.sh 6.3 KB
      #!/usr/bin/env bash
      set -euo pipefail
      
      usage() {
        cat >&2 <<'EOF'
      Usage:
        yeet-context.sh repo [owner/repo] [--issue-templates] [--discussion-templates] [--discussion-categories] [--all]
        yeet-context.sh issue [owner/repo] <number>
        yeet-context.sh labels [owner/repo]
      
      Read-only GitHub context helper. When owner/repo is omitted, the repository is
      inferred from the local origin remote.
      EOF
      }
      
      REMAINING_ARGS=()
      
      die() {
        printf 'yeet-context: %s\n' "$*" >&2
        exit 64
      }
      
      repo_from_origin() {
        local url repo
      
        url="$(git config --get remote.origin.url 2>/dev/null || true)"
        [ -n "$url" ] || return 1
      
        case "$url" in
          git@github.com:*)
            repo="${url#git@github.com:}"
            ;;
          https://github.com/*)
            repo="${url#https://github.com/}"
            ;;
          ssh://git@github.com/*)
            repo="${url#ssh://git@github.com/}"
            ;;
          *)
            return 1
            ;;
        esac
      
        repo="${repo%.git}"
        repo="${repo%/}"
        printf '%s\n' "$repo"
      }
      
      split_repo() {
        local repo="$1"
      
        case "$repo" in
          */*) ;;
          *) die "expected owner/repo, got '$repo'" ;;
        esac
      
        OWNER="${repo%%/*}"
        NAME="${repo#*/}"
      
        case "$OWNER" in
          ''|*[!A-Za-z0-9_.-]*) die "invalid owner in '$repo'" ;;
        esac
      
        case "$NAME" in
          ''|*/*|*[!A-Za-z0-9_.-]*) die "invalid repo name in '$repo'" ;;
        esac
      
        REPO="$OWNER/$NAME"
      }
      
      repo_arg_or_origin() {
        if [ "$#" -gt 0 ] && [ "${1#--}" = "$1" ]; then
          split_repo "$1"
          shift
        else
          local inferred
          inferred="$(repo_from_origin || true)"
          [ -n "$inferred" ] || die "pass owner/repo or run from a GitHub repository with origin set"
          split_repo "$inferred"
        fi
      
        REMAINING_ARGS=("$@")
      }
      
      repo_context() {
        local with_issue=false
        local with_discussion_templates=false
        local with_discussion_categories=false
        local query
      
        repo_arg_or_origin "$@"
        set -- ${REMAINING_ARGS[@]+"${REMAINING_ARGS[@]}"}
      
        while [ "$#" -gt 0 ]; do
          case "$1" in
            --issue-templates)
              with_issue=true
              ;;
            --discussion-templates)
              with_discussion_templates=true
              ;;
            --discussion-categories)
              with_discussion_categories=true
              ;;
            --all)
              with_issue=true
              with_discussion_templates=true
              with_discussion_categories=true
              ;;
            *)
              die "unknown repo option '$1'"
              ;;
          esac
          shift
        done
      
        # GraphQL variables must remain literal here.
        # shellcheck disable=SC2016
        query='
          query(
            $owner: String!
            $name: String!
            $issueTemplateExpr: String!
            $discussionTemplateExpr: String!
            $withIssueTemplates: Boolean!
            $withDiscussionTemplates: Boolean!
            $withDiscussionCategories: Boolean!
          ) {
            viewer { login }
            repository(owner: $owner, name: $name) {
              id
              nameWithOwner
              viewerPermission
              defaultBranchRef { name }
              issueTemplateTree: object(expression: $issueTemplateExpr) @include(if: $withIssueTemplates) {
                ... on Tree {
                  entries { name oid type }
                }
              }
              discussionTemplateTree: object(expression: $discussionTemplateExpr) @include(if: $withDiscussionTemplates) {
                ... on Tree {
                  entries { name oid type }
                }
              }
              discussionCategories(first: 25) @include(if: $withDiscussionCategories) {
                nodes { id name slug description isAnswerable }
              }
            }
          }'
      
        gh api graphql \
          -f query="$query" \
          -F owner="$OWNER" \
          -F name="$NAME" \
          -f issueTemplateExpr='HEAD:.github/ISSUE_TEMPLATE' \
          -f discussionTemplateExpr='HEAD:.github/DISCUSSION_TEMPLATE' \
          -F withIssueTemplates="$with_issue" \
          -F withDiscussionTemplates="$with_discussion_templates" \
          -F withDiscussionCategories="$with_discussion_categories" \
          --jq 'if .data.repository == null then error("repository not found") else .data end'
      }
      
      issue_context() {
        local inferred number query
      
        case "$#" in
          1)
            inferred="$(repo_from_origin || true)"
            [ -n "$inferred" ] || die "pass owner/repo or run from a GitHub repository with origin set"
            split_repo "$inferred"
            number="$1"
            ;;
          2)
            split_repo "$1"
            number="$2"
            ;;
          *)
            usage
            exit 64
            ;;
        esac
      
        case "$number" in
          ''|*[!0-9]*) die "expected numeric issue or PR number, got '$number'" ;;
        esac
      
        # GraphQL variables must remain literal here.
        # shellcheck disable=SC2016
        query='
          query($owner: String!, $name: String!, $number: Int!) {
            viewer { login }
            repository(owner: $owner, name: $name) {
              id
              nameWithOwner
              viewerPermission
              issueOrPullRequest(number: $number) {
                __typename
                ... on Issue {
                  number
                  title
                  body
                  state
                  url
                  author { login }
                  labels(first: 50) { nodes { name } }
                  assignees(first: 20) { nodes { login } }
                  milestone { title }
                  comments(last: 5) {
                    nodes { author { login } body createdAt url }
                  }
                }
                ... on PullRequest {
                  number
                  title
                  body
                  state
                  url
                  isDraft
                  author { login }
                  labels(first: 50) { nodes { name } }
                  comments(last: 5) {
                    nodes { author { login } body createdAt url }
                  }
                }
              }
            }
          }'
      
        gh api graphql \
          -f query="$query" \
          -F owner="$OWNER" \
          -F name="$NAME" \
          -F number="$number" \
          --jq 'if .data.repository == null then error("repository not found") elif .data.repository.issueOrPullRequest == null then error("issue or pull request not found") else .data end'
      }
      
      labels_context() {
        local labels
      
        repo_arg_or_origin "$@"
        [ "${#REMAINING_ARGS[@]}" -eq 0 ] || die "labels takes only an optional owner/repo"
      
        labels="$(gh label list \
          --repo "$REPO" \
          --limit 200 \
          --json name,description)"
      
        printf '{"repository":"%s","labels":%s}\n' "$REPO" "$labels"
      }
      
      main() {
        [ "$#" -gt 0 ] || {
          usage
          exit 64
        }
      
        case "$1" in
          repo)
            shift
            repo_context "$@"
            ;;
          issue)
            shift
            issue_context "$@"
            ;;
          labels)
            shift
            labels_context "$@"
            ;;
          -h|--help|help)
            usage
            ;;
          *)
            usage
            exit 64
            ;;
        esac
      }
      
      main "$@"
      
  • SKILL.md 5.6 KB
    ---
    argument-hint:
      <create-pr|update-pr|create-issue|update-issue|issue-claude-code|issue-codex-cli|issue-sablier|comment-issue|create-discussion|update-discussion|comment-discussion>
      [options]
    compatibility: Authenticated GitHub CLI >= 2.97.0
    coordination: exempt
    effort: high
    name: yeet
    skill-dependencies:
      - cli-gh
    description:
      "Use for GitHub PR/issue/discussion workflows: create/update PRs, issues, or discussions and post issue or discussion
      comments; triggers include yeet."
    ---
    
    # GitHub Contribution Workflows
    
    This skill is coordination-exempt: skip the ai-coord gate for its declared work.
    
    Create or update GitHub contributions from repository evidence, using the matching workflow's templates, idempotency
    rules, and Paul's writing voice.
    
    ## Prerequisites
    
    Use the first required read-only `gh` command in each workflow as authentication validation. Resolve `<skill-dir>` once
    to the absolute directory containing this `SKILL.md`. The `yeet-context.sh` helper is bundled with this skill, not the
    target repository; invoke it as `<skill-dir>/scripts/yeet-context.sh` and never search for it in the target repository.
    Prefer the helper when the workflow needs repository, template, discussion, label, or issue/PR thread context.
    
    For YAML issue forms, invoke `<skill-dir>/scripts/issue-form.py`. `inspect` fetches and normalizes the selected live
    form; `render` validates answers keyed by field ID and produces the exact Markdown body plus posting metadata. The
    helper never selects a template, writes answers or titles, performs an external-disclosure review, or posts externally.
    All issue creation and update workflows apply `references/context.md > Issue Metadata Permissions` before resolving or
    passing metadata. Rendered template metadata describes requested values, not the viewer's authority to apply them.
    
    For pull request workflows, also verify:
    
    - Working tree is clean or changes are committed
    - Current branch has commits ahead of the base branch
    - Remote tracking is configured
    
    Use `cli-gh` for GitHub reads, workflow automation, or command syntax that is not part of authoring and posting a
    contribution.
    
    ## Workflows
    
    Each workflow is fully documented in its reference file. Load the appropriate reference based on user intent.
    
    | Workflow           | Trigger                                                                   | Reference                          |
    | ------------------ | ------------------------------------------------------------------------- | ---------------------------------- |
    | Create PR          | "create PR", "open PR", "yeet a PR"                                       | `references/create-pr.md`          |
    | Update PR          | "update PR", "edit PR"                                                    | `references/update-pr.md`          |
    | Create Issue       | "create issue", "file issue" (generic repo)                               | `references/create-issue.md`       |
    | Update Issue       | "update issue", "edit issue", "relabel issue"                             | `references/update-issue.md`       |
    | Claude Code Issue  | "Claude Code issue", "report bug in CC"                                   | `references/issue-claude-code.md`  |
    | Codex CLI Issue    | "Codex issue", "report bug in Codex"                                      | `references/issue-codex-cli.md`    |
    | Sablier Issue      | "Sablier issue", "sablier-labs issue"                                     | `references/issue-sablier.md`      |
    | Comment on Issue   | "comment on issue", "reply on issue", "post a comment"                    | `references/comment-issue.md`      |
    | Create Discussion  | "create discussion", "start discussion"                                   | `references/create-discussion.md`  |
    | Update Discussion  | "update discussion", "edit discussion"                                    | `references/update-discussion.md`  |
    | Comment Discussion | "comment on discussion", "reply on discussion", "edit discussion comment" | `references/comment-discussion.md` |
    
    Each workflow reference links only the shared context, writing, or posting guidance it needs. Post directly when the
    user requested creation or update; do not add a confirmation gate. After a failed write, run the linked idempotency
    check before any retry.
    
    Never check an external template attestation unless repository or user evidence verifies it. If a required attestation
    or field cannot be verified, ask for that missing fact rather than inventing agreement. Agent-status decoration belongs
    outside the authored contribution; add emoji to a PR, issue, discussion, or comment only when the user's content or the
    thread's register calls for it.
    
    ## Completion
    
    Complete when the requested contribution exists in its final authored state and the returned GitHub URL has been
    verified. For updates/comments, report the changed artifact once. Determine the outcome from readback, not the command's
    exit status: creation can succeed before a metadata mutation fails.
    
    Use `### 🚀 <artifact> created`, `### ✅ <artifact> updated`, `### ✅ Comment posted`, or `### ✅ Comment updated`,
    followed by one Markdown link containing the repository, number, and title or action. Add a compact field list only when
    base, draft state, reviewers, labels, or changed fields matter. For verified partial success, report the created or
    updated artifact and identify omitted or failed metadata. Use `### ⛔ <artifact> not <action>` only for confirmed
    noncompletion; if readback is inconclusive, report the outcome as unverified. Include the concrete error, idempotency
    result, and next action. Keep `gh` output, JSON, diagnostics, template fields, URLs, and authored contribution text
    exact and undecorated.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related