Setup
Pre-flight check and installer for the WTF workflow.
Validate the GitHub CLI. Install required extensions. Make sure .github/ISSUE_TEMPLATE/ holds all required templates.
Set up issue classification (native GitHub issue types or labels) and the lifecycle labels.
Install the PR template. Agents and humans can then create structured issues and pull requests.
Process
1. Verify gh is installed
gh --version
If gh is not found, tell the user that the GitHub CLI is required. Link them to https://cli.github.com. Stop. Do not continue until gh is installed.
2. Verify gh is authenticated
gh auth status
If gh is not authenticated, tell the user to run gh auth login. Stop. Do not continue until authentication is confirmed.
3. Check and install required extensions
gh extension list
Check the output for both extensions below. If an extension is missing, install it:
# Sub-issue hierarchy (epic → feature → task)
gh extension install yahsan2/gh-sub-issue
# Issue dependency tracking (X blocks Y)
gh extension install xiduzo/gh-issue-dependency
If installation fails (for example network error or permissions), warn the user. Relationship tracking stays unavailable until the extension is installed. Note the failure for the final status report.
After you try installation, verify the command syntax for each newly installed extension:
gh sub-issue --help
gh issue-dependency --help
Record two booleans for the final report:
gh-sub-issue-available: true ifyahsan2/gh-sub-issueis installed and workinggh-issue-dependency-available: true ifxiduzo/gh-issue-dependencyis installed and working
4. Detect repo context
gh repo view --json nameWithOwner -q .nameWithOwner
If this fails (not inside a git repo, or no GitHub remote), warn the user. Note that issue creation will not work until the repo is connected to GitHub. Continue to the template check anyway.
4b. Verify GitHub permissions
The workflow needs the authenticated user to manage labels and create issue relationships (sub-issues, dependencies).
Both need write access to the repo. The token needs the repo scope (or public_repo for public repos).
Check token scopes:
gh auth status 2>&1 | grep -i "token scopes"
Required scopes (any of):
repo— full control (private + public repos)public_repo— sufficient for public repos only
If neither scope is present, tell the user to refresh auth with the right scopes:
gh auth refresh -h github.com -s repo
Stop until the user re-runs setup.
Check repo write permission:
gh api "repos/$(gh repo view --json nameWithOwner -q .nameWithOwner)" \
--jq '.permissions | {admin, maintain, push, triage, pull}'
The user must have admin: true, maintain: true, or push: true. If the user has only triage or pull, warn:
⚠️ You have read-only access to this repo. Label creation and issue linking will fail. Ask a maintainer for write access or fork the repo.
Record two booleans for the final report:
token-scopes-ok: true ifrepoorpublic_reposcope is presentrepo-write-ok: true ifadmin,maintain, orpushis true
If either is false, skip label creation in step 7. Warn that sub-issue and dependency creation will fail at runtime.
5. Check issue templates
Check whether .github/ISSUE_TEMPLATE/ exists and holds all four required templates:
ls .github/ISSUE_TEMPLATE/
Required files:
BUG.mdEPIC.mdFEATURE.mdTASK.md
First resolve where this skill's payload is installed. npx skills add drops wtf.setup under the skills root (~/.claude/skills/wtf.setup, .claude/skills/wtf.setup, or the .agents/skills equivalent). The bundled files (references/, shared-references/, hooks/) are included inside it.
Probe in order. Keep the first that exists. Reuse $SETUP_DIR for every copy below:
for cand in \
"$HOME/.claude/skills/wtf.setup" \
"$HOME/.agents/skills/wtf.setup" \
"$PWD/.claude/skills/wtf.setup" \
"$PWD/.agents/skills/wtf.setup" \
"$PWD/skills/wtf.setup"; do
[ -d "$cand/references" ] && SETUP_DIR="$cand" && break
done
For each missing file, copy it from this skill's bundled references at $SETUP_DIR/references/:
mkdir -p .github/ISSUE_TEMPLATE
cp "$SETUP_DIR/references/BUG.md" .github/ISSUE_TEMPLATE/BUG.md
cp "$SETUP_DIR/references/EPIC.md" .github/ISSUE_TEMPLATE/EPIC.md
cp "$SETUP_DIR/references/FEATURE.md" .github/ISSUE_TEMPLATE/FEATURE.md
cp "$SETUP_DIR/references/TASK.md" .github/ISSUE_TEMPLATE/TASK.md
Copy only files that are missing. Do not overwrite existing templates. After copying, list the final contents of .github/ISSUE_TEMPLATE/ to confirm.
6. Check PR template
Check whether .github/pull_request_template.md exists:
ls .github/pull_request_template.md 2>/dev/null
If it is missing, copy it from the skill's bundled references (use $SETUP_DIR from step 5):
cp "$SETUP_DIR/references/pull_request_template.md" .github/pull_request_template.md
Do not overwrite the file if it already exists.
7. Choose the issue-classification mode and provision it
WTF classifies every issue as an Epic, Feature, Task, or Bug.
There are two mechanisms. One is native GitHub issue types (an organization-only feature). That leaves labels free for your own segmentation. The other is the epic/feature/task/bug labels (portable to any repo).
See ../references/issue-classification.md. Pick the mode once here. Record it in .wtf/config.json so every skill resolves it the same way. Lifecycle labels (implemented, designed, verified) are always created in both modes.
Step A — detect the owner type:
OWNER=$(gh repo view --json owner -q .owner.login)
OWNER_TYPE=$(gh api "users/$OWNER" --jq '.type' 2>/dev/null) # "User" or "Organization"
Step B — pick the mode.
If
OWNER_TYPEis notOrganization(a personal account, or detection failed): native issue types are unavailable. GitHub gates them to organizations. SetCLASS_MODE=labelsand tell the user plainly: "This is a personal-account repo, so GitHub issue types are not available (they are org-only). WTF will classify with theepic/feature/task/buglabels." Skip to Step D.If
OWNER_TYPEisOrganizationandrepo-write-okandtoken-scopes-ok(from step 4b) are both true, callAskUserQuestion(per../references/questioning-style.md):- question: "This repo is in an org, so WTF can classify issues with native GitHub issue types (Epic/Feature/Task/Bug) instead of labels — leaving labels free for your own segmentation like
phase-2. Use native issue types?" - header: "Classification"
- options:
- Native issue types (recommended) →
CLASS_MODE=types - Labels →
CLASS_MODE=labels
- Native issue types (recommended) →
If write or token permissions are missing, do not offer types. Provisioning needs org-owner rights. Set
CLASS_MODE=labelsand note it.- question: "This repo is in an org, so WTF can classify issues with native GitHub issue types (Epic/Feature/Task/Bug) instead of labels — leaving labels free for your own segmentation like
Step C — provision native types (only when CLASS_MODE=types).
Run the Provision native types block from ../references/issue-classification.md. It creates Epic (Task/Bug/Feature ship as org defaults).
Then verify all four resolved. If any is missing (for example you are not an org owner), fall back to labels:
HAVE=$(gh api "orgs/$OWNER/issue-types" --jq '[.[].name]' 2>/dev/null)
for t in Epic Feature Task Bug; do
printf '%s' "$HAVE" | grep -qi "\"$t\"" || { echo "⚠️ could not provision issue type: $t — falling back to labels"; CLASS_MODE=labels; }
done
When this falls back, warn that native types need org-owner rights. WTF will use labels instead.
Step D — create labels. Always create the lifecycle labels.
Create the kind labels (epic/feature/task/bug) only in labels mode. In types mode omit them on purpose so the label space stays free for your own segmentation.
--force is idempotent. It updates color/description if the label already exists. Otherwise it creates the label:
# Lifecycle labels — always, both modes:
gh label create implemented --color 0e8a16 --description "Implementation complete — ready for QA" --force
gh label create designed --color f9d0c4 --description "Design coverage added to the Task" --force
gh label create verified --color 006b75 --description "QA verified — ready for merge" --force
# Kind labels — labels mode only:
if [ "$CLASS_MODE" = labels ]; then
gh label create epic --color 5319e7 --description "Strategic initiative spanning multiple features" --force
gh label create feature --color 0075ca --description "User-facing capability delivered as a vertical slice" --force
gh label create task --color e4e669 --description "Implementable vertical slice of a Feature" --force
gh label create bug --color d73a4a --description "Something is broken" --force
fi
If any label creation fails (for example insufficient permissions), warn the user. The affected skills fall back to creating labels on first use.
Step D (cont.) — align the issue templates with the mode.
In types mode, rewrite each copied .github/ISSUE_TEMPLATE/*.md. Make its kind come from the native type rather than a label.
Flip the labels: <kind> frontmatter line to type: <Kind>. (type is a supported template frontmatter key alongside title/labels/assignees.)
This keeps issues opened manually from the GitHub UI typed, not labelled. The label space stays free.
In labels mode leave the templates as they are. The rewrite is conservative. It only touches an exact single labels: <kind> line. Customized multi-label templates are left alone:
if [ "$CLASS_MODE" = types ]; then
python3 - <<'PY'
import re, pathlib
kinds = {"BUG": ("bug", "Bug"), "EPIC": ("epic", "Epic"), "FEATURE": ("feature", "Feature"), "TASK": ("task", "Task")}
d = pathlib.Path(".github/ISSUE_TEMPLATE")
for fname, (lbl, typ) in kinds.items():
p = d / f"{fname}.md"
if not p.exists():
continue
text = p.read_text()
new = re.sub(rf'(?m)^labels:\s*{lbl}\s*$', f'type: {typ}', text)
if new != text:
p.write_text(new)
print(f" {fname}.md: labels: {lbl} -> type: {typ}")
PY
fi
Step E — record the mode so every skill resolves it the same way:
mkdir -p .wtf
python3 - ".wtf/config.json" "$CLASS_MODE" <<'PY'
import json, sys, pathlib
path, mode = sys.argv[1], sys.argv[2]
p = pathlib.Path(path)
data = json.loads(p.read_text()) if p.exists() and p.read_text().strip() else {}
data["classification"] = mode
p.write_text(json.dumps(data, indent=2) + "\n")
PY
Commit .wtf/config.json so every teammate classifies issues the same way. Record classification: types|labels for the status report.
Closing convention: GitHub has no native setting to require PR-based closure. Skill behavior enforces this. Issues are only "closed as completed" when a merged PR contains
Closes #<n>. Directgh issue closecalls are reserved for--reason "not planned"(will not implement) and--reason "duplicate"only. Surface this convention in the status report.
8. Install intervention-tracker hook
The tracker hook counts user corrections and nudges toward /wtf.reflect. skills.sh copies the hook script into the skill dir. You must register the hook in Claude Code's settings.json manually.
Step A — locate the installed hook script. The hook is a Python script (track-interventions.py). It runs the same way on macOS, Linux, and Windows. Probe in order. Keep the first that exists:
for cand in \
"$HOME/.claude/skills/wtf.setup/hooks/track-interventions.py" \
"$PWD/.claude/skills/wtf.setup/hooks/track-interventions.py" \
"$PWD/skills/wtf.setup/hooks/track-interventions.py"; do
[ -f "$cand" ] && HOOK_PATH="$cand" && break
done
If none exist, warn the user that the hook script could not be found. Skip hook registration.
Step B — ask scope (apply ../references/questioning-style.md):
Call AskUserQuestion (per ../references/questioning-style.md):
- question: "Install the WTF intervention-tracker hook globally or only for this repo?"
- header: "Hook scope"
- options:
- Global (~/.claude/settings.json) → runs in every repo that has
docs/steering/ - This repo only (.claude/settings.json) → scoped to this project
- Skip → do not install the hook
- Global (~/.claude/settings.json) → runs in every repo that has
Set SETTINGS_FILE as follows:
- Global →
$HOME/.claude/settings.json - Per-repo →
.claude/settings.json - Skip → jump to step 9.
Step C — patch settings.json idempotently.
If the file is missing, create it (echo '{}' > "$SETTINGS_FILE"). Then merge the two hook entries with python3.
python3 is available on macOS/Linux. git-bash on Windows ships it via the installer or can use py.
The registered command invokes the hook via python3. It works without a POSIX shell on Windows:
PY_BIN=$(command -v python3 || command -v py || echo python)
HOOK_CMD="$PY_BIN $HOOK_PATH"
python3 - "$SETTINGS_FILE" "$HOOK_CMD" <<'PY'
import json, sys, pathlib
path, cmd = sys.argv[1], sys.argv[2]
p = pathlib.Path(path)
data = json.loads(p.read_text()) if p.exists() and p.read_text().strip() else {}
hooks = data.setdefault("hooks", {})
for event in ("UserPromptSubmit", "Stop"):
arr = hooks.setdefault(event, [])
# Strip legacy sh-based entries so we don't double-fire after the .sh→.py migration.
for entry in arr:
entry["hooks"] = [
h for h in entry.get("hooks", [])
if "track-interventions.sh" not in (h.get("command") or "")
]
arr[:] = [e for e in arr if e.get("hooks")]
exists = any(
any(h.get("command") == cmd for h in entry.get("hooks", []))
for entry in arr
)
if not exists:
arr.append({"matcher": "", "hooks": [{"type": "command", "command": cmd}]})
p.write_text(json.dumps(data, indent=2))
PY
Re-running is safe. Existing entries are detected by exact command string and are not duplicated.
Windows note: If the user is on Windows without python3, skip the patch. Print the JSON snippet for manual paste. Detect via command -v python3 >/dev/null || echo 'manual'.
Record hook-installed: true|false|skipped for the status report.
8b. Install the gh body helper
gh-body.py is a cross-platform utility that makes every GitHub issue/PR body read and write UTF-8-safe. It prevents the CP850 mojibake, newline collapse, and inline---body corruption that gh suffers under PowerShell on Windows.
Skills invoke it at .wtf/gh-body.py. Install it here so the guard is committed to the repo and shared with every teammate. See ../references/gh-body-helper.md.
Step A — locate the bundled helper (same install-location probe as the tracker):
for cand in \
"$HOME/.claude/skills/wtf.setup/hooks/gh-body.py" \
"$PWD/.claude/skills/wtf.setup/hooks/gh-body.py" \
"$PWD/skills/wtf.setup/hooks/gh-body.py"; do
[ -f "$cand" ] && GHBODY_SRC="$cand" && break
done
Step B — copy it into the repo:
if [ -n "$GHBODY_SRC" ]; then
mkdir -p .wtf
cp "$GHBODY_SRC" .wtf/gh-body.py
fi
Commit .wtf/gh-body.py so the guard travels with the repo for every contributor.
If GHBODY_SRC is empty (helper not found) or cp is unavailable (Windows without git-bash), tell the user to copy gh-body.py into .wtf/ manually. Note it. Skills fall back to raw gh until then. That path is unguarded on Windows.
Step C — verify it actually runs. Skills invoke the helper as python3 .wtf/gh-body.py. Test that exact form. It validates the interpreter name, that Python is present, and that the copy is valid, all in one shot:
if python3 .wtf/gh-body.py --help >/dev/null 2>&1; then
GUARD=verified
elif command -v python3 >/dev/null 2>&1 || command -v python >/dev/null 2>&1 || command -v py >/dev/null 2>&1; then
GUARD=wrong-name # Python is installed, but not reachable as `python3`
else
GUARD=no-python
fi
Interpret the result for the user:
verified→ the guard is live.wrong-name→ Python exists but not aspython3. On Windows the python.org installer often providespython/py, notpython3. The skill commands callpython3. The guard will fail until the user adds apython3alias/shim. Show the working interpreter you found (for examplepy -3). Tell them to alias it.no-python→ no Python 3 on PATH. The guard is inert. Every body/comment op falls back to rawgh. That corrupts UTF-8 on Windows. Point the user to https://www.python.org/downloads/. Have them re-run setup.
Record gh-body-helper: verified|wrong-name|no-python|not-installed for the status report (not-installed if Step B could not copy the file).
8c. Install the shared skill references
Every execution skill loads cross-skill docs via ../references/<name>.md. That is a references/ folder next to the installed skills, at the skills root.
npx skills add installs each wtf.* skill directory one by one. The repo's skills/references/ folder has no SKILL.md, so it is never shipped. <skills-root>/references/ would be empty.
To fix that, wtf.setup carries a vendored copy at $SETUP_DIR/shared-references/ and writes it to the skills root here. Without this step, every other skill fails to resolve its references.
Step A — derive the skills root (the parent of $SETUP_DIR, resolved in step 5):
SKILLS_ROOT="$(dirname "$SETUP_DIR")" # e.g. ~/.claude/skills
Step B — copy the bundled references into <skills-root>/references/:
if [ -d "$SETUP_DIR/shared-references" ]; then
mkdir -p "$SKILLS_ROOT/references"
cp "$SETUP_DIR/shared-references/"*.md "$SKILLS_ROOT/references/"
fi
This overwrites prior copies so updates propagate on every npx skills update + re-run.
When setup runs from the wtf repo itself ($PWD/skills/wtf.setup), $SKILLS_ROOT is skills/. The copy is a harmless no-op refresh.
If $SETUP_DIR/shared-references is absent (older payload, or cp unavailable on Windows without git-bash), tell the user to copy the repo's skills/references/*.md into <skills-root>/references/ manually. Note it. Other skills cannot resolve ../references/... until then.
Step C — verify that at least one known reference landed:
[ -f "$SKILLS_ROOT/references/questioning-style.md" ] && echo "shared-references: installed" || echo "shared-references: MISSING"
Record shared-references: installed|missing for the status report.
9. Report status
Print a clear status summary that covers every check:
WTF Setup — Status Report
─────────────────────────
gh CLI installed ✅
gh authenticated ✅
gh-sub-issue extension ✅ (or ⚠️ not installed — relationship links unavailable)
gh-issue-dependency ext ✅ (or ⚠️ not installed — dependency links unavailable)
Repo context ✅ owner/repo (or ⚠️ not detected)
Token scopes ✅ repo (or ⚠️ missing — run `gh auth refresh -s repo`)
Repo write permission ✅ push/maintain/admin (or ⚠️ read-only — labels & links will fail)
Issue templates
BUG.md ✅ (or ✅ installed from references)
EPIC.md ✅ (or ✅ installed from references)
FEATURE.md ✅ (or ✅ installed from references)
TASK.md ✅ (or ✅ installed from references)
PR template ✅ (or ✅ installed from references)
Issue classification ✅ native types (Epic/Feature/Task/Bug) (or ✅ labels: epic, feature, task, bug)
Lifecycle labels ✅ implemented, designed, verified
Intervention hook ✅ installed (global) (or ✅ installed (repo) / ⚪ skipped / ⚠️ manual paste required)
Body encoding guard ✅ verified (python3) (or ⚠️ Python is 'py'/'python', not 'python3' — alias it or body ops fail / ⚠️ Python 3 not found — guard inert, raw-gh fallback / ⚠️ helper not copied)
Shared skill references ✅ installed (<skills-root>/references) (or ⚠️ missing — copy skills/references/*.md manually; other skills can't resolve ../references)
─────────────────────────
Ready to use WTF. Start with `wtf.write-epic` to plan your first initiative.
If any item failed (gh not installed, not authenticated), replace the closing line with a clear "Fix the issues above before proceeding." Do not suggest next steps.
10. Offer to set up steering docs
If setup completed without fatal errors, call AskUserQuestion (per ../references/questioning-style.md):
- question: "Setup complete. The steering docs (VISION.md, TECH.md, DESIGN.md, QA.md) capture your project's principles and standards — every skill reads them automatically. Would you like to create them now?"
- header: "Steering docs"
- options:
- Yes — set them up now → run
wtf.steer-vision(it will offer to chain to TECH, DESIGN, and QA at the end) - Not now → skip. Skills will prompt you to create them on first use
- Yes — set them up now → run