Skip to content

Commit af1a0e4

Browse files
locus313Copilot
andauthored
feat: make repo AI-ready (#10)
* feat: make repo AI-ready - Add AGENTS.md — project overview, directory map, build/run, conventions, adding-a-new-script guide - Add CHANGELOG.md — full history from initial commit (2021) through today - Add .mcp.json — GitHub MCP server config for Copilot CLI - Add .github/workflows/ci.yml — shellcheck on all .sh files for every PR - Add .github/workflows/copilot-setup-steps.yml — pre-installs jq, shellcheck, gitleaks for cloud agent - Add .github/ISSUE_TEMPLATE/bug_report.yml + feature_request.yml - Add .github/PULL_REQUEST_TEMPLATE.md — checklist derived from script conventions - Add .github/dependabot.yml — monthly GitHub Actions version updates - Update .github/copilot-instructions.md — append maintenance matrix - Update README.md — add Contributing section and AI-Ready badge Before: 2/12 assets (Getting Started) After: 12/12 assets (AI-Ready) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: declare local variable separately to fix SC2155 shellcheck warning Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4c7b045 commit af1a0e4

12 files changed

Lines changed: 683 additions & 3 deletions

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Bug Report
2+
description: Report a broken or incorrect script behaviour
3+
labels: ["bug"]
4+
body:
5+
- type: markdown
6+
attributes:
7+
value: |
8+
Thanks for taking the time to report a bug. Please fill in as much detail as possible so it can be reproduced and fixed quickly.
9+
10+
- type: textarea
11+
id: description
12+
attributes:
13+
label: What happened?
14+
description: A clear description of the incorrect behaviour.
15+
validations:
16+
required: true
17+
18+
- type: textarea
19+
id: reproduce
20+
attributes:
21+
label: Steps to reproduce
22+
description: Exact commands and environment variable exports used (redact the token value).
23+
placeholder: |
24+
export GITHUB_TOKEN=ghp_REDACTED
25+
export ORG=my-org
26+
./org-admin/github-get-repo-list/github-get-repo-list.sh
27+
validations:
28+
required: true
29+
30+
- type: textarea
31+
id: expected
32+
attributes:
33+
label: Expected behaviour
34+
description: What should the script have done?
35+
validations:
36+
required: true
37+
38+
- type: textarea
39+
id: actual
40+
attributes:
41+
label: Actual output
42+
description: Paste the full terminal output (redact any sensitive values).
43+
render: shell
44+
validations:
45+
required: true
46+
47+
- type: input
48+
id: script
49+
attributes:
50+
label: Affected script
51+
description: Which script is affected?
52+
placeholder: org-admin/github-get-repo-list/github-get-repo-list.sh
53+
validations:
54+
required: true
55+
56+
- type: dropdown
57+
id: target
58+
attributes:
59+
label: GitHub target
60+
options:
61+
- github.com
62+
- GitHub Enterprise Server (GHES)
63+
- GitHub Enterprise Cloud (GHEC)
64+
validations:
65+
required: true
66+
67+
- type: textarea
68+
id: environment
69+
attributes:
70+
label: Environment
71+
description: OS, bash version, jq version
72+
placeholder: |
73+
OS: macOS 14 / Ubuntu 22.04
74+
bash: 5.2
75+
jq: 1.7
76+
validations:
77+
required: false
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Feature Request
2+
description: Suggest a new script or an improvement to an existing one
3+
labels: ["enhancement"]
4+
body:
5+
- type: markdown
6+
attributes:
7+
value: |
8+
Got an idea for a new script or a useful improvement? Describe it below.
9+
10+
- type: textarea
11+
id: problem
12+
attributes:
13+
label: What problem does this solve?
14+
description: Describe the GitHub admin task you're trying to automate.
15+
validations:
16+
required: true
17+
18+
- type: textarea
19+
id: solution
20+
attributes:
21+
label: Proposed solution
22+
description: Describe the script or change you'd like to see, including what env vars it would accept and what it would output.
23+
validations:
24+
required: true
25+
26+
- type: dropdown
27+
id: domain
28+
attributes:
29+
label: Script domain
30+
description: Which folder would this belong in?
31+
options:
32+
- org-admin
33+
- enterprise
34+
- reporting
35+
- personal
36+
- lib (shared utility)
37+
- not sure
38+
validations:
39+
required: false
40+
41+
- type: textarea
42+
id: alternatives
43+
attributes:
44+
label: Alternatives considered
45+
description: Any other approaches or existing tools you evaluated?
46+
validations:
47+
required: false

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
## Description
2+
3+
<!-- What does this PR do? Why is it needed? -->
4+
5+
## Changes
6+
7+
<!-- List the scripts added, modified, or removed. -->
8+
9+
- [ ] New script: `<domain>/github-<name>/github-<name>.sh`
10+
- [ ] Modified: `lib/github-common.sh`
11+
- [ ] Other:
12+
13+
## How to test
14+
15+
```bash
16+
export GITHUB_TOKEN=ghp_REDACTED
17+
export ORG=test-org
18+
./path/to/script.sh [--dry-run]
19+
```
20+
21+
<!-- Describe the expected output and any edge cases tested. -->
22+
23+
## Checklist
24+
25+
- [ ] `set -euo pipefail` is the first executable line after the `# ===` header
26+
- [ ] Script sources `lib/github-common.sh` via `SCRIPT_DIR`
27+
- [ ] All required env vars validated with `require_env_var`
28+
- [ ] Token validated with `validate_github_token` (or `validate_token` for secondary tokens)
29+
- [ ] User-supplied slugs validated with `validate_slug`
30+
- [ ] `sleep` added between repo-level operations to respect rate limits
31+
- [ ] README.md updated with the new/changed script (env var table + usage example)
32+
- [ ] Script header comment matches README documentation
33+
- [ ] Tested on a non-production org before production
34+
- [ ] shellcheck passes (`shellcheck --severity=warning --exclude=SC2034,SC1091 --shell=bash <script>`)

.github/copilot-instructions.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,24 @@ For enterprise-level org iteration (GraphQL), use cursor-based pagination — se
9999
### Rate Limiting
100100

101101
Rate limiting delays are calibrated per operation type:
102+
- **Repo-level operations** (permission grants, archival): `sleep 5` between each repository.
103+
- **Code search** (`github-dockerfile-discovery`): configurable via `SEARCH_SLEEP` (default 2 s) and `CONTENT_SLEEP` (default 1 s).
104+
- **gh_api helper** (lib): auto-retries up to 5 times on HTTP 403/429, sleeping 60 s before each retry.
102105

103106
### Authentication Headers
104107

108+
- **Standard org/repo REST endpoints**: `Authorization: token ${GITHUB_TOKEN}`
109+
- **Enterprise endpoints and GraphQL**: `Authorization: Bearer ${GITHUB_TOKEN}`
110+
- **`gh_api` helper** (lib): always uses `Bearer`; pass `"bearer"` to `validate_github_token` for enterprise-level scripts.
111+
- **`validate_github_token`** wrapper: call without arguments for token auth, or pass `"bearer"` for Bearer auth.
105112

106113
### Accept Headers (Media Types)
107114

108115
Different endpoints require specific Accept headers:
116+
- **Standard REST**: `Accept: application/vnd.github+json` (used by `gh_api` helper and newer scripts)
117+
- **Legacy REST** (older scripts): `Accept: application/vnd.github.v3+json`
118+
- **GraphQL**: no Accept header required; POST to `/graphql` with `Content-Type: application/json`
119+
- **All modern API calls**: include `X-GitHub-Api-Version: 2022-11-28` (or `2026-03-10` for Copilot usage-metrics endpoints)
109120

110121
### Enterprise Org Lookup Strategy
111122

@@ -126,36 +137,80 @@ Scripts follow this validation sequence:
126137

127138
### `github-add-enterprise-team-read-permissions`
128139

140+
Uses GraphQL cursor-based pagination to enumerate all enterprise orgs, then assigns the `all_repo_read` org role (configurable via `ALL_REPO_READ_ROLE_NAME`) to the enterprise team in each org. Requires Bearer token authentication with `admin:enterprise` scope. The `ENTERPRISE_TEAM_SLUG` is the slug without the `"ent:"` prefix.
141+
129142
### `github-add-repo-collaborators-by-pattern`
130143

144+
Adds individual collaborators to org repos whose names match `REPO_NAME_REGEX` (ERE). Accepts a comma-separated `COLLABORATORS` list and an optional `REPO_EXCLUDE_REGEX` to skip matching repos. Uses `token` auth for all REST calls. Iterates all org repos paginated at 100 per page.
145+
131146
### `github-add-repo-permissions`
132147

148+
Grants team permissions across all repos in an org. Reads five space-separated team-slug lists (`REPO_ADMIN`, `REPO_MAINTAIN`, `REPO_PUSH`, `REPO_TRIAGE`, `REPO_PULL`). Optionally filters repos by name prefix via `REPO_NAME_FILTER`. Sleeps 5 s between each repo to stay within rate limits. Uses legacy `Accept: application/vnd.github.v3+json` and `token` auth.
149+
133150
### `github-archive-old-repos`
134151

152+
Calculates a cutoff date based on `YEARS_THRESHOLD` (default 5). Generates a timestamped CSV report under `reports/`, shows the top 10 oldest repos, and prompts for confirmation before archiving. Handles both BSD (`date -v`, macOS) and GNU (`date -d`, Linux) date syntax for cross-platform compatibility.
153+
135154
### `github-auto-repo-creation`
136155

156+
Creates private repos from a comma-separated `REPO_NAMES` list. Configures branch protection on the default branch, creates a CODEOWNERS file (Base64-encoded via API), and grants admin access to comma-separated `ADMIN_TEAMS`. All slug values in `REPO_NAMES`, `ADMIN_TEAMS`, and `REPO_OWNERS` are validated before use. Requires `base64` in addition to `curl` and `jq`.
157+
137158
### `github-close-archived-repo-security-alerts`
138159

160+
Dismisses open Dependabot, code-scanning, and secret-scanning alerts on all archived repos. Supports `--type` to target a single alert type and `--dry-run` to preview without changes. Generates a timestamped CSV report under `reports/`. Requires a token with `security_events` and `repo` scopes.
161+
139162
### `github-dockerfile-discovery`
140163

164+
Uses GitHub code-search API to find Dockerfiles across all enterprise orgs, then fetches and parses each file to extract `FROM` instructions (including multi-stage builds). Produces three timestamped reports in `REPORT_DIR`: a detail CSV, a summary CSV, and a plain-text summary. Supports `ORGS` override, `ORG_FILTER`/`ORG_EXCLUDE` regex filters (validated as syntactically correct ERE before use), and configurable sleep intervals (`SEARCH_SLEEP`, `CONTENT_SLEEP`).
165+
141166
### `github-enable-issues`
142167

168+
Iterates all non-archived repos in an org and enables the Issues feature on any repo where it is disabled. Supports `--dry-run` to list affected repos without making changes.
169+
143170
### `github-get-consumed-licenses`
144171

172+
Calls the enterprise consumed-licenses endpoint using Bearer authentication. Requires `read:enterprise` scope. Returns seat consumption and purchase counts. Token-only script; no pagination or retry logic beyond what the single API call provides.
173+
145174
### `github-get-public-repos`
146175

176+
Discovers all enterprise orgs (three-tier fallback via `get_enterprise_orgs`), fetches all repos in each, and filters to public visibility in `jq` (does not rely on `?type=public` query param, which is unreliable for enterprise-managed orgs). Writes a timestamped CSV to `REPORT_DIR`. Supports `ORGS` override and `ORG_FILTER`/`ORG_EXCLUDE` ERE regex filters.
177+
147178
### `github-get-repo-list`
148179

180+
Outputs a CSV row per repository (full name, owner, visibility, URL, description, fork flag, pushed/created/updated timestamps) to stdout. Pagination handled at 100 repos per page via `get_repo_page_count`.
181+
149182
### `github-import-repo`
150183

184+
Performs a full bare clone (`git clone --mirror`) of a source repo and pushes all branches, tags, and history to a new private destination repo. Validates `GIT_URL_PREFIX` against a GitHub-host allowlist before running any git operations to prevent credential leakage. Repo names and `OWNER_USERNAME` are validated as slugs.
185+
151186
### `github-migrate-internal-repos-to-private`
152187

188+
Fetches all repos with internal visibility (paginated) and converts each to private via PATCH. Logs success or failure per repo. This operation cannot be undone via the API — converting internal to private removes access from members of other enterprise orgs.
189+
153190
### `github-monthly-issues-report`
154191

192+
Generates an HTML report of issues created in a date range (`MONTH_START`/`MONTH_END`), filtered by a hardcoded label (`Linked [AC]` — edit the script to change). Uses the timeline API to track who applied labels. URL-encodes labels for API calls (e.g., `Linked [AC]``Linked%20[AC]`). Outputs to `output.txt`.
193+
155194
### `github-organize-stars`
156195

196+
Uses `gh` CLI GraphQL (not `curl`) to fetch all starred repos. Categorizes by primary language, GitHub topics, and name keywords using a `RULES` array (pipe-delimited: `List Name|LANGUAGES|TOPICS|NAME_KEYWORDS`; first matching rule wins). Caches stars at `~/.cache/gh-star-organizer/stars.json`. Supports `--dry-run`, `-y` (skip confirm), `--show-repos`, and `--no-cache`. Adds repos to Lists in batches of 25.
197+
157198
### `github-repo-from-template`
158199

200+
Creates a private repo from `TEMPLATE_REPO` (including all branches), assigns admin permissions to space-separated `REPO_ADMIN` teams, write permissions to `REPO_WRITE` teams, then invites `CD_USERNAME` as a collaborator using `CD_GITHUB_TOKEN` and auto-accepts the invitation. All slug values are validated before use.
201+
202+
### `github-repo-permissions-report`
203+
204+
Uses `gh` CLI (not `curl`/`GITHUB_TOKEN`) for all API calls. Accepts `-r OWNER/REPO`, `-b BRANCH`, and `-o FILE` flags. Outputs a CSV with two record types: `permission` (all users/teams) and `bypass_actor` (explicit PR approval bypass entries). Reports both branch protection rules and repository rulesets. Requires `gh auth login` before running — no `GITHUB_TOKEN` env var needed.
205+
206+
### `github-copilot-report`
207+
208+
Uses `gh` CLI (not `curl`/`GITHUB_TOKEN`) for all GitHub API calls; requires `gh auth refresh --scopes "read:enterprise,manage_billing:enterprise"`. Also requires `az` to be **installed** (not just logged in) — the script calls `require_command az` unconditionally before checking `--no-entra`. When `az` is logged in, enriches each user with Entra ID department and job title via `az rest`.
209+
210+
Auto-detects credits per seat from a promo/standard table keyed on plan type and today's date (promo period Jun 1 – Sep 1, 2026); override with `--credits N` or `$CREDITS_PER_SEAT_OVERRIDE`. Credits are pooled enterprise-wide, not per-user buckets. Code completions are not billed in AI credits.
211+
212+
Uses API version `2026-03-10` and the new usage-metrics NDJSON endpoints (signed download links). The legacy `/copilot/metrics` and `/copilot/usage` endpoints were closed Apr 2, 2026.
213+
159214
## Development Guidelines
160215

161216
### Adding New Scripts
@@ -188,3 +243,23 @@ Keep new scripts dependency-minimal; document any non-standard dependencies expl
188243
7. **URL encoding:** Labels in API calls must be URL-encoded (`Linked [AC]``Linked%20[AC]`)
189244
8. **Public repo filtering:** Do not rely on `?type=public` for enterprise-managed orgs — fetch all and filter in jq
190245
9. **macOS vs Linux date:** `github-archive-old-repos.sh` handles both BSD `date -v` (macOS) and GNU `date -d` (Linux)
246+
247+
## Maintenance Matrix
248+
249+
When you change one of these files, you must also update the files in the "Also update" column.
250+
251+
| When you change… | Also update |
252+
|------------------|-------------|
253+
| `lib/github-common.sh` — any public function signature or behaviour | All 19 scripts that source it; verify each caller still passes the right arguments. Check with: `grep -r "source.*github-common" . --include="*.sh"` |
254+
| `lib/github-common.sh` — add a new helper function | `AGENTS.md` shared library table; `README.md` if the function affects usage |
255+
| Any script's required env vars | That script's `# ===` header comment; the corresponding README.md section's env var table |
256+
| Any script's optional env vars or defaults | Same as above |
257+
| Any script's `--dry-run` or CLI flag behaviour | README.md usage example for that script |
258+
| `README.md` — script documentation | Verify the script's `# ===` header comment still matches (env vars, options, requirements) |
259+
| `.githooks/pre-commit` | `install-hooks.sh` if hook path or installation instructions change; README.md Best Practices section |
260+
| `install-hooks.sh` | README.md Installation section |
261+
| Add a new script | `README.md` (add use case, env var table, usage example); `CHANGELOG.md` under `[Unreleased]` |
262+
| Add a new domain folder | `README.md` top-level structure description; `AGENTS.md` Repository Structure section |
263+
| `.github/workflows/ci.yml` — shellcheck flags | `.githooks/pre-commit` shellcheck invocation (keep them in sync) |
264+
| `.github/workflows/copilot-setup-steps.yml` — tool versions | `AGENTS.md` Tech Stack table |
265+
| `AGENTS.md` | No cascade — but keep in sync with `copilot-instructions.md` if architecture changes |

.github/dependabot.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: github-actions
4+
directory: /
5+
schedule:
6+
interval: monthly
7+
commit-message:
8+
prefix: chore

.github/workflows/ci.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
paths-ignore:
6+
- '**.md'
7+
- 'LICENSE'
8+
- '.github/FUNDING.yml'
9+
push:
10+
branches:
11+
- main
12+
paths-ignore:
13+
- '**.md'
14+
- 'LICENSE'
15+
- '.github/FUNDING.yml'
16+
17+
jobs:
18+
shellcheck:
19+
name: Shellcheck
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: read
23+
steps:
24+
- name: Checkout code
25+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
26+
27+
- name: Install shellcheck
28+
run: sudo apt-get update -qq && sudo apt-get install -y shellcheck
29+
30+
- name: Run shellcheck on all scripts
31+
run: |
32+
find . -name "*.sh" -not -path "./.git/*" | sort | xargs shellcheck \
33+
--severity=warning \
34+
--exclude=SC2034,SC1091 \
35+
--shell=bash
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Copilot Setup Steps
2+
3+
on:
4+
workflow_dispatch:
5+
6+
jobs:
7+
copilot-setup-steps:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Checkout code
11+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
12+
13+
- name: Install jq
14+
run: sudo apt-get update -qq && sudo apt-get install -y jq
15+
16+
- name: Install shellcheck
17+
run: sudo apt-get install -y shellcheck
18+
19+
- name: Install gitleaks
20+
run: |
21+
GITLEAKS_VERSION=$(curl -s https://api.github.com/repos/gitleaks/gitleaks/releases/latest | jq -r .tag_name)
22+
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION#v}_linux_x64.tar.gz" \
23+
| sudo tar -xz -C /usr/local/bin gitleaks
24+
25+
- name: Verify tools
26+
run: |
27+
jq --version
28+
shellcheck --version
29+
gitleaks version

.mcp.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"mcpServers": {
3+
"github": {
4+
"command": "gh",
5+
"args": ["mcp", "serve"],
6+
"env": {}
7+
}
8+
}
9+
}

0 commit comments

Comments
 (0)