| name | lisa-setup-github |
| description | 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). |
| allowed-tools | ["Bash","Read","Write","Edit","Skill","AskUserQuestion"] |
Setup GitHub: $ARGUMENTS
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.
Workflow
Step 0 — Decide what GitHub is for
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 the status:* label namespace. Sets tracker: "github".
- PRD source — humans drop PRDs here as Issues labeled
prd-ready; /lisa:intake scans and ticketes them off the prd-* label namespace. Sets source: "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.
Step 1 — Ensure the gh CLI is installed and authenticated
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
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
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.
Step 2 — Resolve org/repo
Honor 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 different org/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.
Step 2b — Choose the queue repository
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.
Step 3 — Scaffold the lifecycle label namespaces
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() {
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}}"
}
ensure_label() {
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 --description \
&&
}
3a. Build-queue labels (only if GitHub is the tracker)
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"
3b. PRD-lifecycle labels (only if GitHub is the PRD source)
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"
3c. Handle name collisions / renames
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.
Step 4 — Write .lisa.config.json
github.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
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
if [ -n "$LABEL_OVERRIDES_JSON" ] && [ "$LABEL_OVERRIDES_JSON" != "{}" ]; then
jq --argjson o \
\
.lisa.config.json > .lisa.config.json.tmp && .lisa.config.json.tmp .lisa.config.json
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.
Step 5 — Offer to set top-level tracker / source
For 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-level tracker: "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 for prd-ready PRDs?
If yes: jq '.source = "github"' ...
Both are project-wide switches that change every downstream skill's default — never set either without explicit confirmation.
Step 6 — Verify
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:
- choose a Project owned by the tracked repo namespace,
- grant the token Project read/write access,
- correct the configured Project number/owner, or
- remove
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.
Idempotency
- Re-running merges the
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.
- Re-running does not re-prompt for
tracker / source if they already point at GitHub.
Rules
- Never write the GitHub token to
.lisa.config.json. It stays in gh's store or GH_TOKEN.
- Never create a duplicate label for a role that already has a (differently-named) label — map the role to the existing label and record it as a config override instead.
- Never overload one label across both the PRD and build namespaces — a single issue is either a PRD or a build ticket, never both (see
config-resolution).
- Never set
tracker / source without explicit user confirmation — they're project-wide and switch every downstream skill's behavior.
- Never invent an
org/repo. Default-detect from the remote and confirm; if detection fails, ask the user to supply it.