diff --git a/scripts/pyauto_status_full.sh b/scripts/pyauto_status_full.sh new file mode 100755 index 00000000..c1ef0785 --- /dev/null +++ b/scripts/pyauto_status_full.sh @@ -0,0 +1,213 @@ +#!/usr/bin/env bash +# pyauto_status_full.sh — release-prep run dashboard. +# +# Defines a shell function `pyauto-status-full` that reads the latest +# PyAutoBuild full release-prep run (the one symlinked from +# PyAutoBuild/test_results/latest/) and prints a dashboard: +# +# - Run timestamp + path + ready/not-ready verdict + total duration +# - Per-workspace pass / fail / skipped / timeout / duration table +# - Failure counts grouped by classification +# - Top-25 slowest scripts (any status) — surfaces timing regressions +# before they cross the timeout threshold +# - Slow-skip / needs-fix banner counts +# - Pointer to triage.md if present (free-form analytical clustering) +# +# Usage: +# source ~/Code/PyAutoLabs/PyAutoPrompt/scripts/pyauto_status_full.sh +# pyauto-status-full +# +# Override the run path (e.g. to inspect a specific historical run) +# by passing it as the first argument: +# pyauto-status-full ~/Code/PyAutoLabs/PyAutoBuild/test_results/runs/2026-04-29T14-48-47Z +# +# Note: this shell function shares its name with the /pyauto-status-full +# slash command (PyAutoPrompt/skills/pyauto-status-full/) but lives in a +# different namespace. The slash command is the conversational layer; this +# function is the same data, printed straight to stdout, no Claude needed. + +PYAUTO_STATUS_FULL_DEFAULT="${PYAUTO_STATUS_FULL_DEFAULT:-$HOME/Code/PyAutoLabs/PyAutoBuild/test_results/latest}" + +pyauto-status-full() { + local run_dir="${1:-$PYAUTO_STATUS_FULL_DEFAULT}" + + if [[ ! -e "$run_dir" ]]; then + cat >&2 <&2 + return 1 + fi + + python3 - "$run_dir" <<'PY' +import json +import sys +from pathlib import Path + +run_dir = Path(sys.argv[1]) +with open(run_dir / "report.json") as f: + r = json.load(f) + +ready = r.get("ready") +total = float(r.get("total_duration_seconds", 0.0) or 0.0) +summary = r.get("summary", {}) or {} +n_pass = summary.get("passed", 0) +n_fail = summary.get("failed", 0) +n_skip = summary.get("skipped", 0) +n_to = summary.get("timeout", 0) + +GREEN = "\033[32m" +RED = "\033[31m" +YEL = "\033[33m" +DIM = "\033[2m" +RST = "\033[0m" + +verdict = f"{GREEN}READY{RST}" if ready else f"{RED}NOT READY{RST}" +print(f"{'=' * 76}") +print(f" PyAuto Status Full") +print(f"{'=' * 76}") +print(f"Run: {r.get('run_label','')}") +print(f"Path: {run_dir}") +print(f"Status: {verdict} (passed: {n_pass}, failed: {n_fail}, skipped: {n_skip}, timeout: {n_to})") +print(f"Total: {total:.1f}s ({total/60:.1f} min)") +print() + +# Per-workspace +print("Per-workspace") +print("-" * 76) +print(f"{'Workspace':<22} {'Passed':>6} {'Failed':>6} {'Skipped':>7} {'Timeout':>7} {'Duration':>10}") +pp = r.get("per_project", {}) or {} +ppd = r.get("per_project_duration_seconds", {}) or {} +for proj in sorted(pp.keys()): + c = pp[proj] + f = c.get("failed", 0) + t = c.get("timeout", 0) + color = GREEN if (f == 0 and t == 0) else RED + print( + f"{color}{proj:<22}{RST} " + f"{c.get('passed',0):>6} {f:>6} " + f"{c.get('skipped',0):>7} {t:>7} " + f"{ppd.get(proj,0):>9.1f}s" + ) +print() + +# Failures by classification +failures = r.get("failures", []) or [] +if failures: + by_class = {} + for fr in failures: + cls = fr.get("classification", "unknown") + by_class.setdefault(cls, []).append(fr) + labels = { + "source_code_bug": "Source code bugs", + "workspace_issue": "Workspace issues", + "workspace_data": "Missing data files", + "environment": "Environment issues", + "timeout": "Timeouts", + "known_numerical": "Known numerical", + "unknown": "Unclassified", + } + print(f"Failures by classification ({len(failures)} total)") + print("-" * 76) + for cls in sorted(by_class.keys(), key=lambda c: -len(by_class[c])): + items = by_class[cls] + print(f" {labels.get(cls, cls):<22} {len(items)}") + print() + +# Slowest 25 +slowest = r.get("slowest", []) or [] +if slowest: + print(f"Slowest {len(slowest)} scripts") + print("-" * 76) + print(f"{'Duration':>9} {'Status':<8} {'Project':<16} Script") + for s in slowest: + proj = s.get("project", "") + stat = s.get("status", "") + fil = s.get("file", "") + # Trim absolute paths to last 3 segments for readability. + short = "/".join(fil.split("/")[-3:]) + dur = float(s.get("duration_seconds", 0.0) or 0.0) + color = RED if stat in ("failed", "timeout") else (YEL if dur > 180 else "") + print(f"{color}{dur:>8.1f}s {stat:<8} {proj:<16} {short}{RST}") + print() + +# Parked scripts banners +slow_skips = r.get("slow_skips") or [] +nf_skips = r.get("needs_fix_skips") or [] +if slow_skips or nf_skips: + print("Parked scripts (workspace no_run.yaml banners)") + print("-" * 76) + if slow_skips: + print(f" SLOW skips: {len(slow_skips)} (need performance fix)") + if nf_skips: + print(f" NEEDS_FIX skips: {len(nf_skips)} (parked broken)") + print() + +# Pointers +print("Pointers") +print("-" * 76) +print(f" Markdown report: {run_dir}/report.md {DIM}(pyauto-report){RST}") +print(f" Run JSON: {run_dir}/report.json {DIM}(pyauto-json){RST}") +triage = run_dir / "triage.md" +if triage.exists(): + print(f" {GREEN}Triage notes: {triage}{RST} {DIM}(pyauto-triage){RST}") +PY +} + +# _pyauto_run_file [run-dir-arg] — resolve a file inside the latest +# (or supplied) run directory. Used by the pyauto-{report,json,triage} viewers. +_pyauto_run_file() { + local subpath="$1" + local run_dir="${2:-$PYAUTO_STATUS_FULL_DEFAULT}" + + if [[ ! -e "$run_dir" ]]; then + echo "pyauto: no run found at $run_dir" >&2 + return 1 + fi + run_dir="$(readlink -f "$run_dir")" + + local target="$run_dir/$subpath" + if [[ ! -f "$target" ]]; then + echo "pyauto: $target missing" >&2 + return 1 + fi + printf '%s' "$target" +} + +# pyauto-report [run-dir] — view report.md in the pager. +pyauto-report() { + local f + f="$(_pyauto_run_file report.md "$1")" || return 1 + "${PAGER:-less}" "$f" +} + +# pyauto-json [run-dir] — view report.json. Uses jq for color + paging when +# available, falls back to plain cat otherwise. +pyauto-json() { + local f + f="$(_pyauto_run_file report.json "$1")" || return 1 + if command -v jq >/dev/null 2>&1; then + jq -C . "$f" | "${PAGER:-less}" -R + else + "${PAGER:-less}" "$f" + fi +} + +# pyauto-triage [run-dir] — view triage.md in the pager. +pyauto-triage() { + local f + f="$(_pyauto_run_file triage.md "$1")" || return 1 + "${PAGER:-less}" "$f" +} diff --git a/skills/pyauto-status-full/pyauto-status-full.md b/skills/pyauto-status-full/pyauto-status-full.md new file mode 100644 index 00000000..70d985cf --- /dev/null +++ b/skills/pyauto-status-full/pyauto-status-full.md @@ -0,0 +1,110 @@ +# PyAuto Status Full: Latest Release-Prep Run Dashboard + +Render the most recent PyAutoBuild full-run report as a release-readiness dashboard. Use this to inspect timing and failures from the last `python autobuild/run_all.py`. Read-only. + +## Usage + +``` +/pyauto-status-full +``` + +This skill is the deeper sibling of `/pyauto-status`: + +- `/pyauto-status` — what's in flight right now (planned/active/recently-completed tasks, branches, dirty repos). +- `/pyauto-status-full` — what the last full release-prep run produced (per-workspace pass/fail/timing, slowest scripts, failure tracebacks). + +## Steps + +### 1. Locate the latest run + +PyAutoBuild stores every full run under `~/Code/PyAutoLabs/PyAutoBuild/test_results/runs//` and updates a `latest` symlink on success. + +```bash +LATEST=~/Code/PyAutoLabs/PyAutoBuild/test_results/latest +``` + +If the symlink does not exist, no full run has completed yet. Print: + +``` +No full release-prep run on disk. + +To produce one, from PyAutoBuild root: + source ../activate.sh + python autobuild/run_all.py +``` + +…and exit. Do not invent data. + +### 2. Read the aggregated report + +```bash +cat "$LATEST/report.json" +``` + +The JSON is produced by `aggregate_results.aggregate()` and contains: + +- `ready` — boolean release-readiness gate +- `run_label`, `run_path`, `total_duration_seconds` +- `summary` — totals by status (passed / failed / skipped / timeout) +- `per_project` + `per_project_duration_seconds` — per-workspace counts and total wall-clock +- `slowest` — top-25 slowest scripts (any status), with `duration_seconds`, `status`, `project` +- `failures` — every failed/timed-out script with `classification`, `error_message`, `traceback` +- `failure_pr_correlations` — which failures coincide with recently-merged PRs +- `slow_skips`, `needs_fix_skips` — surfaced from each workspace's `config/build/no_run.yaml` +- `runs` — per-job manifest (no per-result records, just metadata) + +### 3. Render the dashboard + +Print a concise Markdown dashboard that includes: + +**Header** + +``` +Run: +Path: +Status: READY | NOT READY (failures: N, timeouts: M) +Total: s +``` + +**Per-workspace table** — columns: Workspace, Passed, Failed, Skipped, Timeout, Duration. Sort alphabetically. + +**Failures (if any)** — group by `classification`. For each failure, show: + +- Script path +- One-line error message +- PR correlations from `failure_pr_correlations[]` if present +- Last 10 lines of `traceback` in a fenced block + +**Slowest scripts (top 25)** — table of Script | Project | Status | Duration | Share. The `Share` column is `duration / total_duration_seconds * 100`. + +**Slow-skip / needs-fix banners** — render `report["slow_skips"]` and `report["needs_fix_skips"]` verbatim using the same table format the markdown report uses (workspace, pattern, marked-date, age, reason). + +**Footer** + +``` +Open full markdown report: /report.md +Open run JSON: /report.json +``` + +### 4. Drift since previous run (optional) + +If the parent `runs/` directory contains at least two run subdirs, locate the second-most-recent one (sort by name descending, skip `latest`). Read its `report.json` and produce a "Drift since previous run" section showing: + +- Per-workspace passed-count delta (`+N`, `-N`, `0`) +- Any test that was passed previously and is now failed/timeout (regression) +- Any test that was failed/timeout previously and is now passed (recovery) +- Top-5 timing regressions: scripts whose duration grew by ≥ 50 % between runs + +If only one run exists, omit this section silently. + +### 5. Notes + +- Read-only — never modifies PyAutoBuild output, never deletes runs, never touches the `latest` symlink. +- No GitHub posting (matches `/pyauto-status` convention). The user can decide to share findings manually. +- For quick browsing, the markdown report at `/report.md` already contains the same data — this skill is the conversational summary layer on top. + +## Remote / mobile mode + +This skill reads local artefacts produced by an autobuild run on the laptop. There is no mobile equivalent — full runs only happen on the dev machine. + +If `~/Code/PyAutoLabs/PyAutoBuild/test_results/latest` does not exist (e.g. the user is on a fresh machine or has never run `run_all.py`), print the same "no run on disk" message from step 1 and exit.