Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
for t in tests/test-lib-inventory.sh tests/test-lib-modules.sh \
tests/test-core-repos.sh tests/test-org-profile.sh \
tests/test-extract-broken-links.sh tests/test-parse-diff-map.sh \
tests/test-pr-review-impact.sh; do
tests/test-pr-review-impact.sh tests/test-weekly-report.sh; do
echo "== $t =="
bash "$t"
done
Expand Down
94 changes: 94 additions & 0 deletions scripts/weekly-report.sh
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}"

Copy link
Copy Markdown
Member

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-skills is github-weekly-report, not github-report-generator:

skills/
  automation-health-dashboard
  dep-bump-fixer
  dep-bump-scanner
  github-pr-review
  github-weekly-report   <-- here
  link-health-fixer
  link-health-scanner

skills/github-report-generator returns 404, and an org-wide code search for github-report-generator finds it nowhere outside this PR. The companion PR (agent-skills#32) edits skills/github-weekly-report/scripts/report.py.

So anyone invoking the wrapper without presetting REPORT_PY gets:

Error: report generator not found at: $HOME/workspaces/shared/skills/github-report-generator/scripts/report.py

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.

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" "$@"
52 changes: 52 additions & 0 deletions tests/test-weekly-report.sh
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" \

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: all three invocations set REPORT_PY explicitly (here, line 39, and line 46), so the built-in default is never exercised — which is why the wrong path in the other comment ships with CI fully green.

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 REPORT_PY unset and HOME pointed at a temp dir, then check that the error message names the expected skill directory:

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 ;;
esac

That 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"
Loading