Skip to content
8 changes: 8 additions & 0 deletions skills/github-conversation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ gh llm doctor

## Fast start

### Preflight an unfamiliar repo

```bash
gh-llm repo preflight --repo <owner/repo>
```

Use this before forking or opening a PR when you need the default branch, onboarding docs (`CONTRIBUTING*`, `AGENTS.md`, PR template, `CODEOWNERS`), branch-protection summary, and likely next commands in one place.

### Read a PR

```bash
Expand Down
2 changes: 2 additions & 0 deletions src/gh_llm/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
parse_review_ids as _parse_review_ids,
register_pr_parser,
)
from gh_llm.commands.repo import register_repo_parser
from gh_llm.diagnostics import GhCommandError, format_command_error
from gh_llm.invocation import detect_prog_name

Expand Down Expand Up @@ -60,6 +61,7 @@ def _build_parser() -> argparse.ArgumentParser:
subparsers = parser.add_subparsers(dest="command")
register_pr_parser(subparsers)
register_issue_parser(subparsers)
register_repo_parser(subparsers)
Comment thread
ShigureNyako marked this conversation as resolved.
register_doctor_parser(subparsers)

return parser
Expand Down
238 changes: 238 additions & 0 deletions src/gh_llm/commands/repo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
from __future__ import annotations

from typing import TYPE_CHECKING, Any

from gh_llm.github_api import GitHubClient
from gh_llm.invocation import display_command_with

if TYPE_CHECKING:
from gh_llm.models import RepoDocument, RepoPreflight


def register_repo_parser(subparsers: Any) -> None:
repo_parser = subparsers.add_parser("repo", help="Repository-level onboarding commands")
repo_subparsers = repo_parser.add_subparsers(dest="repo_command")

preflight_parser = repo_subparsers.add_parser(
"preflight",
help="show repo-level preflight summary",
)
preflight_parser.add_argument("--repo", required=True, help="repository in OWNER/REPO format")
preflight_parser.set_defaults(handler=cmd_repo_preflight)


def cmd_repo_preflight(args: Any) -> int:
client = GitHubClient()
preflight = client.resolve_repo_preflight(str(args.repo))
for line in render_repo_preflight(preflight):
print(line)
return 0


def render_repo_preflight(preflight: RepoPreflight) -> list[str]:
return [
*render_repo_frontmatter(preflight),
"",
*render_repo_summary(preflight),
*render_repo_onboarding_files(preflight),
*render_repo_branch_protection(preflight),
*render_repo_next_commands(preflight),
]


def render_repo_frontmatter(preflight: RepoPreflight) -> list[str]:
lines = [
"---",
f"repo: {preflight.owner}/{preflight.name}",
f"url: {preflight.url}",
f"default_branch: {preflight.default_branch}",
f"viewer_permission: {preflight.viewer_permission or 'UNKNOWN'}",
f"can_push: {str(preflight.can_push).lower()}",
f"fork_recommended: {str(preflight.fork_recommended).lower()}",
f"is_fork: {str(preflight.is_fork).lower()}",
f"tree_truncated: {str(preflight.tree_truncated).lower()}",
]
if preflight.parent_repo:
lines.append(f"parent_repo: {preflight.parent_repo}")
if preflight.branch_protection is not None:
lines.append(f"branch_protection_pattern: {preflight.branch_protection.pattern}")
lines.append("---")
return lines


def render_repo_summary(preflight: RepoPreflight) -> list[str]:
permission = preflight.viewer_permission or "UNKNOWN"
https_clone_url = (
preflight.url + ".git" if preflight.url else f"https://github.com/{preflight.owner}/{preflight.name}.git"
)
lines = [
"## Repository",
f"Description: {preflight.description or '(no description)'}",
f"Default branch: `{preflight.default_branch}`",
f"Homepage: {preflight.homepage_url or '(none)'}",
f"HTTPS clone: `{https_clone_url}`",
f"SSH clone: `{preflight.ssh_url or '(not available)'}`",
f"Push access: {'yes' if preflight.can_push else 'no'} (viewer_permission: {permission})",
f"Fork before push: {'yes' if preflight.fork_recommended else 'no'}",
]
if preflight.is_fork and preflight.parent_repo:
lines.append(f"Parent repo: `{preflight.parent_repo}`")
if preflight.tree_truncated:
lines.append(
"Warning: recursive repository tree output was truncated; onboarding file detection used common-path fallback and may still be incomplete."
)
lines.append("")
return lines


def render_repo_onboarding_files(preflight: RepoPreflight) -> list[str]:
repo = f"{preflight.owner}/{preflight.name}"
return [
"## Onboarding Files",
*render_repo_document_group(
title="CONTRIBUTING",
docs=preflight.contributing_docs,
repo=repo,
default_branch=preflight.default_branch,
),
*render_repo_document_group(
title="AGENTS",
docs=preflight.agents_docs,
repo=repo,
default_branch=preflight.default_branch,
),
*render_repo_document_group(
title="PR Templates",
docs=preflight.pr_templates,
repo=repo,
default_branch=preflight.default_branch,
),
*render_repo_document_group(
title="CODEOWNERS",
docs=preflight.codeowners_files,
repo=repo,
default_branch=preflight.default_branch,
),
"",
]


def render_repo_document_group(
*,
title: str,
docs: tuple[RepoDocument, ...],
repo: str,
default_branch: str,
) -> list[str]:
lines = [f"### {title}"]
if not docs:
lines.append("(not found)")
return lines

for doc in docs:
browse_cmd = _gh_browse_command(repo=repo, default_branch=default_branch, path=doc.path)
lines.append(f"- `{doc.path}`")
lines.append(f" ⏎ open: `{browse_cmd}`")
return lines


def render_repo_branch_protection(preflight: RepoPreflight) -> list[str]:
lines = ["## Branch Protection"]
protection = preflight.branch_protection
if protection is None:
lines.append(f"Default branch `{preflight.default_branch}` is not protected.")
lines.append("")
return lines

if protection.source == "graphql":
lines.append(f"Matched rule: `{protection.pattern}`")
else:
lines.append(f"Protected branch: `{protection.pattern}`")
lines.append("Review-related rule details were not available from branch rule queries.")
if protection.requires_status_checks:
if protection.required_status_check_contexts:
lines.append(
"Required checks: " + ", ".join(f"`{name}`" for name in protection.required_status_check_contexts)
)
else:
lines.append("Required checks: enabled, but GitHub did not return named contexts.")
else:
lines.append("Required checks: not enabled")

if protection.requires_approving_reviews is None:
lines.append("Approving reviews: unknown")
elif protection.requires_approving_reviews:
required = protection.required_approving_review_count or 1
lines.append(f"Approving reviews: required ({required})")
else:
lines.append("Approving reviews: not required")

if protection.requires_code_owner_reviews is None:
lines.append("Code owner reviews: unknown")
else:
lines.append(
"Code owner reviews: " + ("required" if protection.requires_code_owner_reviews else "not required")
)

if protection.is_admin_enforced is None:
lines.append("Admin enforcement: unknown")
else:
lines.append("Admin enforcement: " + ("enabled" if protection.is_admin_enforced else "disabled"))
lines.append("")
return lines


def render_repo_next_commands(preflight: RepoPreflight) -> list[str]:
repo = f"{preflight.owner}/{preflight.name}"
pr_target_repo = preflight.parent_repo if preflight.is_fork and preflight.parent_repo is not None else repo
lines = [
"## Next Commands",
"These commands are inferred from the permission, onboarding-file, and default-branch protection signals above.",
]

step = 1
if preflight.fork_recommended:
lines.append(f"{step}. Create your fork before pushing:")
lines.append(f" ⏎ `gh repo fork {repo} --clone`")
else:
lines.append(f"{step}. Clone the repository:")
lines.append(f" ⏎ `gh repo clone {repo}`")
step += 1

doc_steps = [
("Read contribution guidance", preflight.contributing_docs),
("Read AGENTS instructions", preflight.agents_docs),
("Inspect the PR template before opening a pull request", preflight.pr_templates),
("Check CODEOWNERS to see who may review your changes", preflight.codeowners_files),
]
for title, docs in doc_steps:
if not docs:
continue
lines.append(f"{step}. {title}:")
lines.append(
f" ⏎ `{_gh_browse_command(repo=repo, default_branch=preflight.default_branch, path=docs[0].path)}`"
)
step += 1

if pr_target_repo != repo:
lines.append(f"{step}. Open your PR against the parent repository:")
else:
lines.append(f"{step}. Open your PR against the default branch:")
lines.append(f" ⏎ `gh pr create --repo {pr_target_repo} --base {preflight.default_branch}`")
step += 1

protection = preflight.branch_protection
if protection is not None and protection.requires_status_checks:
lines.append(f"{step}. Check required CI after the PR exists:")
lines.append(f" ⏎ `{display_command_with(f'pr checks --pr <pr_number> --repo {pr_target_repo}')}`")

lines.append("")
return lines


def _gh_browse_command(*, repo: str, default_branch: str, path: str) -> str:
return f"gh browse -R {repo} --branch {default_branch} {_shell_single_quote(path)}"


def _shell_single_quote(value: str) -> str:
return "'" + value.replace("'", "'\"'\"'") + "'"
Loading
Loading