plugins/src/base/skills/lisa-setup-github/SKILL.md
Configure GitHub Issues as the destination tracker and/or the PRD source for this project. Verifies the gh CLI is installed and authenticated, resolves `org/repo`, scaffolds the build-queue label namespace (`status:*`) when GitHub is the tracker and/or the PRD-lifecycle label namespace (`prd-*` + sentinel) when GitHub is the PRD source, writes the `github` section into `.lisa.config.json`, and offers to set top-level `tracker: "github"` and/or `source: "github"`. Idempotent — re-running updates the existing section and reuses existing labels rather than duplicating. No /lisa:setup:atlassian prerequisite (GitHub auth is standalone).
npx skillsauth add codyswanngt/lisa lisa-setup-githubInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
Security scan pending...
This skill is queued for security scanning. Results will appear when the scan completes.
Make GitHub Issues a tracker, a PRD source, or both for this project. After this skill, .lisa.config.json contains github.org + github.repo, the configured repo carries the lifecycle label namespaces lisa needs, and (optionally) tracker / source point at GitHub.
Unlike setup-jira / setup-confluence, this skill has no setup-atlassian dependency — GitHub auth runs through the gh CLI directly.
Ask via AskUserQuestion (multiSelect):
What should lisa use this GitHub repo for?
- Destination tracker — lisa writes Epics / Stories / Sub-tasks here as Issues, and the build queue (
/lisa:intake,/lisa:implement) runs off thestatus:*label namespace. Setstracker: "github".- PRD source — humans drop PRDs here as Issues labeled
prd-ready;/lisa:intakescans and ticketes them off theprd-*label namespace. Setssource: "github".
The answer drives which label namespaces get scaffolded in Step 3: tracker → status:*, source → prd-* + sentinel. Self-host (both) is supported — the two namespaces never overlap (see the config-resolution rule's "Self-host edge case").
If the user selects neither, stop — there's nothing to configure.
if ! command -v gh >/dev/null 2>&1; then
if command -v brew >/dev/null 2>&1; then
brew install gh
else
cat >&2 <<'EOF'
Error: gh (GitHub CLI) not found and Homebrew unavailable. Install it:
https://github.com/cli/cli#installation
Then re-run /lisa:setup:github.
EOF
exit 1
fi
fi
# Auth: prefer an interactive login; CI/headless uses GH_TOKEN.
if ! gh auth status >/dev/null 2>&1; then
if [ -n "$GH_TOKEN" ] || [ -n "$GITHUB_TOKEN" ]; then
echo "gh not logged in interactively, but GH_TOKEN/GITHUB_TOKEN is set — gh will use it."
else
cat >&2 <<'EOF'
Error: gh is not authenticated. Run:
gh auth login # interactive (developer machines)
or set GH_TOKEN as a secret (CI / headless), then re-run /lisa:setup:github.
EOF
exit 1
fi
fi
Confirm the authenticated identity can write Issues and labels — gh auth status shows the token scopes; repo scope (or fine-grained Issues: read-write + metadata: read) is required to create labels and issues. If the scopes look read-only, surface it and instruct gh auth refresh -s repo.
org/repoHonor any --repo=org/repo argument. Otherwise default-detect from the current repo's origin remote, then confirm — the tracker/PRD repo is frequently a different repo than the code repo (e.g. a dedicated product-prds repo):
DEFAULT_REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null)
Present the detected value via AskUserQuestion:
Use
<DEFAULT_REPO>as the GitHub repo for lisa's issues/PRDs, or specify a differentorg/repo?
Once resolved, split into ORG and REPO and confirm reachability + write access:
gh repo view "$ORG/$REPO" --json name,viewerPermission \
--jq 'if (.viewerPermission | IN("ADMIN","MAINTAIN","WRITE")) then "ok" else error("insufficient permission: \(.viewerPermission)") end'
If permission is READ / TRIAGE, stop — lisa cannot create labels or issues. Surface and exit.
When GitHub is selected as the destination tracker, initialize the choice from an existing
github.queueRepo, or $ORG/$REPO when absent, then ask whether the build backlog lives there, in
the identity repo, or in another umbrella/planning repo. If the user gives an umbrella repo, accept a short name by normalizing it to $ORG/<name> or accept a full
owner/repo, then verify it is reachable and the authenticated identity can manage the lifecycle
labels Lisa needs:
gh repo view "$QUEUE_ORG/$QUEUE_REPO" --json name,viewerPermission \
--jq 'if (.viewerPermission | IN("ADMIN","MAINTAIN","WRITE")) then "ok" else error("insufficient permission: \(.viewerPermission)") end'
Store github.queueRepo only when the canonical queue owner/repo differs from $ORG/$REPO.
This choice changes queue scans, not repository identity: github.repo, repo:<current> claim
scoping, issue destinations, and automation naming remain tied to the identity/tracker repo.
Read role → label mappings with the same default-fallback ladder the intake skills use, so the labels created here exactly match what they look for. Only the namespaces selected in Step 0 are scaffolded.
read_role() { # $1=namespace (build|prd) $2=role $3=default
local ns="$1" role="$2" default="$3" local_v global_v
local_v=$(jq -r ".github.labels.${ns}.${role} // empty" .lisa.config.local.json 2>/dev/null)
global_v=$(jq -r ".github.labels.${ns}.${role} // empty" .lisa.config.json 2>/dev/null)
echo "${local_v:-${global_v:-$default}}"
}
# Idempotent label creator: create if missing, leave untouched if present.
# LABEL_ORG/LABEL_REPO select the repo for the namespace being scaffolded.
ensure_label() { # $1=name $2=hex-color $3=description
local name="$1" color="$2" desc="$3"
if gh label list --repo "$LABEL_ORG/$LABEL_REPO" --limit 200 --json name --jq '.[].name' | grep -qxF "$name"; then
echo " = $name (exists)"
else
gh label create "$name" --repo "$LABEL_ORG/$LABEL_REPO" --color "$color" --description "$desc" \
&& echo " + $name (created)"
fi
}
Defaults from config-resolution. The done role is env-keyed — create all three by default; a project whose terminal state is env-independent can later collapse github.labels.build.done to a single string.
LABEL_ORG="$QUEUE_ORG"
LABEL_REPO="$QUEUE_REPO"
ensure_label "$(read_role build ready status:ready)" FBCA04 "Ready for build (human signal)"
ensure_label "$(read_role build claimed status:in-progress)" 0E8A16 "Build in progress (agent owns)"
ensure_label "$(read_role build blocked status:blocked)" D93F0B "Blocked — human attention required"
ensure_label "$(read_role build done.dev status:on-dev)" 1D76DB "Deployed to dev"
ensure_label "$(read_role build done.staging status:on-stg)" 1D76DB "Deployed to staging"
ensure_label "$(read_role build done.production status:done)" 0E8A16 "Shipped to production"
LABEL_ORG="$ORG"
LABEL_REPO="$REPO"
ensure_label "$(read_role prd draft prd-draft)" C5DEF5 "PRD in progress (product owns)"
ensure_label "$(read_role prd ready prd-ready)" FBCA04 "PRD ready for ticketing"
ensure_label "$(read_role prd in_review prd-in-review)" 5319E7 "Claude is reviewing this PRD"
ensure_label "$(read_role prd blocked prd-blocked)" D93F0B "PRD blocked — see comments"
ensure_label "$(read_role prd ticketed prd-ticketed)" 0E8A16 "Tickets created — see comments"
ensure_label "$(read_role prd shipped prd-shipped)" 1D76DB "Work delivered (product owns)"
ensure_label "$(read_role prd verified prd-verified)" 0E8A16 "Shipped product empirically verified against the PRD (product owns)"
ensure_label "$(read_role prd sentinel prd-intake-feedback)" EDEDED "Marker for PRD-intake feedback issues"
If a project already uses a differently-named label for a role (e.g. ready-for-dev instead of status:ready), do NOT create a duplicate. Present the repo's existing labels via AskUserQuestion and let the user map the role to the existing label — that mapping becomes an override written in Step 4. Renaming-in-place (keeping lisa's defaults) is also fine if the user prefers; just surface the choice.
.lisa.config.jsongithub.org / github.repo are project-wide → committed. Write only label keys that differ from the documented defaults — never echo the full default map into config (keeps it lean; missing keys inherit defaults at runtime).
touch .lisa.config.json
[ -s .lisa.config.json ] || echo '{}' > .lisa.config.json
jq --arg org "$ORG" --arg repo "$REPO" \
'.github = ((.github // {}) | .org = $org | .repo = $repo)' \
.lisa.config.json > .lisa.config.json.tmp && mv .lisa.config.json.tmp .lisa.config.json
# Queue repo is canonical owner/repo and omitted for the identity/default case.
# A source-only setup rerun preserves any existing build-queue choice.
if [ "$GITHUB_TRACKER_SELECTED" = "true" ]; then
if [ "$QUEUE_ORG/$QUEUE_REPO" = "$ORG/$REPO" ]; then
jq 'del(.github.queueRepo)' .lisa.config.json > .lisa.config.json.tmp \
&& mv .lisa.config.json.tmp .lisa.config.json
else
jq --arg queue "$QUEUE_ORG/$QUEUE_REPO" '.github.queueRepo = $queue' \
.lisa.config.json > .lisa.config.json.tmp \
&& mv .lisa.config.json.tmp .lisa.config.json
fi
fi
# Conditionally write label overrides (only roles the user mapped to non-default names).
# $LABEL_OVERRIDES_JSON is e.g. {"build":{"ready":"ready-for-dev"}} — {} if all defaults.
if [ -n "$LABEL_OVERRIDES_JSON" ] && [ "$LABEL_OVERRIDES_JSON" != "{}" ]; then
jq --argjson o "$LABEL_OVERRIDES_JSON" \
'.github.labels = ((.github.labels // {}) * $o)' \
.lisa.config.json > .lisa.config.json.tmp && mv .lisa.config.json.tmp .lisa.config.json
fi
If this project later enables shared GitHub Project coordination, store it under the same github block as:
"github": {
"org": "<tracked-repo-owner>",
"repo": "<tracked-repo>",
"projects": {
"v2": {
"owner": { "kind": "organization", "slug": "<tracked-repo-owner>" },
"number": 7,
"required": false
}
}
}
That block is optional and coordination-only: real issues and pull requests stay the durable source of truth. In v1, github.projects.v2.owner.slug MUST match the tracked repository namespace (github.org) — user-owned repos use a user-owned Project, org-owned repos use an org-owned Project, and cross-namespace Project ownership is rejected. required defaults to false, meaning Project membership is best-effort unless a later setup/doctor flow explicitly opts into strict mode.
When that block is present, later setup/doctor and runtime validation must read the shared Project's owner + access before membership writes depend on it. Best-effort mode (required: false) reports warning-level validation failures and continues repository-local writes without Project membership; strict mode (required: true) reports the same failures as blocking errors and stops the write.
No secrets go in config — the GitHub token lives in gh's own store (~/.config/gh/) or the GH_TOKEN env var, never in .lisa.config.json.
tracker / sourceFor each role selected in Step 0, offer the matching top-level flag (skip if already set to GitHub).
If tracker was selected and .tracker is unset or not "github", ask via AskUserQuestion:
Repo
<org>/<repo>configured. Set top-leveltracker: "github"so all vendor-neutral skills write Issues here?
If yes: jq '.tracker = "github"' ...
If source was selected and .source is unset or not "github", ask:
Set top-level
source: "github"so/lisa:intake(with no args) scans this repo forprd-readyPRDs?
If yes: jq '.source = "github"' ...
Both are project-wide switches that change every downstream skill's default — never set either without explicit confirmation.
jq -e '.github.org and .github.repo' .lisa.config.json >/dev/null
EFFECTIVE_ORG=$(jq -r '.github.org // empty' .lisa.config.local.json 2>/dev/null)
EFFECTIVE_ORG=${EFFECTIVE_ORG:-$(jq -r '.github.org' .lisa.config.json)}
EFFECTIVE_REPO=$(jq -r '.github.repo // empty' .lisa.config.local.json 2>/dev/null)
EFFECTIVE_REPO=${EFFECTIVE_REPO:-$(jq -r '.github.repo' .lisa.config.json)}
QUEUE_VALUE=$(jq -r '.github.queueRepo // empty' .lisa.config.local.json 2>/dev/null)
QUEUE_VALUE=${QUEUE_VALUE:-$(jq -r '.github.queueRepo // empty' .lisa.config.json)}
if [ -z "$QUEUE_VALUE" ]; then
QUEUE_REF="$EFFECTIVE_ORG/$EFFECTIVE_REPO"
elif [[ "$QUEUE_VALUE" == */* ]]; then
QUEUE_REF="$QUEUE_VALUE"
else
QUEUE_REF="$EFFECTIVE_ORG/$QUEUE_VALUE"
fi
gh repo view "$QUEUE_REF" --json nameWithOwner --jq .nameWithOwner
gh label list --repo "$ORG/$REPO" --limit 200 --json name --jq '.[].name' \
| grep -E 'prd-' || true
gh label list --repo "$QUEUE_REF" --limit 200 --json name --jq '.[].name' \
| grep -E 'status:' || true
If github.projects.v2 is configured, setup verification MUST also run the shared Project utility in
read-only resolution mode before declaring success:
operation: resolve-project
This is the setup/doctor chokepoint for GitHub Project coordination. Do not inline ad-hoc GraphQL
here; delegate to lisa-github-project-v2 so setup, doctor, writers, and linked-PR flows all read
the same owner/access contract and surface the same exact failure text.
Verification contract when github.projects.v2 is present:
First enforce the v1 namespace rule locally: github.projects.v2.owner.slug MUST equal
github.org. If not, report the exact configuration failure and remediation:
code: project_namespace_mismatch
message: "github.projects.v2.owner.slug must match github.org in v1"
remediation: "Use a Project owned by <github.org> or remove github.projects.v2."
Then resolve the configured Project owner + number through lisa-github-project-v2.
Preserve the exact GitHub / GraphQL failure text for inaccessible or unsupported Project
configurations. Examples: missing Project, Resource not accessible by integration, unsupported
owner kind, or any other Project read failure.
Report the exact remediation path. At minimum, say whether the operator must:
github.projects.v2 if coordination is not required.Branch severity on required exactly the same way the shared utility does:
required: false => warning-level validation failure, continue repository-local setup success;
required: true => blocking verification failure, stop setup/doctor before claiming coordination
is usable.
The verify output should make the operator's next step obvious. Good examples:
WARNING github.projects.v2: Resource not accessible by integration
Remediation: grant the token Project read/write access or remove github.projects.v2.required.
Repository-local GitHub issue/PR flows remain usable; Project coordination is disabled.
ERROR github.projects.v2: github.projects.v2.owner.slug must match github.org in v1
Remediation: use a Project owned by CodySwannGT or remove github.projects.v2.
Report success with the resolved org/repo, which label namespaces were scaffolded (and which labels already existed vs. were created), any non-default label overrides written, and whether tracker / source were set. Direct the user to /lisa:intake to test.
github section's fields rather than appending — jq merge semantics throughout.ensure_label is find-or-create: existing labels are left exactly as they are (color/description not overwritten), so a re-run never churns labels a human customized.tracker / source if they already point at GitHub..lisa.config.json. It stays in gh's store or GH_TOKEN.config-resolution).tracker / source without explicit user confirmation — they're project-wide and switch every downstream skill's behavior.org/repo. Default-detect from the remote and confirm; if detection fails, ask the user to supply it.tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.
tools
Configure the official SonarQube plugin + MCP as Lisa's single Sonar substrate across every supported coding agent. Installs/updates the SonarQube CLI, authenticates (browser login on a dev machine, or SONARQUBE_CLI_TOKEN headless), selects the Test Manager target, runs `sonar integrate <agent>` for each supported agent (Claude, Codex, Cursor, Copilot, Antigravity) and wires the MCP for OpenCode, then writes only non-secret policy to .lisa.config.json. Separate from the CI SonarCloud scan gate, which is unchanged.