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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
reports/
*.log

# Local copies of host cron/job state (contains deployment internals; never commit)
.host-backup/
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,28 @@ reports/ (gitignored) Runtime data stays on remote host only
| Dependency Bump | `scripts/dep-bump-scanner.sh` | `scripts/dep-bump-fixer.sh` | Tracks and triages Dependabot PRs across repos (comments, closes stale, audits coverage) |
| Health Dashboard | `scripts/automation-health-dashboard.sh` | — | Aggregates program run health into a dashboard |

Shared helpers live in `scripts/program-lib.sh`. Repo coverage comes from the `config/core-repos.txt` allowlist via `get_core_repos()` (one `owner/name` per line; `#` comments and blank lines allowed) -- edit that file to change which repos are scanned.
Shared helpers live in `scripts/program-lib.sh`. Repo coverage comes from the `config/core-repos.txt` allowlist via `get_core_repos()` (one **bare repo name** per line -- the owner is prepended from the active org; `#` comments and blank lines allowed) -- edit that file to change which repos are scanned.

## Org profile

Org identity (which GitHub org the programs target) lives in one committed profile file, `config/org.env`. It assigns only `PROFILE_`-prefixed keys:

```bash
PROFILE_ORG=rossoctl # the org (required)
PROFILE_FORK_OWNER=clawgenti # fork account for cross-fork PRs
PROFILE_MAIN_REPO=rossoctl/rossoctl
PROFILE_REPOS_DIR=/home/claw/rossoctl
PROFILE_REMAP="kagenti:rossoctl kagenti-extensions:cortex" # transitional
```

Each program calls `load_org_profile()` (in `program-lib.sh`), which resolves the identity by precedence **`--flag` > env var > profile > built-in default**:

- `--org NAME` / `ORG` -- the org (fails loud if it cannot be resolved).
- `--fork-owner NAME` / `FORK_OWNER` -- fork account (default `clawgenti`).
- `--repos-dir DIR` / `REPOS_DIR` -- local clone root (default `$HOME/$ORG`).
- `--main-repo-dir DIR` / `MAIN_REPO_DIR` -- the main-repo clone for dashboard/report git ops.

`PROFILE_REMAP` is profile-only (transitional; maps pre-rename clone-dir names to canonical names). To target a **different org**, copy `config/org.env` to `config/org.<name>.env`, edit the `PROFILE_*` values, and run any program with `--profile <name>` (or `ORG_PROFILE=<name>`). No per-script edits are needed.

## Deploy

Expand All @@ -35,10 +56,10 @@ scp scripts/<name>.sh kagenti-bot:~/workspaces/clawgenti/scripts/

No gateway restart needed -- scripts are read from disk on each cron trigger.

Scanners/fixers that use the allowlist also require `config/core-repos.txt` to be present on the host alongside the scripts:
All programs resolve their org identity from `config/org.env`, and allowlist-driven scanners/fixers also read `config/core-repos.txt`. Both must be present on the host alongside the scripts:

```bash
scp config/core-repos.txt kagenti-bot:~/workspaces/clawgenti/config/
scp config/org.env config/core-repos.txt kagenti-bot:~/workspaces/clawgenti/config/
```

## Runtime
Expand Down
20 changes: 20 additions & 0 deletions automation-health/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# automation-health

Machine-generated reports from the automation programs. These files are
**overwritten in place on every run** by their generating scripts — do not edit
them by hand, and do not add dated copies.

| File | Generated by | Cadence |
|------|--------------|---------|
| `link-health.md` | `scripts/link-health-scanner.sh` | per scan run |
| `automation-health.md` | `scripts/automation-health-dashboard.sh` | per dashboard run |

## Why a single overwritten file (not dated snapshots)

Trends are reconstructed by replaying the file's git commit history (parent by
parent), so each commit already captures a point-in-time snapshot. Storing dated
copies would duplicate what git already records — the files-vs-diffs-on-Git
anti-pattern. A future trend tool should read `git log` for these paths rather
than expect a directory of dated files.

See rossoctl/automation#44 for the decision.
6 changes: 6 additions & 0 deletions config/org.env
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ PROFILE_FORK_OWNER=clawgenti
PROFILE_MAIN_REPO=rossoctl/rossoctl
PROFILE_REPOS_DIR=${HOME}/rossoctl

# Repo where this suite (scripts, skills, standing orders) is version-controlled.
# Report PRs link back to the invoking program's standing order here for
# auditability. Defaults to "$ORG/automation" if unset; override if a fork keeps
# its automation elsewhere.
# PROFILE_SOURCE_REPO=rossoctl/automation

# TRANSITIONAL: maps pre-rename clone-dir basenames to canonical repo names.
# Self-retires once host clone dirs are renamed (rossoctl/automation#37):
# delete this line and the remap becomes pure identity.
Expand Down
35 changes: 29 additions & 6 deletions docs/running-without-openclaw.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,33 @@ None of these are required for the scripts to work. They're convenience features

## Adapting for a Different Org

To run against a different GitHub org:
Org identity is no longer edited into each script. It lives in a committed
profile file, `config/org.env`, resolved by `load_org_profile()` with
precedence `--flag > env > profile > default`. To run against a different org:

1. Clone that org's repos into `$REPOS_DIR`
2. Edit `FORK_OWNER` in the fixer (or set it as an env var)
3. Edit `ORG="rossoctl"` in the fixer to your org name
4. Update DCO identity in the fixer (`GIT_AUTHOR_NAME` / `GIT_AUTHOR_EMAIL`)
5. Run the scanner -- it will detect broken links and create issues in the target repos
1. Copy the default profile and edit the `PROFILE_*` values for your org:

```bash
cp config/org.env config/org.myorg.env
# edit config/org.myorg.env:
# PROFILE_ORG=myorg
# PROFILE_FORK_OWNER=myfork
# PROFILE_MAIN_REPO=myorg/myorg
# PROFILE_REPOS_DIR=$HOME/myorg
# PROFILE_REMAP="" # only for transitional dir renames
```

2. Clone that org's repos into the profile's `PROFILE_REPOS_DIR` (or override
at run time with `--repos-dir` / `$REPOS_DIR`).

3. Run any program with `--profile myorg` (or `ORG_PROFILE=myorg`):

```bash
bash scripts/link-health-scanner.sh --profile myorg --dry-run
```

Individual facts can still be overridden per run without touching the
profile, e.g. `--org myorg --fork-owner myfork --repos-dir ~/myorg`.

4. Update the DCO identity in the fixer (`GIT_AUTHOR_NAME` / `GIT_AUTHOR_EMAIL`)
if fix PRs should be attributed to a different maintainer.
101 changes: 64 additions & 37 deletions scripts/automation-health-dashboard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -euo pipefail

# =============================================================================
# Automation Health Dashboard Generator — kagenti org
# Automation Health Dashboard Generator
# Combines link-health and dep-bump program metrics into a single executive-
# facing markdown dashboard. Pushes to a standing fork-based PR.
#
Expand All @@ -13,23 +13,24 @@ set -euo pipefail
# =============================================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
source "$SCRIPT_DIR/program-lib.sh"

# --- CLI args ---
DRY_RUN=true
ORG="kagenti"
VERBOSE=false
SHOW_HELP=false
FORK_OWNER="${FORK_OWNER:-clawgenti}"
KAGENTI_DIR="${KAGENTI_DIR:-}"
MAIN_REPO_DIR="${MAIN_REPO_DIR:-}"

while [[ $# -gt 0 ]]; do
case $1 in
--dry-run) DRY_RUN=true; shift ;;
--live) DRY_RUN=false; shift ;;
--reports-dir) REPORTS_DIR="$2"; shift 2 ;;
--kagenti-dir) KAGENTI_DIR="$2"; shift 2 ;;
--org) ORG="$2"; shift 2 ;;
--fork-owner) FORK_OWNER="$2"; shift 2 ;;
--main-repo-dir) MAIN_REPO_DIR="$2"; shift 2 ;;
--profile) PROFILE_FLAG="$2"; shift 2 ;;
--org) ORG_FLAG="$2"; shift 2 ;;
--fork-owner) FORK_OWNER_FLAG="$2"; shift 2 ;;
--verbose) VERBOSE=true; shift ;;
--help|-h) SHOW_HELP=true; shift ;;
*) echo "Unknown option: $1"; exit 1 ;;
Expand All @@ -49,21 +50,41 @@ Usage:
Options:
--dry-run Generate and preview dashboard (default)
--live Commit and push to fork, create/update PR
--reports-dir DIR Base reports directory (default: $REPORTS_DIR or ./reports)
--kagenti-dir DIR Path to kagenti repo clone (default: $KAGENTI_DIR)
--org NAME GitHub org (default: kagenti)
--fork-owner NAME Fork owner for PR workflow (default: clawgenti)
--verbose Print diagnostic output
--help, -h Show this help
--reports-dir DIR Base reports directory (default: $REPORTS_DIR or ./reports)
--main-repo-dir DIR Path to the report-target repo clone, overriding the
REPOS_DIR-derived default (default: $MAIN_REPO_DIR)
--profile NAME Org profile to load (config/org.<name>.env; default org.env)
--org NAME GitHub org (default: from profile, config/org.env)
--fork-owner NAME Fork owner for PR workflow (default: from profile)
--verbose Print diagnostic output
--help, -h Show this help

Environment:
REPORTS_DIR Base directory containing link-scan/ and dep-bump/ subdirs
KAGENTI_DIR Path to the org's main repo clone (for live mode git operations)
FORK_OWNER Fork owner for cross-fork PRs
REPORTS_DIR Base directory containing link-scan/ and dep-bump/ subdirs
MAIN_REPO_DIR Path to the report-target repo clone (live mode git ops);
overrides the REPOS_DIR-derived default
FORK_OWNER Fork owner for cross-fork PRs
HELP
exit 0
fi

# Resolve org identity (--flag > env > profile > default). Sets ORG, FORK_OWNER,
# MAIN_REPO, REPOS_DIR, REMAP.
load_org_profile

# Report-PR destination. The org main repo's docs/ folder feeds the docs site
# (rossoctl.dev) and cannot host machine-generated reports, so the standing
# dashboard PR lands under automation-health/ in the automation repo. A single
# file, overwritten in place each run: trend tooling reconstructs history by
# replaying git commit parents, so we store state (not dated snapshots) and
# avoid the files-vs-diffs-on-Git anti-pattern (rossoctl/automation#44).
REPORT_TARGET_REPO="$ORG/automation"
REPORT_TARGET_NAME="${REPORT_TARGET_REPO##*/}"
REPORT_TARGET_PATH="automation-health/automation-health.md"
# Clone dir for the report target: honor an explicit --main-repo-dir/MAIN_REPO_DIR
# override, else derive from REPOS_DIR.
REPORT_TARGET_DIR="${MAIN_REPO_DIR:-$REPOS_DIR/$REPORT_TARGET_NAME}"

# --- Validate inputs ---
if [ -z "${REPORTS_DIR:-}" ]; then
if [ -d "./reports" ]; then
Expand Down Expand Up @@ -418,7 +439,7 @@ $COVERAGE_TABLE
$CRON_TABLE

---
*Generated by Kagenti Automation Health Dashboard. Do not edit manually.*
*Generated by Rossoctl Automation Health Dashboard. Do not edit manually.*
DASHBOARD_EOF

echo "Dashboard generated ($TMPDIR/automation-health.md)"
Expand All @@ -433,29 +454,31 @@ if [ "$DRY_RUN" = true ]; then
echo "---"
cat "$TMPDIR/automation-health.md"
echo "---"
echo "[DRY RUN] Would push docs/automation-health.md to fork and create/update PR"
echo "[DRY RUN] Would push $REPORT_TARGET_PATH to fork and create/update PR against $REPORT_TARGET_REPO"
else
if [ -z "$KAGENTI_DIR" ]; then
echo "ERROR: KAGENTI_DIR is not set (required for live mode)."
echo "Export it to the path of the kagenti/kagenti repo clone:"
echo " export KAGENTI_DIR=~/kagenti/kagenti"
if [ -z "$REPORT_TARGET_DIR" ]; then
echo "ERROR: report target clone dir is not set (required for live mode)."
echo "Export MAIN_REPO_DIR or set REPOS_DIR so $REPORT_TARGET_REPO can be found:"
echo " export MAIN_REPO_DIR=$REPOS_DIR/$REPORT_TARGET_NAME"
Comment on lines +459 to +462

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

L86 sets this now, so this appears to be dead code

exit 1
fi

if [ ! -d "$KAGENTI_DIR/.git" ]; then
echo "ERROR: $KAGENTI_DIR does not appear to be a git repository."
if [ ! -d "$REPORT_TARGET_DIR/.git" ]; then
echo "ERROR: $REPORT_TARGET_DIR does not appear to be a git repository."
exit 1
fi

FORK_REMOTE="$FORK_OWNER"
DASHBOARD_BRANCH="automation/health-dashboard"

cd "$KAGENTI_DIR"
cd "$REPORT_TARGET_DIR"

# Ensure fork remote exists
if ! git remote get-url "$FORK_REMOTE" &>/dev/null; then
git remote add "$FORK_REMOTE" "https://github.com/$FORK_OWNER/$ORG.git"
fi
# Ensure the fork remote exists AND points at the current target. set-url
# corrects a stale remote (e.g. one left by a prior deployment pointing at the
# old report repo); the || add branch handles the not-yet-registered case.
fork_url="https://github.com/$FORK_OWNER/${REPORT_TARGET_NAME}.git"
git remote set-url "$FORK_REMOTE" "$fork_url" 2>/dev/null \
|| git remote add "$FORK_REMOTE" "$fork_url"

# Fetch fork's branch if it exists, otherwise create from main
if git fetch "$FORK_REMOTE" "$DASHBOARD_BRANCH" 2>/dev/null; then
Expand All @@ -466,19 +489,19 @@ else
|| git checkout -B "$DASHBOARD_BRANCH"
fi

mkdir -p docs
cp "$TMPDIR/automation-health.md" docs/automation-health.md
git add docs/automation-health.md
mkdir -p "$(dirname "$REPORT_TARGET_PATH")"
cp "$TMPDIR/automation-health.md" "$REPORT_TARGET_PATH"
git add "$REPORT_TARGET_PATH"
git commit -s -m "docs: Update automation health dashboard ($SCAN_TIME_ET)" 2>/dev/null || echo "No changes to commit"
git push "$FORK_REMOTE" "$DASHBOARD_BRANCH" 2>/dev/null || echo "WARN: Failed to push dashboard to fork"

# Create or update standing cross-fork PR
existing_pr=$(gh api "repos/$ORG/$ORG/pulls?head=$FORK_OWNER:$DASHBOARD_BRANCH&state=open" \
existing_pr=$(gh api "repos/$REPORT_TARGET_REPO/pulls?head=$FORK_OWNER:$DASHBOARD_BRANCH&state=open" \
--jq '.[0].number' 2>/dev/null || echo "")

pr_body="## Summary

Auto-updated by Kagenti Automation Health Dashboard. This PR is continuously updated with each generation. Merge when convenient.
Auto-updated by Rossoctl Automation Health Dashboard. This PR is continuously updated with each generation. Merge when convenient.

| Metric | Value |
|--------|-------|
Expand All @@ -489,15 +512,19 @@ Auto-updated by Kagenti Automation Health Dashboard. This PR is continuously upd

## Related issue(s)

- kagenti/kagenti#1260"
- $MAIN_REPO#1260

## Automation program

Generated by the [Rossoctl Automation Health Dashboard](https://github.com/$SOURCE_REPO/blob/main/standing-orders/health-dashboard.md)."

if [ -z "$existing_pr" ] || [ "$existing_pr" = "null" ]; then
gh pr create --repo "$ORG/$ORG" \
gh pr create --repo "$REPORT_TARGET_REPO" \
--head "$FORK_OWNER:$DASHBOARD_BRANCH" --base main \
--title "docs: Automation health dashboard (auto-updated)" \
--body "$pr_body" 2>/dev/null || echo "WARN: Failed to create dashboard PR"
else
gh pr edit "$existing_pr" --repo "$ORG/$ORG" --body "$pr_body" 2>/dev/null || true
gh pr edit "$existing_pr" --repo "$REPORT_TARGET_REPO" --body "$pr_body" 2>/dev/null || true
fi

echo "Dashboard committed and pushed"
Expand Down
25 changes: 17 additions & 8 deletions scripts/dep-bump-fixer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ source "$SCRIPT_DIR/program-lib.sh"
# --- CLI args ---
DRY_RUN=true # Safe by default
ISSUE_LIMIT=15
ORG="kagenti"
VERBOSE=false
SHOW_HELP=false

Expand All @@ -29,7 +28,8 @@ while [[ $# -gt 0 ]]; do
--dry-run) DRY_RUN=true; shift ;;
--live) DRY_RUN=false; shift ;;
--issue-limit) ISSUE_LIMIT="$2"; shift 2 ;;
--org) ORG="$2"; shift 2 ;;
--profile) PROFILE_FLAG="$2"; shift 2 ;;
--org) ORG_FLAG="$2"; shift 2 ;;
--verbose) VERBOSE=true; shift ;;
--help|-h) SHOW_HELP=true; shift ;;
*) echo "Unknown option: $1"; exit 1 ;;
Expand All @@ -47,7 +47,8 @@ OPTIONS:
--dry-run Analyze and preview comments only (default)
--live Post comments on PRs and issues
--issue-limit N Process at most N issues (default: 5)
--org NAME GitHub org (default: kagenti)
--profile NAME Org profile to load (config/org.<name>.env; default org.env)
--org NAME GitHub org (default: from profile, config/org.env)
--verbose Print additional diagnostic output
--help, -h Show this help

Expand All @@ -61,15 +62,23 @@ USAGE
exit 0
fi

# Resolve org identity (--org > env > profile > default). Sets ORG, FORK_OWNER,
# MAIN_REPO, REPOS_DIR, REMAP. Reads use canonical $ORG/<name>; write paths
# (fork PRs) derive from $ORG/$FORK_OWNER.
load_org_profile

# --- Configuration ---
validate_repos_dir "${REPOS_DIR:-}"

REPORTS_DIR="${REPORTS_DIR:-./reports/dep-bump}"
SCAN_DATE=$(date -u +"%Y-%m-%d")
SCAN_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
MAX_HISTORY_ROWS=500
FORK_OWNER="clawgenti"
FIXER_SIGNATURE="Automated analysis by Kagenti Dep Bump Fixer"
# Marker appended to every fixer comment; also the dedup key (line 278 searches
# existing comments for it before posting). Changing this string means comments
# carrying the OLD marker are no longer recognized, so an already-analyzed PR
# may receive one duplicate comment on the next run — acceptable, one-time.
FIXER_SIGNATURE="Automated analysis by Rossoctl Dep Bump Fixer"

# --- Workspace setup ---
setup_workspace "dep-bump-fixer"
Expand Down Expand Up @@ -111,7 +120,7 @@ if [ ! -f "$REPORTS_DIR/baseline.json" ]; then
esac
SEEN_CANON="$SEEN_CANON $canon"

gh pr list --repo "rossoctl/$canon" \
gh pr list --repo "$ORG/$canon" \
--author "app/dependabot" \
--state merged \
--json number,createdAt,mergedAt \
Expand Down Expand Up @@ -179,7 +188,7 @@ for repo_dir in "$REPOS_DIR"/*/ "$REPOS_DIR"/.github/; do
SEEN_CANON="$SEEN_CANON $canon"

REPOS_CHECKED=$((REPOS_CHECKED + 1))
full_repo="rossoctl/$canon"
full_repo="$ORG/$canon"

issues_json=$(gh issue list --repo "$full_repo" \
--search "[dep-bump] in:title" \
Expand Down Expand Up @@ -554,7 +563,7 @@ for repo_dir in "$REPOS_DIR"/*/ "$REPOS_DIR"/.github/; do
esac
SEEN_CANON_TTM="$SEEN_CANON_TTM $canon"

gh pr list --repo "rossoctl/$canon" \
gh pr list --repo "$ORG/$canon" \
--author "app/dependabot" \
--state merged \
--json number,createdAt,mergedAt \
Expand Down
Loading