-
Notifications
You must be signed in to change notification settings - Fork 3
feat: Add weekly-report.sh scoped to core repos #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| # ============================================================================= | ||
| # Weekly Report (core-scoped) | ||
| # Resolves the org and the curated core-repo allowlist via the shared library, | ||
| # then invokes the Python report generator scoped to exactly those repos. | ||
| # | ||
| # Usage: | ||
| # bash weekly-report.sh --help | ||
| # bash weekly-report.sh --output /tmp/report.md --json-output /tmp/report-data.json | ||
| # bash weekly-report.sh --org rossoctl --since 2026-08-10 --until 2026-08-17 | ||
| # ============================================================================= | ||
|
|
||
| # --- Load shared library --- | ||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| # shellcheck disable=SC1091 | ||
| source "$SCRIPT_DIR/program-lib.sh" | ||
|
|
||
| # --- CLI args --- | ||
| SINCE="" | ||
| UNTIL="" | ||
| OUTPUT="" | ||
| JSON_OUTPUT="" | ||
| SHOW_HELP=false | ||
|
|
||
| while [[ $# -gt 0 ]]; do | ||
| case $1 in | ||
| --since) SINCE="$2"; shift 2 ;; | ||
| --until) UNTIL="$2"; shift 2 ;; | ||
| --output) OUTPUT="$2"; shift 2 ;; | ||
| --json-output) JSON_OUTPUT="$2"; shift 2 ;; | ||
| --profile) PROFILE_FLAG="$2"; shift 2 ;; | ||
| --org) ORG_FLAG="$2"; shift 2 ;; | ||
| --help|-h) SHOW_HELP=true; shift ;; | ||
| *) echo "Unknown option: $1" >&2; exit 1 ;; | ||
| esac | ||
| done | ||
|
|
||
| if [ "$SHOW_HELP" = true ]; then | ||
| cat << 'USAGE' | ||
| weekly-report -- Generate the weekly org report scoped to the core repos | ||
|
|
||
| USAGE: | ||
| weekly-report.sh [OPTIONS] | ||
|
|
||
| OPTIONS: | ||
| --since DATE Start of reporting window (YYYY-MM-DD; default: 7 days ago) | ||
| --until DATE End of reporting window (YYYY-MM-DD; default: today) | ||
| --output FILE Write the Markdown report to FILE (default: stdout) | ||
| --json-output FILE Write structured JSON for AI synthesis to FILE | ||
| --profile NAME Org profile to load (config/org.<name>.env; default org.env) | ||
| --org NAME GitHub org (default: from profile, config/org.env) | ||
| --help, -h Show this help | ||
|
|
||
| ENVIRONMENT: | ||
| REPORT_PY Path to report.py (default: the deployed report generator) | ||
|
|
||
| PREREQUISITES: | ||
| python3, gh (authenticated). The core-repo list comes from config/core-repos.txt | ||
| via the shared library (get_core_repos). | ||
| USAGE | ||
| exit 0 | ||
| fi | ||
|
|
||
| # Resolve org identity (sets ORG, honoring --org/--profile/env/profile precedence). | ||
| load_org_profile | ||
|
|
||
| # The curated allowlist, owner-qualified (e.g. rossoctl/operator), one per line. | ||
| repos="$(get_core_repos)" | ||
| if [ -z "$repos" ]; then | ||
| echo "Error: get_core_repos returned no repos" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Locate the generator. Default points at the deployed report generator; override | ||
| # with REPORT_PY in dev. | ||
| REPORT_PY="${REPORT_PY:-$HOME/workspaces/shared/skills/github-report-generator/scripts/report.py}" | ||
| if [ ! -f "$REPORT_PY" ]; then | ||
| echo "Error: report generator not found at: $REPORT_PY" >&2 | ||
| echo "Set REPORT_PY to the path of report.py." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Build args. $repos is intentionally unquoted so each line becomes a separate | ||
| # --repos value; core repo names never contain whitespace. | ||
| # shellcheck disable=SC2086 | ||
| set -- --org "$ORG" --repos $repos | ||
| [ -n "$SINCE" ] && set -- "$@" --since "$SINCE" | ||
| [ -n "$UNTIL" ] && set -- "$@" --until "$UNTIL" | ||
| [ -n "$OUTPUT" ] && set -- "$@" --output "$OUTPUT" | ||
| [ -n "$JSON_OUTPUT" ] && set -- "$@" --json-output "$JSON_OUTPUT" | ||
|
|
||
| exec python3 "$REPORT_PY" "$@" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| # Verifies scripts/weekly-report.sh builds the generator invocation from the | ||
| # core-repo allowlist. Hermetic: a fixture allowlist via $CORE_REPOS_FILE and a | ||
| # stub report.py via $REPORT_PY, so no gh / network / real config is touched. | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| WRAPPER="$SCRIPT_DIR/../scripts/weekly-report.sh" | ||
|
|
||
| TEST_TMPDIR=$(mktemp -d) | ||
| trap 'rm -rf "$TEST_TMPDIR"' EXIT | ||
|
|
||
| fail=0 | ||
|
|
||
| # Fixture allowlist (bare names; owner is derived from ORG). | ||
| cat > "$TEST_TMPDIR/repos.txt" <<'EOF' | ||
| # comment | ||
| alpha | ||
| beta | ||
| EOF | ||
|
|
||
| # Stub generator: echo the args it was called with. | ||
| cat > "$TEST_TMPDIR/report.py" <<'PY' | ||
| import sys | ||
| print(" ".join(sys.argv[1:])) | ||
| PY | ||
|
|
||
| got=$(ORG=rossoctl \ | ||
| CORE_REPOS_FILE="$TEST_TMPDIR/repos.txt" \ | ||
| REPORT_PY="$TEST_TMPDIR/report.py" \ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: all three invocations set That is the correct call for the two happy-path assertions: they must be hermetic, and pointing at a real deployed generator would break that. If you want a cheap regression guard for the default without giving up hermeticity, assert on its shape rather than its resolvability — e.g. run the wrapper with out=$(ORG=rossoctl CORE_REPOS_FILE="$TEST_TMPDIR/repos.txt" \
HOME="$TEST_TMPDIR/fakehome" bash "$WRAPPER" 2>&1 || true)
case "$out" in
*github-weekly-report/scripts/report.py*) ;;
*) echo "FAIL default REPORT_PY path: $out"; fail=1 ;;
esacThat pins the default against future renames while still touching no network and no real config. Entirely optional. |
||
| bash "$WRAPPER" --output /tmp/ignored.md) | ||
| want="--org rossoctl --repos rossoctl/alpha rossoctl/beta --output /tmp/ignored.md" | ||
| [ "$got" = "$want" ] || { echo "FAIL wrapper args: got [$got] want [$want]"; fail=1; } | ||
|
|
||
| # --since / --until pass through. | ||
| got2=$(ORG=rossoctl \ | ||
| CORE_REPOS_FILE="$TEST_TMPDIR/repos.txt" \ | ||
| REPORT_PY="$TEST_TMPDIR/report.py" \ | ||
| bash "$WRAPPER" --since 2026-08-10 --until 2026-08-17) | ||
| want2="--org rossoctl --repos rossoctl/alpha rossoctl/beta --since 2026-08-10 --until 2026-08-17" | ||
| [ "$got2" = "$want2" ] || { echo "FAIL wrapper window args: got [$got2] want [$want2]"; fail=1; } | ||
|
|
||
| # Missing generator fails loud. | ||
| if ORG=rossoctl CORE_REPOS_FILE="$TEST_TMPDIR/repos.txt" \ | ||
| REPORT_PY="$TEST_TMPDIR/does-not-exist.py" \ | ||
| bash "$WRAPPER" >/dev/null 2>&1; then | ||
| echo "FAIL wrapper should error on missing REPORT_PY"; fail=1 | ||
| fi | ||
|
|
||
| if [ "$fail" -eq 0 ]; then echo "PASS test-weekly-report.sh"; fi | ||
| exit "$fail" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: the default path names a skill directory that does not exist, so the documented default can never resolve.
The directory in
agent-skillsisgithub-weekly-report, notgithub-report-generator:skills/github-report-generatorreturns 404, and an org-wide code search forgithub-report-generatorfinds it nowhere outside this PR. The companion PR (agent-skills#32) editsskills/github-weekly-report/scripts/report.py.So anyone invoking the wrapper without presetting
REPORT_PYgets:The fail-loud handling right below is good — it turns this into a clear error rather than something silent — but the default should point at the real directory:
REPORT_PY="${REPORT_PY:-$HOME/workspaces/shared/skills/github-weekly-report/scripts/report.py}"Worth double-checking the
$HOME/workspaces/shared/skills/...prefix against how the deployment actually lays out skills, since I could only verify the trailing directory name from the repo.