Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .claude/skills/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Run `python3 <skill>/scripts/<name>` (`bash` for `.sh`). `name: purpose — argu
- merge_train.py: land several green PRs as ONE verified union (a merge train). — plan --base main [--repo OWNER/NAME]…
- mutation_changed.py: Mutation testing restricted to the lines a diff changed. — [--base origin/main] [--max-seconds…
- operating_selfcheck.py: per-item PRESENT / MISSING / COULD_NOT_CHECK report for the… — [--settings FILE] [--skill-root DIR]
- org_settings_audit.py: read-only audit of GitHub org settings, then each repo's. — ORG [--max-repos N] [--token-env VAR]…
- partial_pass.py: PARTIAL-PASS receipt for a PR whose precheck ended… — write PR --head SHA --unchecked NAME…
- perun_policy.py: read the one resource-policy file, .perun/policy.json. — get <dim>
- pipeline_hub.py: one entry point that says where a delivery pipeline loses… — [--repo OWNER/NAME] [--root DIR]…
Expand Down Expand Up @@ -147,6 +148,9 @@ Run `python3 <skill>/scripts/<name>` (`bash` for `.sh`). `name: purpose — argu
- impact_map.py: map a diff's changed symbols to callers and callees. — [--base REF] [--repo DIR] [--cap N]
- lint_ui_slop.py: zero-token, deterministic lint for common UI defects in HTML… — [--json] [--tokens FILE] PATH...
- merge_findings.py: dedupe, rank and cap findings from one or more passes. — [--cap N] [--root DIR] [--ref REF]…
- org_audit_driver.py: review_checks.sh over a repo list, one result file per repo. — --repos FILE --out DIR [--timeout SECS]
- org_inventory.py: dated TSV inventory of GitHub org repos, reconciled with a… — --org ORG [--org ORG ...] [--out DIR]…
- org_leak_sweep.py: scan an org's public repos (or one local repo) for secrets and… — --org ORG [--banlist FILE] [--out DIR]…
- outline.py: cheap symbol map (line ranges) so a lane reads only the ranges… — FILE...
- ownership_sibling_check.py: lead-finder for broken object-level authorization (OWASP API1… — PATH...
- parity_differ.py: the two-sided design<->app parity gate: sections first, then… — --design <file> --app <file> [--accept…
Expand Down
3 changes: 2 additions & 1 deletion .claude/skills/agentic-delivery/INDEX-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Generated by `scripts/skill_index.py` (do not hand-edit). The script half of `IN
| `scripts/merge_train.py` | land several green PRs as ONE verified union (a merge train). | merge_train.py plan --base main [--repo OWNER/NAME] [--min-checks K] merge_train.py verify --base main --verify-cmd 'ma… |
| `scripts/mutation_changed.py` | Mutation testing restricted to the lines a diff changed. | mutation_changed.py [--base origin/main] [--max-seconds 300] [--strict] [--selftest] |
| `scripts/operating_selfcheck.py` | per-item PRESENT / MISSING / COULD_NOT_CHECK report for the always-on operating layer references/op… | operating_selfcheck.py [--settings FILE] [--skill-root DIR] operating_selfcheck.py --selftest |
| `scripts/org_settings_audit.py` | org_settings_audit.py: read-only audit of GitHub org settings, then each repo's. | org_settings_audit.py ORG [--max-repos N] [--token-env VAR] [--json] |
| `scripts/outcome_claim_guard.py` | Stop + PreToolUse + UserPromptSubmit hook: no "fixed / faster / done" on delivery outcomes without… | — |
| `scripts/partial_pass.py` | PARTIAL-PASS receipt for a PR whose precheck ended COULD_NOT_CHECK on some checks (#1778). | partial_pass.py write PR --head SHA --unchecked NAME [NAME ...] |
| `scripts/peer_ack.py` | read receipts for peer directives, on the coordination board: no ack within T seconds means undeliv… | — |
Expand All @@ -84,7 +85,7 @@ Generated by `scripts/skill_index.py` (do not hand-edit). The script half of `IN
| `scripts/refix_gate.py` | re-touching a recently-fixed file requires a class-level artifact. | refix_gate.py --base REF --head REF [--window-hours 72] [--class-glob GLOB ...] refix_gate.py --selftest |
| `scripts/repeat_poll_guard.py` | PreToolUse(Bash) hook: nudge when the same command runs more than 3 times in one session. | — |
| `scripts/reply_cap.py` | owner rule: chat replies max 3 lines, 500 chars. | — |
| `scripts/repo_settings_audit.py` | repo_settings_audit.py: read-only audit of the GitHub settings that decide who can change a reposit… | repo_settings_audit.py OWNER/NAME [OWNER/NAME ...] [--repos-file FILE] [--json] --repos-file holds one OWNER/NAME per l… |
| `scripts/repo_settings_audit.py` | repo_settings_audit.py: read-only audit of the GitHub settings that decide who can change a reposit… | repo_settings_audit.py OWNER/NAME [OWNER/NAME ...] [--repos-file FILE] [--json] [--scorecard] --repos-file holds one OW… |
| `scripts/restore_ledger.py` | classify every branch after a VCS history-rewrite incident. | — |
| `scripts/review_digest.py` | a returning reviewer's change digest, not a full re-read. | review_digest.py --since SHA [--head REF] [--paths PATH ...] [--max-lines N] [--allow-diverged] review_digest.py --self… |
| `scripts/review_gate.py` | pre-merge gate: every PR needs an independent deep-code-review DIFF pass plus an evil-twin check. | review_gate.py check <pr> --head SHA [--repo R] |
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/agentic-delivery/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ Generated by `scripts/skill_index.py` (do not hand-edit; rerun it after changing

## Scripts

110 scripts. Read `INDEX-scripts.md` when you need a script (a trigger and CLI per script). `../INDEX.md`, `### agentic-delivery`, has only each one-line purpose and arguments.
111 scripts. Read `INDEX-scripts.md` when you need a script (a trigger and CLI per script). `../INDEX.md`, `### agentic-delivery`, has only each one-line purpose and arguments.
153 changes: 153 additions & 0 deletions .claude/skills/agentic-delivery/scripts/org_settings_audit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/usr/bin/env python3
"""org_settings_audit.py: read-only audit of GitHub org settings, then each repo's.

WHY: branch protection, required 2FA, Actions token defaults, installed apps and self-hosted runners are not visible in
code, yet they decide whether a review gate means anything (the CIS GitHub benchmark covers them). Per repository this
reuses `repo_settings_audit.audit` unchanged (branch protection and rulesets, Actions, collaborators, deploy keys,
webhooks, environments, secret scanning and push protection); this file adds the organization-level checks:
2FA `two_factor_requirement_enabled` on the organization (owner token only)
Actions organization default workflow token has write, workflows can approve reviews, allowed actions = all
apps installed GitHub Apps holding admin or write on repository administration or contents (count)
outside people outside collaborators (count)
self-hosted organization-level self-hosted runners (count)
A 403 or 404, a missing field or an odd reply is a `not checked` row that names the token scope or permission it needs,
never a pass.

USAGE: org_settings_audit.py ORG [--max-repos N] [--token-env VAR] [--json]

Token: the env var named by --token-env, else `gh auth token`; it reaches curl on stdin only (never argv), as in
org_leak_sweep.py. `GITHUB_API_URL` overrides https://api.github.com (tests point it at a stub).
Output: `{"org": rows, "repos": {"OWNER/NAME": rows}, "summary": {...}}` with `--json`, else one table.
Exit codes: 0 the audit ran (findings do not change it), 2 usage or no token.
Side effects: HTTP GET only; nothing is written or changed. Counts and booleans only: no login, app name or API text.
Limits: first --max-repos repositories (default 200); a longer org says so in a not-checked row.
"""
from __future__ import annotations

import argparse
import json
import os
import re
import subprocess
import sys
import urllib.parse

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import repo_settings_audit as rsa # noqa: E402

API = "https://api.github.com"
ORG = re.compile(r"[A-Za-z0-9][A-Za-z0-9-]*")


def curl_get(token):
"""get(path, params=None) -> parsed JSON over curl; failure raises OSError('... (HTTP n)') for `rsa.why`."""
def get(path, params=None):
url = os.environ.get("GITHUB_API_URL", API) + path + ("?" + urllib.parse.urlencode(params) if params else "")
cfg = 'header = "Authorization: Bearer ' + token.replace("\\", "\\\\").replace('"', '\\"') + '"\n'
r = subprocess.run(["curl", "-sS", "-w", "\n%{http_code}", "-H", "Accept: application/vnd.github+json",
"--config", "-", url], input=cfg, capture_output=True, text=True, errors="replace")
body, _, code = r.stdout.rpartition("\n")
if r.returncode or not code.isdigit():
raise OSError("API error")
if int(code) >= 400:
try:
msg = str(json.loads(body).get("message", ""))
except (ValueError, AttributeError):
msg = ""
raise OSError("%s (HTTP %s)" % (msg, code))
return json.loads(body)
return get


def need(scope):
return ("needs " + scope, "re-run with a token that has " + scope)


def org_rows(get, org):
"""(check, severity, fix) tuples for the organization itself; any failed read is a not-checked row."""
base, rows = "/orgs/" + org, []
o, e = rsa.read(get, base, dict)
if e:
return [rsa.nc("organization", rsa.why(e, need("read access to the organization")))]
tf = o.get("two_factor_requirement_enabled") # absent unless the token is an org owner
if tf is None:
rows.append(rsa.nc("2FA requirement", need("owner access (admin:org)")))
elif tf is not True:
rows.append(("2FA requirement is off", "high", "enable 'Require two-factor authentication' in the organization security settings"))
adm = "admin:org (fine-grained: Administration read)"
w, e = rsa.read(get, base + "/actions/permissions/workflow", dict,
need=(("default_workflow_permissions",), ("can_approve_pull_request_reviews",)))
if e:
rows.append(rsa.nc("org Actions token", rsa.why(e, need(adm))))
else:
if w["default_workflow_permissions"] == "write":
rows.append(("org default workflow token can write", "medium", "set the organization default workflow permissions to read"))
if w["can_approve_pull_request_reviews"] is True:
rows.append(("org workflows can approve pull request reviews", "medium", "turn off 'Allow GitHub Actions to create and approve pull requests'"))
a, e = rsa.read(get, base + "/actions/permissions", dict, need=(("enabled_repositories",),))
if e:
rows.append(rsa.nc("org allowed actions", rsa.why(e, need(adm))))
elif a.get("allowed_actions") == "all":
rows.append(("org allows all actions", "medium", "restrict allowed actions to GitHub-owned and an allowlist"))
ins, e = rsa.read(get, base + "/installations", dict, need=(("installations",),))
if e:
rows.append(rsa.nc("installed apps", rsa.why(e, need("admin:read (fine-grained: Administration read)"))))
else:
n = sum(1 for i in ins["installations"] if isinstance(i, dict) and any(
(i.get("permissions") or {}).get(k) in ("write", "admin") for k in ("administration", "contents")))
rows.append(("%d installed app(s) hold write or admin on administration or contents" % n, "low" if n else "info",
"review each app's permissions in the organization installed apps settings"))
oc, e = rsa.listing(get, base + "/outside_collaborators")
rows.append(rsa.nc("outside collaborators", rsa.why(e, need("admin:org (owner)"))) if e else
("%d outside collaborator(s)" % len(oc), "low" if oc else "info", "confirm each still needs access"))
rn, e = rsa.listing(get, base + "/actions/runners", key="runners")
rows.append(rsa.nc("self-hosted runners", rsa.why(e, need("admin:org (fine-grained: Self-hosted runners read)"))) if e else
("%d org self-hosted runner(s)" % len(rn), "medium" if rn else "info",
"keep self-hosted runners off public repositories and use ephemeral runners"))
return rows


def audit(org, get, max_repos=200):
"""(org rows, {slug: rows}); an unreadable or over-long repo list is a not-checked org row."""
keys = ("check", "severity", "fix")
rows = [dict(zip(keys, r)) for r in org_rows(get, org)]
repos, e = rsa.listing(get, "/orgs/%s/repos" % org, {"type": "all"}, need=(("full_name",),))
if e:
rows.append(dict(zip(keys, rsa.nc("repository list", rsa.why(e, need("read access to the organization's repositories"))))))
repos = []
if len(repos) > max_repos:
rows.append(dict(zip(keys, rsa.nc("repository list", ("capped at %d repos" % max_repos, "raise --max-repos")))))
rows.sort(key=lambda r: rsa.SEVERITY.index(r["severity"]))
return rows, {r["full_name"]: rsa.audit(r["full_name"], get) for r in repos[:max_repos]}


def main(argv=None, get=None):
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
ap.add_argument("org")
ap.add_argument("--max-repos", type=int, default=200)
ap.add_argument("--token-env")
ap.add_argument("--json", action="store_true")
a = ap.parse_args(argv)
if not ORG.fullmatch(a.org):
print("org_settings_audit: not an organization name", file=sys.stderr)
return 2
if get is None:
tok = os.environ.get(a.token_env, "") if a.token_env else subprocess.run(
["gh", "auth", "token"], capture_output=True, text=True).stdout.strip()
if not tok:
print("org_settings_audit: no token (set --token-env VAR or run `gh auth login`)", file=sys.stderr)
return 2
get = curl_get(tok)
org, repos = audit(a.org, get, a.max_repos)
flat = [r for v in repos.values() for r in v]
summ = {"repos": len(repos), "findings": sum(r["severity"] in rsa.FINDING for r in org + flat),
"not_checked": sum(r["severity"] == "not-checked" for r in org + flat)}
if a.json:
print(json.dumps({"org": org, "repos": repos, "summary": summ}, indent=2))
else:
print(rsa.render([dict(r, repo=a.org) for r in org] + flat, len(repos))[0])
return 0


if __name__ == "__main__":
sys.exit(main())
3 changes: 3 additions & 0 deletions .claude/skills/deep-code-review/INDEX-scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ Generated by `scripts/skill_index.py` (do not hand-edit). The script half of `IN
| `scripts/invisible_chars.py` | flag NUL, control and hidden characters in text files. | — |
| `scripts/lint_ui_slop.py` | zero-token, deterministic lint for common UI defects in HTML, CSS, JSX and TSX. | lint_ui_slop.py [--json] [--tokens FILE] PATH... lint_ui_slop.py --hook < hook-event.json |
| `scripts/merge_findings.py` | dedupe, rank and cap findings from one or more passes. | merge_findings.py [--cap N] [--root DIR] [--ref REF] <findings.json>... |
| `scripts/org_audit_driver.py` | org_audit_driver.py: review_checks.sh over a repo list, one result file per repo. | org_audit_driver.py --repos FILE --out DIR [--timeout SECS] |
| `scripts/org_inventory.py` | org_inventory.py: dated TSV inventory of GitHub org repos, reconciled with a previous run. | org_inventory.py --org ORG [--org ORG ...] [--out DIR] [--previous TSV] [--results-dir DIR] [--max-age-days N] [--token… |
| `scripts/org_leak_sweep.py` | org_leak_sweep.py: scan an org's public repos (or one local repo) for secrets and banned terms. | org_leak_sweep.py --org ORG [--banlist FILE] [--out DIR] [--token-env VAR] org_leak_sweep.py --local REPO [--banlist FI… |
| `scripts/outline.py` | cheap symbol map (line ranges) so a lane reads only the ranges it needs, not the whole file. | outline.py FILE... |
| `scripts/ownership_sibling_check.py` | lead-finder for broken object-level authorization (OWASP API1, CWE-639) by sibling comparison. | ownership_sibling_check.py PATH... |
| `scripts/parity_differ.py` | the two-sided design<->app parity gate: sections first, then the element inventory of every matched… | parity_differ.py --design <file> --app <file> [--accept <tsv> [--accept-rev REV]] [--min-pairs N] [--style [--style-min… |
Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/deep-code-review/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Generated by `scripts/skill_index.py` (do not hand-edit; rerun it after changing
| `references/testing-ui.md` | the target renders a UI or ships browser / E2E specs. | 4868 |
| `references/threat-modeling.md` | a change adds a trust boundary, an auth path, a data flow to a new party, an agent or tool capabili… | 2574 |
| `references/time-date-correctness.md` | reviewing anything that stores, computes, schedules, or renders timestamps, durations, recurring ev… | 4809 |
| `references/tool-hub.md` | a review should lean on external scanners via the tool hub (workflows, Python, Go, SBOM/lockfiles,… | 3942 |
| `references/tool-hub.md` | a review should lean on external scanners via the tool hub (workflows, Python, Go, SBOM/lockfiles,… | 4853 |
| `references/ux-components.md` | the target or diff renders one concept in more than one place: a shared component or design token,… | 7235 |
| `references/ux-dataviz.md` | the target or diff renders a chart, sparkline, heat-map or calendar grid, a KPI / metric delta, a c… | 2787 |
| `references/ux-gates.md` | a UI change is claimed verified on screenshot evidence, the project's auto-merge or CI policy gates… | 3756 |
Expand All @@ -153,4 +153,4 @@ Generated by `scripts/skill_index.py` (do not hand-edit; rerun it after changing

## Scripts

48 scripts. Read `INDEX-scripts.md` when you need a script (a trigger and CLI per script). `../INDEX.md`, `### deep-code-review`, has only each one-line purpose and arguments.
51 scripts. Read `INDEX-scripts.md` when you need a script (a trigger and CLI per script). `../INDEX.md`, `### deep-code-review`, has only each one-line purpose and arguments.
Loading
Loading