From 2123d55345d020f35c47e7053e5dd51a83c28d96 Mon Sep 17 00:00:00 2001 From: Gloire Rubambiza Date: Mon, 2 Nov 2026 12:20:53 -0500 Subject: [PATCH 1/5] fix: Resolve weekly-report generator relative to the wrapper REPORT_PY defaulted to $HOME/workspaces/shared/... (an OpenClaw-host path), so 'repoman run weekly-report' hard-failed on a stock plugin install. Default to the bundled generator relative to the wrapper ($SCRIPT_DIR/../../github-weekly-report/scripts/report.py); REPORT_PY still overrides for other layouts. Adds a regression test asserting the default no longer names a workspaces path. Fixes #126 Assisted-By: Claude Code (Anthropic AI) Assisted-By: Claude Code (Anthropic AI) Signed-off-by: Gloire Rubambiza --- scripts/weekly-report.sh | 10 ++++++---- tests/test-weekly-report.sh | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/scripts/weekly-report.sh b/scripts/weekly-report.sh index f721303..d2fa646 100755 --- a/scripts/weekly-report.sh +++ b/scripts/weekly-report.sh @@ -52,7 +52,7 @@ OPTIONS: --help, -h Show this help ENVIRONMENT: - REPORT_PY Path to report.py (default: the deployed report generator) + REPORT_PY Path to report.py (default: the bundled generator beside this skill) PREREQUISITES: python3, gh (authenticated). The repo list comes from the RepoMan enrolled set @@ -74,9 +74,11 @@ if [ -z "$repos" ]; then exit 1 fi -# Locate the generator. Default points at the deployed github-weekly-report skill -# (the upstream skill name in agent-skills); override with REPORT_PY in dev. -REPORT_PY="${REPORT_PY:-$HOME/workspaces/shared/skills/github-weekly-report/scripts/report.py}" +# Locate the generator. Default is relative to this wrapper: in the bundled +# plugin layout the github-weekly-report skill sits beside the repoman skill, so +# ../../github-weekly-report/scripts/report.py resolves to the bundled copy. +# Override with REPORT_PY for other layouts (e.g. the OpenClaw deployment). +REPORT_PY="${REPORT_PY:-$SCRIPT_DIR/../../github-weekly-report/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 diff --git a/tests/test-weekly-report.sh b/tests/test-weekly-report.sh index 453b097..4c4b226 100755 --- a/tests/test-weekly-report.sh +++ b/tests/test-weekly-report.sh @@ -78,5 +78,23 @@ if REPOMAN_REPOS_FILE="$TEST_TMPDIR/empty.json" \ echo "FAIL wrapper should error on empty enrolled set"; fail=1 fi +# REPORT_PY default is resolved RELATIVE TO THE WRAPPER (the bundled generator +# at ../../github-weekly-report/scripts/report.py), not a host-specific +# $HOME/workspaces path (rossoctl/automation#126). With REPORT_PY unset and the +# bundled copy absent (automation has the wrapper but not report.py), the +# "not found" error must name the wrapper-relative path and must NOT name a +# $HOME/workspaces path. +expected_default="$(cd "$SCRIPT_DIR/../scripts" && pwd)/../../github-weekly-report/scripts/report.py" +err=$(REPOMAN_REPOS_FILE="$TEST_TMPDIR/repos.json" \ + bash "$WRAPPER" --output "$TEST_TMPDIR/x.md" 2>&1 >/dev/null) || true +case "$err" in + *"workspaces/shared"*) + echo "FAIL default still points at the OpenClaw \$HOME/workspaces path: [$err]"; fail=1 ;; + *github-weekly-report/scripts/report.py*) + : ;; # names the bundle-relative generator — correct + *) + echo "FAIL default error names neither the bundle path nor workspaces: [$err]"; fail=1 ;; +esac + if [ "$fail" -eq 0 ]; then echo "PASS test-weekly-report.sh"; fi exit "$fail" From 98708170c51997d7392406c7a5af1f6587d9cc55 Mon Sep 17 00:00:00 2001 From: Gloire Rubambiza Date: Mon, 2 Nov 2026 13:37:10 -0500 Subject: [PATCH 2/5] test: Fix SC2034 and tighten the REPORT_PY default assertion Use expected_default in the case match (clawgenti #127): consumes the previously-unused variable (fixing the shellcheck SC2034 CI failure) and makes the regression assertion exact instead of a loose substring glob. Assisted-By: Claude Code (Anthropic AI) Signed-off-by: Gloire Rubambiza --- tests/test-weekly-report.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test-weekly-report.sh b/tests/test-weekly-report.sh index 4c4b226..dbc7107 100755 --- a/tests/test-weekly-report.sh +++ b/tests/test-weekly-report.sh @@ -90,10 +90,10 @@ err=$(REPOMAN_REPOS_FILE="$TEST_TMPDIR/repos.json" \ case "$err" in *"workspaces/shared"*) echo "FAIL default still points at the OpenClaw \$HOME/workspaces path: [$err]"; fail=1 ;; - *github-weekly-report/scripts/report.py*) - : ;; # names the bundle-relative generator — correct + *"$expected_default"*) + : ;; # names the exact wrapper-relative bundled generator — correct *) - echo "FAIL default error names neither the bundle path nor workspaces: [$err]"; fail=1 ;; + echo "FAIL default error does not name the wrapper-relative generator ($expected_default): [$err]"; fail=1 ;; esac if [ "$fail" -eq 0 ]; then echo "PASS test-weekly-report.sh"; fi From 3a4c1ad6802b92ea9b6149b79d9d9b937db69626 Mon Sep 17 00:00:00 2001 From: Gloire Rubambiza Date: Mon, 2 Nov 2026 21:28:04 -0500 Subject: [PATCH 3/5] fix: Candidate-list REPORT_PY default; prove the positive path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address Jim's review on #127: - P1: resolve REPORT_PY from a candidate list (bundled plugin path, then the OpenClaw $HOME/workspaces path), so neither layout breaks with no per-host config. The personal-path fallback is transitional (#116). - P3: relax the negative assertion to a property check (names the bundled generator, never a workspaces path) so it survives the candidate order. - P2: add a positive-path test — real bundle layout, REPORT_PY unset, the generator is found, runs, and emits the report (exit 0). Capture-then-head to avoid a SIGPIPE/pipefail exit-141. Assisted-By: Claude Code (Anthropic AI) Signed-off-by: Gloire Rubambiza --- scripts/weekly-report.sh | 21 ++++++++++++++++----- tests/test-weekly-report.sh | 36 +++++++++++++++++++++++++++++------- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/scripts/weekly-report.sh b/scripts/weekly-report.sh index d2fa646..cab70c3 100755 --- a/scripts/weekly-report.sh +++ b/scripts/weekly-report.sh @@ -74,11 +74,22 @@ if [ -z "$repos" ]; then exit 1 fi -# Locate the generator. Default is relative to this wrapper: in the bundled -# plugin layout the github-weekly-report skill sits beside the repoman skill, so -# ../../github-weekly-report/scripts/report.py resolves to the bundled copy. -# Override with REPORT_PY for other layouts (e.g. the OpenClaw deployment). -REPORT_PY="${REPORT_PY:-$SCRIPT_DIR/../../github-weekly-report/scripts/report.py}" +# Locate the generator. When REPORT_PY is unset, try candidates in order and use +# the first that exists, so both deployment layouts work with no per-host config: +# 1. bundled — the plugin layout, where github-weekly-report sits beside the +# repoman skill (the self-contained install; the primary target). +# 2. deployed — the OpenClaw host layout under $HOME/workspaces. Transitional: +# the personal-path default is tracked for removal in #116, and +# falls away entirely if OpenClaw deployment is retired. +# If neither exists, keep the bundled path so the error names the expected location. +if [ -z "${REPORT_PY:-}" ]; then + bundled="$SCRIPT_DIR/../../github-weekly-report/scripts/report.py" + deployed="$HOME/workspaces/shared/skills/github-weekly-report/scripts/report.py" + for candidate in "$bundled" "$deployed"; do + if [ -f "$candidate" ]; then REPORT_PY="$candidate"; break; fi + done + REPORT_PY="${REPORT_PY:-$bundled}" +fi 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 diff --git a/tests/test-weekly-report.sh b/tests/test-weekly-report.sh index dbc7107..08f3d3e 100755 --- a/tests/test-weekly-report.sh +++ b/tests/test-weekly-report.sh @@ -84,17 +84,39 @@ fi # bundled copy absent (automation has the wrapper but not report.py), the # "not found" error must name the wrapper-relative path and must NOT name a # $HOME/workspaces path. -expected_default="$(cd "$SCRIPT_DIR/../scripts" && pwd)/../../github-weekly-report/scripts/report.py" -err=$(REPOMAN_REPOS_FILE="$TEST_TMPDIR/repos.json" \ +# Assert the PROPERTY, not an exact recomputed path: with no bundled generator +# present (automation ships the wrapper, not report.py) and no $HOME/workspaces +# copy, the error names the bundled github-weekly-report generator and never a +# workspaces path. A property check (not an exact string) keeps this green across +# the candidate-list resolution order. +err=$(HOME="$TEST_TMPDIR/nohome" REPOMAN_REPOS_FILE="$TEST_TMPDIR/repos.json" \ bash "$WRAPPER" --output "$TEST_TMPDIR/x.md" 2>&1 >/dev/null) || true case "$err" in - *"workspaces/shared"*) - echo "FAIL default still points at the OpenClaw \$HOME/workspaces path: [$err]"; fail=1 ;; - *"$expected_default"*) - : ;; # names the exact wrapper-relative bundled generator — correct + *workspaces*) + echo "FAIL default error still names a \$HOME/workspaces path: [$err]"; fail=1 ;; + *github-weekly-report/scripts/report.py*) + : ;; # names the bundled generator — correct *) - echo "FAIL default error does not name the wrapper-relative generator ($expected_default): [$err]"; fail=1 ;; + echo "FAIL default error does not name the bundled generator: [$err]"; fail=1 ;; esac +# Positive path (P2, clawgenti/Jim #127): a real bundle layout with the generator +# present and REPORT_PY unset must FIND it, run it, and emit the report — proving +# the acceptance criterion "succeeds on a clean plugin install", not just the error. +bundle="$TEST_TMPDIR/bundle" +mkdir -p "$bundle/skills/repoman/scripts" "$bundle/skills/github-weekly-report/scripts" +cp "$SCRIPT_DIR/../scripts/"*.sh "$bundle/skills/repoman/scripts/" +printf 'import sys\nprint("RAN", " ".join(sys.argv[1:]))\n' \ + > "$bundle/skills/github-weekly-report/scripts/report.py" +# Capture full output first, then take line 1 — piping the wrapper directly into +# `head` makes head close the pipe early, and under `set -o pipefail` the SIGPIPE +# surfaces as exit 141 even though the assertion is fine. +full=$(HOME="$TEST_TMPDIR/nohome" REPOMAN_REPOS_FILE="$TEST_TMPDIR/repos.json" \ + REPOMAN_SKILL_DIR="$TEST_TMPDIR/meta" \ + bash "$bundle/skills/repoman/scripts/weekly-report.sh" 2>/dev/null) +got=$(printf '%s\n' "$full" | head -n 1) +[ "$got" = "RAN --repos rossoctl/alpha alice/beta" ] \ + || { echo "FAIL bundled default not found/run (REPORT_PY unset): [$got]"; fail=1; } + if [ "$fail" -eq 0 ]; then echo "PASS test-weekly-report.sh"; fi exit "$fail" From 232a2b95f0e4d456ae189713e42b2291d1c35d68 Mon Sep 17 00:00:00 2001 From: Gloire Rubambiza Date: Mon, 2 Nov 2026 21:31:15 -0500 Subject: [PATCH 4/5] docs: Reconcile the REPORT_PY default note with the candidate list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #116's weekly-report scope shrinks from 'remove the personal REPORT_PY default' to 'drop the personal fallback candidate' — #127 made the bundled plugin path the primary default, personal path a fallback. Assisted-By: Claude Code (Anthropic AI) Signed-off-by: Gloire Rubambiza --- docs/specs/2026-10-18-repoman-run-path-design.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/specs/2026-10-18-repoman-run-path-design.md b/docs/specs/2026-10-18-repoman-run-path-design.md index ae22150..3a6061d 100644 --- a/docs/specs/2026-10-18-repoman-run-path-design.md +++ b/docs/specs/2026-10-18-repoman-run-path-design.md @@ -461,10 +461,12 @@ leans on the capability gate). remove the hardcoded org/person defaults — `REPORT_TARGET_REPO` and `SOURCE_REPO` in `link-health-scanner.sh` / `automation-health-dashboard.sh`, the committer identity in `link-health-fixer.sh`, and the personal `REPORT_PY` - default in `weekly-report.sh` (already env-overridable). 4 behavioral - constants across 3 files. #116 carries the authoritative per-line locations; - distinct from #99 (that is config *plumbing*; this is embedded org/person - *defaults*). + **fallback** in `weekly-report.sh` (already env-overridable). As of #127 that + wrapper resolves `REPORT_PY` from a candidate list — the bundled plugin path + first, the personal `$HOME/workspaces/...` path only as a fallback — so #116's + remaining task here is dropping that fallback candidate, not the whole default. + #116 carries the authoritative per-line locations; distinct from #99 (that is + config *plumbing*; this is embedded org/person *defaults*). - **#99** — scanner/fixer/dashboard param-driven decoupling (v0.1.1). - **#82** — automation commit/sign-off identity (gates fixer orchestration). - **#76** — unify program registry. From 2ddab941b5c22b773b11366de3432873121754eb Mon Sep 17 00:00:00 2001 From: Gloire Rubambiza Date: Tue, 3 Nov 2026 13:07:30 -0500 Subject: [PATCH 5/5] test: Cover the deployed REPORT_PY fallback; guard unset HOME Round-2 review (#127): add a positive test for candidate 2 (bundled absent, $HOME/workspaces present) so dropping/reordering the fallback fails CI; guard ${HOME:-} in the deployed candidate so an unset HOME reaches the friendly diagnostic instead of a set -u abort; correct the ENVIRONMENT help to describe the candidate list; drop reviewer names from a test comment in favor of the issue ref. Assisted-By: Claude Code (Anthropic AI) Signed-off-by: Gloire Rubambiza --- scripts/weekly-report.sh | 5 +++-- tests/test-weekly-report.sh | 20 +++++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/scripts/weekly-report.sh b/scripts/weekly-report.sh index cab70c3..3500ca0 100755 --- a/scripts/weekly-report.sh +++ b/scripts/weekly-report.sh @@ -52,7 +52,8 @@ OPTIONS: --help, -h Show this help ENVIRONMENT: - REPORT_PY Path to report.py (default: the bundled generator beside this skill) + REPORT_PY Path to report.py (default: first of the bundled generator + beside this skill, then the $HOME/workspaces deploy path) PREREQUISITES: python3, gh (authenticated). The repo list comes from the RepoMan enrolled set @@ -84,7 +85,7 @@ fi # If neither exists, keep the bundled path so the error names the expected location. if [ -z "${REPORT_PY:-}" ]; then bundled="$SCRIPT_DIR/../../github-weekly-report/scripts/report.py" - deployed="$HOME/workspaces/shared/skills/github-weekly-report/scripts/report.py" + deployed="${HOME:-}/workspaces/shared/skills/github-weekly-report/scripts/report.py" for candidate in "$bundled" "$deployed"; do if [ -f "$candidate" ]; then REPORT_PY="$candidate"; break; fi done diff --git a/tests/test-weekly-report.sh b/tests/test-weekly-report.sh index 08f3d3e..334e97e 100755 --- a/tests/test-weekly-report.sh +++ b/tests/test-weekly-report.sh @@ -100,9 +100,9 @@ case "$err" in echo "FAIL default error does not name the bundled generator: [$err]"; fail=1 ;; esac -# Positive path (P2, clawgenti/Jim #127): a real bundle layout with the generator -# present and REPORT_PY unset must FIND it, run it, and emit the report — proving -# the acceptance criterion "succeeds on a clean plugin install", not just the error. +# Positive path (candidate 1, bundled — #127): a real bundle layout with the +# generator present and REPORT_PY unset must FIND it, run it, and emit the report +# — proving "succeeds on a clean plugin install", not just the error. bundle="$TEST_TMPDIR/bundle" mkdir -p "$bundle/skills/repoman/scripts" "$bundle/skills/github-weekly-report/scripts" cp "$SCRIPT_DIR/../scripts/"*.sh "$bundle/skills/repoman/scripts/" @@ -118,5 +118,19 @@ got=$(printf '%s\n' "$full" | head -n 1) [ "$got" = "RAN --repos rossoctl/alpha alice/beta" ] \ || { echo "FAIL bundled default not found/run (REPORT_PY unset): [$got]"; fail=1; } +# Positive path (candidate 2, deployed fallback — #127): bundled copy ABSENT, +# the $HOME/workspaces copy present. Proves the OpenClaw layout still resolves +# with REPORT_PY unset, so dropping or reordering the fallback candidate fails here. +rm "$bundle/skills/github-weekly-report/scripts/report.py" +mkdir -p "$TEST_TMPDIR/home/workspaces/shared/skills/github-weekly-report/scripts" +printf 'import sys\nprint("RAN", " ".join(sys.argv[1:]))\n' \ + > "$TEST_TMPDIR/home/workspaces/shared/skills/github-weekly-report/scripts/report.py" +full=$(HOME="$TEST_TMPDIR/home" REPOMAN_REPOS_FILE="$TEST_TMPDIR/repos.json" \ + REPOMAN_SKILL_DIR="$TEST_TMPDIR/meta" \ + bash "$bundle/skills/repoman/scripts/weekly-report.sh" 2>/dev/null) || true +got=$(printf '%s\n' "$full" | head -n 1) +[ "$got" = "RAN --repos rossoctl/alpha alice/beta" ] \ + || { echo "FAIL deployed fallback not used (REPORT_PY unset): [$got]"; fail=1; } + if [ "$fail" -eq 0 ]; then echo "PASS test-weekly-report.sh"; fi exit "$fail"