plugins/src/base/skills/github-write-issue/SKILL.md
Creates or updates a GitHub Issue following the same organizational best practices as lisa:jira-write-ticket — three-audience description, Gherkin acceptance criteria, parent sub-issue (Epic/Story hierarchy), explicit relationship discovery, remote links, labels for status/components/priority/story-points, Validation Journey, and optional GitHub ProjectV2 coordination through lisa:github-project-v2 while keeping the Issue as the lifecycle source of truth. Uses the `gh` CLI exclusively (no MCP). Rejects thin issues. The GitHub counterpart of lisa:jira-write-ticket.
npx skillsauth add codyswanngt/lisa github-write-issueInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Create or update a GitHub Issue with all required relationships, metadata, and quality gates. Every section below is mandatory. Thin issues are rejected.
This skill is the GitHub counterpart of lisa:jira-write-ticket. The two skills share the same gates, description structure, acceptance-criteria format, and verification flow. The data-model translation is documented under "GitHub Issues data model" below — keep that table in sync with lisa:github-validate-issue so what one skill writes the other accepts.
Repository name for scoped comments: basename $(git rev-parse --show-toplevel).
gh CLI installed and authenticated (gh auth status must succeed). The skill never falls back to a different transport — if gh is unauthenticated, stop and surface the auth error..lisa.config.json must declare github.org and github.repo for the destination repository. The configured repo is the issue's home; pass it on every gh invocation via --repo <org>/<repo>.jq installed (used to parse gh JSON outputs without hand-rolling parsers).Determine from $ARGUMENTS and context whether this is a CREATE or UPDATE:
org/repo#<number> or a full https://github.com/<org>/<repo>/issues/<number> URL). Call lisa:github-read-issue <ref> first to load the full current state before editing. Never overwrite without reading.Resolve <ORG> and <REPO> from the ref or from .lisa.config.json.
| Field | Required For | Notes |
|-------|--------------|-------|
| Issue type | CREATE | Epic, Story, Task, Bug, Sub-task, Spike, Improvement. Encoded as a label type:<value>. |
| Summary (title) | CREATE, UPDATE | One line, imperative voice, under 100 chars. |
| Description (body) | CREATE, UPDATE | Multi-section markdown — see Phase 3. |
| Parent sub-issue | Non-bug, non-epic | Native GitHub sub-issue link. Enforced by lisa:github-verify. |
| Priority | CREATE | Label priority:<low|medium|high|critical>. |
| Acceptance criteria | Story, Task, Bug, Sub-task, Improvement | Gherkin in ## Acceptance Criteria — see Phase 3. |
| Validation Journey | Runtime-behavior changes | Delegate to /github-add-journey. |
| Target backend environment | Runtime-behavior changes | Recorded under ## Target Backend Environment. Skip only for doc / config / type-only. |
| Sign-in account / credentials | Authenticated-surface tickets | Recorded under ## Sign-in Required. |
| Repository | Bug, Task, Sub-task | GitHub Issues live in exactly one repo by definition — record the repo name under ## Repository, and reject any AC bullet that references a different repo. |
Optional but recommended: assignee, milestone, components (label component:<name>), story points (label points:<n>), labels.
Use gh api repos/<org>/<repo>/labels --paginate to discover existing labels before referencing one. If a required label doesn't exist (e.g., type:Story, status:ready, priority:high), create it via gh label create <name> --color <hex> --description <desc> --repo <org>/<repo>. The first run on a fresh repo will create the full label set; subsequent runs reuse them.
The description (issue body) MUST address three audiences. Reject and rewrite if any are missing.
## Context / Business Value
[Why this matters. Stakeholder-facing. Concrete user impact or business outcome.
Link to the originating Slack thread, PRD page, incident, or customer report.]
## Technical Approach
[Developer-facing. Integration points, impacted modules, data model implications,
relevant tradeoffs. Not a full design doc — a pointer for someone picking it up.]
## Acceptance Criteria
```gherkin
Scenario: <name>
Given <precondition>
When <action>
Then <observable outcome>
Scenario: <name>
Given <precondition>
When <action>
Then <observable outcome>
[Explicit list of what this issue does NOT cover. Forces scope discipline.]
[Required when the issue changes runtime behavior. One of: dev / staging / prod. Skip section entirely for doc-only, config-only, or type-only issues.]
[Include this section ONLY if the work touches authenticated surfaces. Specify: the account/role, where to get the credentials (1Password item, env var, seeded fixture), and any MFA/SSO notes.]
[Required for Bug / Task / Sub-task. Name the single repo this issue covers. If the work spans repos, this issue type is wrong — split into per-repo Tasks/Sub-tasks under a parent Story or Epic.]
[Group by domain (UI / UX flow / Data / Ops / Reference). One bullet per artifact with title and URL. Inherited from the parent epic per the rules in lisa:tracker-source-artifacts.]
[When artifacts are attached, name the authoritative source per axis: business rules → PRD body, visual treatment → mocks, flow → prototypes, API/data → data artifacts. Cross-axis conflicts go under Open Questions.]
[Remote links: PRs (Resolves <org>/<repo>#<pr>), Confluence pages,
dashboards, incident tickets. Native Resolves #<pr> lines are picked up
by GitHub for auto-close.]
[Document the git+search outcomes from Phase 4b. "Searched git history
for <keywords> and gh issue list for label component:X; no related
work found." A no-result outcome is acceptable when documented.]
[Delegate to /github-add-journey if the issue changes runtime behavior. Skip only for doc-only, config-only, or type-only issues.]
Rules:
- Every acceptance criterion uses Given/When/Then. No vague "should work" language.
- Every criterion is independently verifiable.
- If the issue is a Bug, include reproduction steps, expected vs. actual behavior, and environment.
- If the issue is a Spike, include the question being answered and the definition of done.
- If sign-in is required, the implementer must be able to sign in from the description alone.
## Phase 4 — Relationship Discovery (Mandatory)
### 4a. Parent sub-issue
If the issue is not a Bug and not an Epic, it MUST have a parent sub-issue:
1. If explicitly provided, use that issue number.
2. Otherwise search active Epic issues:
```bash
gh issue list --repo <org>/<repo> --label type:Epic --state open --json number,title,body --limit 100
Match on keywords from the summary and description. 3. If no Epic matches, stop and ask the human to create or pick one. Do NOT orphan the issue.
Run BOTH searches and document outcomes — never declare "no related work" without doing both.
Search 1: local git history
git log --all --oneline --grep="<keyword>"
git log --all --oneline -- <path-or-glob>
git log --since=90.days --oneline -- <path-or-glob>
If a PR or commit surfaces, capture the PR URL — it becomes a Phase 4c link.
Search 2: GitHub issue search
gh issue list --repo <org>/<repo> --search "<keyword>" --state all --limit 50 --json number,title,state,labels,updatedAt
gh issue list --repo <org>/<repo> --label "component:<component>" --state open --limit 100 --json number,title,state
gh issue list --repo <org>/<repo> --search "epic:#<epic-number>" --state all --limit 50 --json number,title,state
For each candidate, classify the relationship using these markdown conventions in the issue body (under ## Links):
| Link type | How it's encoded |
|-----------|------------------|
| blocks | Blocks #<number> (or full org/repo#<number> for cross-repo) |
| is blocked by | Blocked by #<number> |
| relates to | Relates to #<number> |
| duplicates | Duplicates #<number> (and close one as duplicate via gh issue close <number> --reason "duplicate of #<other>") |
| clones | Cloned from #<number> |
GitHub does not have a native typed-link primitive for issues (only Resolves #<pr> for PR↔Issue). The text conventions above are parsed by lisa:github-read-issue to reconstruct the relationship graph.
Identify and attach (under ## Links):
lisa:tracker-source-artifacts. Inherit by domain — UI → ui-design + ux-flow; backend → data; infra → ops; always inherit reference. Never assume a developer will walk up to the Epic to find design context.If the issue was generated from a PRD and the parent Epic has no source artifacts, surface that as a smell and ask whether artifacts were missed during extraction.
Same rule as lisa:jira-write-ticket Phase 4d — record source precedence under ## Source Precedence (or a paragraph under ## Technical Approach) when artifacts are attached. Cross-axis conflicts go under ## Open Questions.
If the issue modifies an existing user-facing surface, a lisa:product-walkthrough should already have been run upstream. Inherit findings under ## Current Product.
GitHub Issues uses labels for the structured metadata that JIRA stores in custom fields. Apply the conventions:
| Concept | Label format | Example |
|---------|--------------|---------|
| Issue type | type:<value> | type:Story, type:Bug, type:Epic, type:Sub-task, type:Spike, type:Improvement |
| Status | status:<value> | status:ready, status:in-progress, status:on-dev, status:done |
| Priority | priority:<value> | priority:low, priority:medium, priority:high, priority:critical |
| Components | component:<name> | component:auth, component:billing |
| Story points | points:<n> | points:3, points:5, points:8 |
| Fix version | fix-version:<value> | fix-version:2.7.0 |
| Repo (multi-repo orgs) | implicit (issue lives in the repo) | — |
| Triage marker | claude-triaged-<repo> | claude-triaged-frontend-v2 |
Milestones map to fix-version:<value> labels OR native GitHub Milestones — pick one convention per repo and document it in .lisa.config.json if needed. Default to labels for parity with the JIRA fix-version concept.
Create labels lazily — call gh label create if a referenced label doesn't exist. Use a stable color palette per category so the issue board reads cleanly.
The build-ready status label (status:ready) is governed by the leaf-only-lifecycle rule. Apply status:ready only when the issue is a leaf work unit — an individually implementable issue type (Bug, Task, Sub-task, Improvement) that has no child work. A container (Epic, Story, Spike, or any issue that has sub-issues) is never written with status:ready; its lifecycle state rolls up from its children. The classification is structural: an item is a container if it has child work, regardless of its declared type (see the childless-parent exception in the rule). Do not hand-apply status:ready to a parent.
For non-build-ready issues created fresh (Epics, Stories, and other containers), omit the status label entirely; the container's rollup state is derived, not set directly.
build_ready)build_ready is an optional write-control input (default: omitted). It governs whether a leaf work unit is stamped with the build-ready role on create. It never overrides leaf-only-lifecycle — a container is never stamped build-ready regardless of build_ready. "Not build-ready" is not a special status: it simply means the issue is created in its natural default (a plain open issue with no status:ready label), which a human can promote later.
status:ready. Preserves what every existing caller (lisa:plan, the *-to-tracker skills) relies on.build_ready: false → create the leaf without status:ready, so it sits in the backlog for a human to review and promote into the queue.build_ready: true → ensure the leaf carries status:ready so lisa:intake / lisa:github-build-intake auto-picks it up.Before any write, invoke lisa:github-validate-issue with the full proposed spec assembled from Phases 2 / 3 / 4 / 5. Pass it as a YAML block per the lisa:github-validate-issue schema, including runtime_behavior_change, authenticated_surface, and artifacts_attached flags so the right gates run.
lisa:github-validate-issue is the single source of truth for what makes a valid GitHub Issue in this pipeline — same gate definitions as lisa:jira-validate-ticket, translated to GitHub's data model. Do not re-implement gate logic here.
If the validator reports FAIL, do NOT proceed to Phase 6. Fix the spec and re-run validation. Never call gh issue create while the validator's verdict is FAIL.
Compose the body markdown from Phases 2/3/4 in a temp file (avoid quoting hell). Apply status:ready only for a leaf work unit per the Phase 5 leaf-only rule (leaf-only-lifecycle) — omit it for Epic / Story / Spike and any issue that has child work, and only when build_ready is not false (a leaf with build_ready: false is created without status:ready; see the Build-ready control input):
# Leaf work unit (Bug / Task / Sub-task / Improvement with no children), build_ready not false:
gh issue create \
--repo <org>/<repo> \
--title "<summary>" \
--body-file /tmp/issue-body.md \
--label "type:<type>" --label "status:ready" --label "priority:<priority>" \
[--label "component:<name>" ...] [--milestone "<milestone>"] \
[--assignee "<login>"]
# Container (Epic / Story / Spike / any issue with child work), OR a leaf with build_ready: false:
# identical, but WITHOUT --label "status:ready" — a container's state rolls up from children;
# a build_ready: false leaf waits in the backlog for a human to promote it.
gh issue create \
--repo <org>/<repo> \
--title "<summary>" \
--body-file /tmp/issue-body.md \
--label "type:<type>" --label "priority:<priority>" \
[--label "component:<name>" ...] [--milestone "<milestone>"] \
[--assignee "<login>"]
Capture the returned issue number.
Link to parent sub-issue (if non-Epic): use the GraphQL sub-issue API.
Resolve the new issue's GraphQL node ID:
child_id=$(gh api graphql -f query='query($org:String!,$repo:String!,$number:Int!){repository(owner:$org,name:$repo){issue(number:$number){id}}}' -F org=<org> -F repo=<repo> -F number=<new_number> --jq '.data.repository.issue.id')
Resolve the parent issue's GraphQL node ID the same way. Then call:
gh api graphql -f query='mutation($parentId:ID!,$childId:ID!){addSubIssue(input:{issueId:$parentId,subIssueId:$childId}){issue{number}subIssue{number}}}' -F parentId=$parent_id -F childId=$child_id
If the GraphQL mutation isn't available on the repo (older GHES, sub-issues feature off), fall back to text linkage in the body (Parent: #<parent>) and surface a warning to the caller. Do NOT silently proceed without recording the parent.
Phase 4b relationship lines (Blocks #..., Blocked by #..., etc.) are already in the body. No separate API call is needed — lisa:github-read-issue parses them on read.
If the issue changes runtime behavior, invoke the lisa:github-add-journey skill to append the Validation Journey section.
If github.projects.v2 is enabled, resolve the created issue's node id and invoke lisa:github-project-v2 with operation: ensure-item and content_node_id: <issue-node-id>. This is membership coordination only — the GitHub Issue remains the lifecycle source of truth for labels, body, comments, and parentage.
outcome: disabled → continue normally; no shared Project is configured.outcome: reused or added → continue normally; the issue is now present in the Project.outcome: warning (required: false) → preserve the exact warning and continue the issue write as success.outcome: blocked (required: true) → surface the exact failure and stop returning success; do not claim Project coordination succeeded silently.gh issue view <number> --repo <org>/<repo> --json body --jq '.body'. Edit only the sections being changed; preserve everything else verbatim, including any existing canonical managed ## Lisa Usage section unless the caller intentionally supplied an updated canonical section. Use the shared usage-accounting serializer/merge path rather than freehand edits to ledger rows.gh issue edit <number> --repo <org>/<repo> --body-file /tmp/updated-body.md
gh issue edit <number> --add-label "<new-label>". Remove labels: --remove-label.## Links if needed. Existing links are not touched unless explicitly removed.lisa:github-project-v2 with operation: ensure-item so updates keep the issue present in the configured shared Project without duplicating membership writes. Branch on disabled / added / reused / warning / blocked exactly as in CREATE.Call the lisa:github-verify skill on the resulting issue. lisa:github-verify re-fetches the issue and runs lisa:github-validate-issue against the live state — same gates as Phase 5.5, applied to what GitHub actually stored.
If it reports failures, fix them before returning.
Post a creation comment via gh issue comment <number> --repo <org>/<repo> --body-file /tmp/announce.md with:
[<repo>] prefix when the issue is repo-scoped (Bug / Task / Sub-task).Blocks, Blocked by, Relates to) with links.Skip on UPDATE when no material change was made.
The mapping below is the single source of truth for how JIRA concepts translate to GitHub Issues. Mirror this in lisa:github-validate-issue and lisa:github-read-issue so the shape is symmetric.
| JIRA concept | GitHub Issues equivalent |
|---|---|
| Issue type | Label type:<value> |
| Status (Ready / In Progress / On Dev / Done) | Label status:<value> |
| Epic / Story / Sub-task hierarchy | Native sub-issues via gh api graphql addSubIssue |
| Acceptance Criteria | ## Acceptance Criteria markdown section with a Gherkin code-fence |
| Validation Journey | ## Validation Journey markdown section |
| Source artifacts | ## Source Artifacts markdown section grouped by domain |
| Epic parent | Native parent sub-issue link |
| Issue links (blocks / is blocked by / relates to / duplicates / clones) | Quick-action lines under ## Links: Blocks #N, Blocked by #N, Relates to #N, Duplicates #N, Cloned from #N |
| Remote links (PRs, dashboards) | Markdown links under ## Links plus native Resolves #<pr> for PR↔Issue auto-close |
| Components | Label component:<name> |
| Fix version | Label fix-version:<value> (or native milestone) |
| Story points | Label points:<n> |
| Priority | Label priority:<value> |
| Custom-field "Reporter" (the human) | The issue's author (immutable) plus the Filed by: line in the body |
| Worklog | Comments (no native time tracking) |
| Triage marker | Label claude-triaged-<repo> |
gh issue list searches must run; outcomes documented under ## Relationship Search. "None found" is acceptable only when documented.## Lisa Usage section on update; never append a second usage
section or silently drop ledger rows.tracker-write shim). Other vendor-neutral skills must NEVER call gh issue create directly.lisa:github-validate-issue, NOT here. This skill calls the validator at Phase 5.5 and Phase 7. When a gate needs to change, change it in lisa:github-validate-issue.lisa:github-read-issue and the GitHub UI use to render the hierarchy.lisa:github-project-v2; never inline separate ProjectV2 GraphQL from this skill.documentation
Onboard a user to the project via its LLM Wiki. Interviews the user about themselves in relation to the project, captures that to project-scoped memory only, then gives a guided tour of what the project is and sample questions they can ask. Use when someone is new to the project or asks to be onboarded. Read-mostly — it does not open PRs or write PII into the wiki.
documentation
Migrate an existing, hand-rolled wiki implementation onto the lisa-wiki kernel — phased and compatibility-first, with a strict no-loss guarantee. Use when adopting lisa-wiki in a repo that already has its own wiki/, ingest skills, docs, or roles. Renaming things into the canonical shape is fine; losing functionality or data is not. Ends by running /doctor.
development
Health-check the LLM Wiki. Reports orphan pages, contradictions, stale claims, broken internal links, missing index/log coverage, structure-manifest violations, and secret/tenant leaks. Use periodically or before hardening a wiki. Read-only — it reports findings, it does not fix them.
testing
Ingest source material into the LLM Wiki. With an argument (URL, file path, or prompt) it ingests that one source; with no argument it runs a full ingest across every enabled non-external-write source. Routes to the right connector, then runs the ordered pipeline (source note → synthesis → index → log → verify → state → commit/PR). Use whenever new knowledge should enter the wiki.