Browse Source

feat(mac-ops): Adopt TERMINAL-DESIGN panel UI (v2.8.0)

- New skills/mac-ops/scripts/_lib/panel.sh wraps term.sh and collects
  findings transparently via log_pass/fail/warn/info overrides
- health-audit.sh and quickrun.sh emit state-grouped tree panels with
  brand glyph, summary line, and health footer indicator
- Refactored term.sh to drop declare -A (bash 4-only) in favour of a
  flat case lookup โ€” panels now work on stock macOS bash 3.2
- Added [mac-ops]="๐Ÿฉบ|[M]" brand glyph
- JSON_MODE never renders a panel (NDJSON contract preserved)
- 115 mac-ops tests still pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0xDarkMatter 2 weeks ago
parent
commit
eaad08ca8b

+ 1 - 1
.claude-plugin/plugin.json

@@ -1,6 +1,6 @@
 {
   "name": "claude-mods",
-  "version": "2.7.8",
+  "version": "2.8.0",
   "description": "Custom commands, skills, and agents for Claude Code - session continuity, 23 expert agents, 78 skills, 2 commands, 6 rules, 4 hooks, 13 output styles, modern CLI tools",
   "author": "0xDarkMatter",
   "repository": "https://github.com/0xDarkMatter/claude-mods",

File diff suppressed because it is too large
+ 3 - 0
README.md


+ 34 - 42
skills/_lib/term.sh

@@ -49,42 +49,8 @@ TERM_ICON_WARN=""
 TERM_ICON_HINT=""
 
 # โ”€โ”€โ”€ Registries (Unicode|ASCII) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
-declare -A TERM_BRAND=(
-  [fleet]="โšก|[F]"
-  [forge]="๐Ÿ”จ|[B]"
-  [psql]="๐Ÿ˜|[P]"
-  [watch]="๐Ÿ“ก|[M]"
-  [deploy]="๐Ÿš€|[D]"
-  [git]="๐ŸŒฟ|[G]"
-  [windows-ops]="๐Ÿฉบ|[H]"
-)
-
-declare -A TERM_HEALTH_GLYPH=(
-  [healthy]="โ€ข|(+)"
-  [pending]="โ€ข|(.)"
-  [warning]="โ€ข|(!)"
-  [critical]="โ€ข|(!!)"
-  [busted]="โฌค|(X)"
-  [unknown]="โ€ข|(?)"
-)
-
-declare -A TERM_DIAGRAM_ICON=(
-  [user]="๐Ÿ‘ค|(U)"
-  [web]="๐ŸŒ|(W)"
-  [mobile]="๐Ÿ“ฑ|(M)"
-  [auth]="๐Ÿ”|(A)"
-  [database]="๐Ÿ—„|(D)"
-  [cache]="โšก|(C)"
-  [queue]="๐Ÿ“จ|(Q)"
-  [storage]="๐Ÿ“ฆ|(P)"
-  [service]="โš™|*"
-  [api]="๐Ÿ”Œ|(I)"
-  [search]="๐Ÿ”|(S)"
-  [timer]="โฑ|(T)"
-  [build]="๐Ÿ”จ|(B)"
-  [hook]="๐Ÿช|(H)"
-  [log]="๐Ÿ“„|(F)"
-)
+# Implemented as case statements in __term_lookup below (bash 3.2 compatible โ€”
+# stock macOS bash lacks associative arrays).
 
 # Header indicator glyph (branch/โއ)
 TERM_GLYPH_BRANCH=""
@@ -205,12 +171,38 @@ term_color() {
 # term_emoji <registry_name> <key>  โ€” returns Unicode glyph or ASCII fallback.
 # Internal helper; pass "BRAND", "HEALTH_GLYPH", "DIAGRAM_ICON".
 __term_lookup() {
-  local map=$1 key=$2 entry uni ascii
-  case "$map" in
-    BRAND)         entry="${TERM_BRAND[$key]:-}" ;;
-    HEALTH_GLYPH)  entry="${TERM_HEALTH_GLYPH[$key]:-}" ;;
-    DIAGRAM_ICON)  entry="${TERM_DIAGRAM_ICON[$key]:-}" ;;
-    *)             entry="" ;;
+  local map=$1 key=$2 entry="" uni ascii
+  case "${map}::${key}" in
+    BRAND::fleet)               entry="โšก|[F]" ;;
+    BRAND::forge)               entry="๐Ÿ”จ|[B]" ;;
+    BRAND::psql)                entry="๐Ÿ˜|[P]" ;;
+    BRAND::watch)               entry="๐Ÿ“ก|[M]" ;;
+    BRAND::deploy)              entry="๐Ÿš€|[D]" ;;
+    BRAND::git)                 entry="๐ŸŒฟ|[G]" ;;
+    BRAND::windows-ops)         entry="๐Ÿฉบ|[H]" ;;
+    BRAND::mac-ops)             entry="๐Ÿฉบ|[M]" ;;
+    HEALTH_GLYPH::healthy)      entry="โ€ข|(+)" ;;
+    HEALTH_GLYPH::pending)      entry="โ€ข|(.)" ;;
+    HEALTH_GLYPH::warning)      entry="โ€ข|(!)" ;;
+    HEALTH_GLYPH::critical)     entry="โ€ข|(!!)" ;;
+    HEALTH_GLYPH::alarm)        entry="โ€ข|(!!)" ;;
+    HEALTH_GLYPH::busted)       entry="โฌค|(X)" ;;
+    HEALTH_GLYPH::unknown)      entry="โ€ข|(?)" ;;
+    DIAGRAM_ICON::user)         entry="๐Ÿ‘ค|(U)" ;;
+    DIAGRAM_ICON::web)          entry="๐ŸŒ|(W)" ;;
+    DIAGRAM_ICON::mobile)       entry="๐Ÿ“ฑ|(M)" ;;
+    DIAGRAM_ICON::auth)         entry="๐Ÿ”|(A)" ;;
+    DIAGRAM_ICON::database)     entry="๐Ÿ—„|(D)" ;;
+    DIAGRAM_ICON::cache)        entry="โšก|(C)" ;;
+    DIAGRAM_ICON::queue)        entry="๐Ÿ“จ|(Q)" ;;
+    DIAGRAM_ICON::storage)      entry="๐Ÿ“ฆ|(P)" ;;
+    DIAGRAM_ICON::service)      entry="โš™|*" ;;
+    DIAGRAM_ICON::api)          entry="๐Ÿ”Œ|(I)" ;;
+    DIAGRAM_ICON::search)       entry="๐Ÿ”|(S)" ;;
+    DIAGRAM_ICON::timer)        entry="โฑ|(T)" ;;
+    DIAGRAM_ICON::build)        entry="๐Ÿ”จ|(B)" ;;
+    DIAGRAM_ICON::hook)         entry="๐Ÿช|(H)" ;;
+    DIAGRAM_ICON::log)          entry="๐Ÿ“„|(F)" ;;
   esac
   [[ -z "$entry" ]] && { printf '%s' "?"; return; }
   uni="${entry%|*}"

+ 214 - 0
skills/mac-ops/scripts/_lib/panel.sh

@@ -0,0 +1,214 @@
+# mac-ops :: _lib/panel.sh
+# Panel-rendering helper that wraps the shared skills/_lib/term.sh API.
+# Source AFTER common.sh. Provides:
+#
+#   panel_init           โ€” detect TTY, source term.sh
+#   panel_findings_open  โ€” start collecting findings (called once at top)
+#   panel_render <name> <indicator>
+#                        โ€” emit the state-grouped panel using collected
+#                          findings + log_pass/log_fail/log_warn counts
+#
+# Collection happens transparently: the existing log_pass / log_fail /
+# log_warn / log_info from common.sh ALSO push (state, section, label,
+# detail) tuples into MAC_PANEL_FINDINGS when panel mode is on.
+
+# Locate the project-level term.sh (4 levels up from script dir, then _lib)
+__MACOPS_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+__MACOPS_TERM_LIB="$(cd "$__MACOPS_SCRIPT_DIR/../../../_lib" 2>/dev/null && pwd)/term.sh"
+
+# Panel mode default: auto. Force on with FORCE_PANEL=1, off with NO_PANEL=1.
+PANEL_MODE="${PANEL_MODE:-auto}"
+MAC_PANEL_ENABLED=0
+MAC_PANEL_FINDINGS=()    # each entry: "state|section|label|detail"
+
+panel_init() {
+    # If JSON output requested, never enable panels โ€” JSON is the contract
+    if [[ "${JSON_MODE:-0}" -eq 1 ]]; then return 0; fi
+
+    # NO_PANEL forces off; FORCE_PANEL forces on; otherwise TTY-detect
+    if [[ "${NO_PANEL:-0}" -eq 1 ]]; then return 0; fi
+
+    # Source term.sh if available.
+    if [[ -f "$__MACOPS_TERM_LIB" ]]; then
+        # shellcheck source=/dev/null
+        . "$__MACOPS_TERM_LIB"
+        term_init
+    else
+        return 0
+    fi
+
+    # Use panel mode if stdout is a TTY or forced
+    if [[ "${FORCE_PANEL:-0}" -eq 1 ]] || [[ "$TERM_TTY" -eq 1 ]]; then
+        MAC_PANEL_ENABLED=1
+    fi
+}
+
+panel_enabled() { [[ "$MAC_PANEL_ENABLED" -eq 1 ]]; }
+
+# Override common.sh log_* functions to also collect findings.
+# (These are sourced AFTER common.sh and will override its versions.)
+__MAC_ORIGINAL_PASS=$(declare -f log_pass || true)
+__MAC_ORIGINAL_FAIL=$(declare -f log_fail || true)
+__MAC_ORIGINAL_WARN=$(declare -f log_warn || true)
+__MAC_ORIGINAL_INFO=$(declare -f log_info || true)
+
+# Wrap each log function so it both updates counters AND collects findings.
+# When panel mode is on, ALSO suppress the inline echo โ€” we'll render at end.
+
+log_pass() {
+    PASS_COUNT=$((PASS_COUNT + 1))
+    MAC_PANEL_FINDINGS+=("pass|${CURRENT_SECTION}|$1|${2:-}")
+    if [[ "$MAC_PANEL_ENABLED" -eq 1 ]]; then return 0; fi
+    if [[ "${JSON_MODE:-0}" -eq 1 ]]; then
+        printf '{"type":"check","section":"%s","label":"%s","status":"pass","detail":"%s"}\n' \
+            "$(_json_escape "$CURRENT_SECTION")" "$(_json_escape "$1")" "$(_json_escape "${2:-}")"
+    else
+        echo "[PASS] $1${2:+ :: $2}"
+    fi
+}
+
+log_fail() {
+    FAIL_COUNT=$((FAIL_COUNT + 1))
+    [[ -z "$FIRST_FAIL" ]] && FIRST_FAIL="[$CURRENT_SECTION] $1"
+    MAC_PANEL_FINDINGS+=("fail|${CURRENT_SECTION}|$1|${2:-}")
+    if [[ "$MAC_PANEL_ENABLED" -eq 1 ]]; then return 0; fi
+    if [[ "${JSON_MODE:-0}" -eq 1 ]]; then
+        printf '{"type":"check","section":"%s","label":"%s","status":"fail","detail":"%s"}\n' \
+            "$(_json_escape "$CURRENT_SECTION")" "$(_json_escape "$1")" "$(_json_escape "${2:-}")"
+    else
+        echo "[FAIL] $1${2:+ :: $2}"
+    fi
+}
+
+log_warn() {
+    WARN_COUNT=$((WARN_COUNT + 1))
+    MAC_PANEL_FINDINGS+=("warn|${CURRENT_SECTION}|$1|${2:-}")
+    if [[ "$MAC_PANEL_ENABLED" -eq 1 ]]; then return 0; fi
+    if [[ "${JSON_MODE:-0}" -eq 1 ]]; then
+        printf '{"type":"check","section":"%s","label":"%s","status":"warn","detail":"%s"}\n' \
+            "$(_json_escape "$CURRENT_SECTION")" "$(_json_escape "$1")" "$(_json_escape "${2:-}")"
+    else
+        echo "[WARN] $1${2:+ :: $2}"
+    fi
+}
+
+log_info() {
+    INFO_COUNT=$((INFO_COUNT + 1))
+    MAC_PANEL_FINDINGS+=("info|${CURRENT_SECTION}|$1|${2:-}")
+    if [[ "$MAC_PANEL_ENABLED" -eq 1 ]]; then return 0; fi
+    if [[ "${JSON_MODE:-0}" -eq 1 ]]; then
+        printf '{"type":"check","section":"%s","label":"%s","status":"info","detail":"%s"}\n' \
+            "$(_json_escape "$CURRENT_SECTION")" "$(_json_escape "$1")" "$(_json_escape "${2:-}")"
+    else
+        echo "[INFO] $1${2:+ :: $2}"
+    fi
+}
+
+# In panel mode, suppress `note` and `section` so the body stays clean โ€”
+# rendering happens at the end via panel_render.
+__MAC_ORIGINAL_NOTE=$(declare -f note || true)
+__MAC_ORIGINAL_SECTION=$(declare -f section || true)
+
+note() {
+    [[ "$MAC_PANEL_ENABLED" -eq 1 ]] && return 0
+    [[ "${JSON_MODE:-0}" -eq 1 ]] && return 0
+    [[ "${QUIET:-0}" -eq 1 ]] && return 0
+    echo "$@"
+}
+
+section() {
+    CURRENT_SECTION="$1"
+    if [[ "$MAC_PANEL_ENABLED" -eq 1 ]]; then return 0; fi
+    if [[ "${JSON_MODE:-0}" -eq 1 ]]; then
+        printf '{"type":"section","name":"%s"}\n' "$(_json_escape "$1")"
+    else
+        [[ "${QUIET:-0}" -eq 1 ]] || { echo; echo "=== $1 ==="; }
+    fi
+}
+
+# Render the collected findings as a state-grouped tree panel.
+# Args:
+#   $1 = script tag (e.g. "health-audit")
+#   $2 = right indicator (e.g. hostname)
+panel_render() {
+    if [[ "$MAC_PANEL_ENABLED" -ne 1 ]]; then
+        # Not panel mode โ€” just emit the standard summary block
+        emit_summary
+        return
+    fi
+
+    local tag="${1:-mac-ops}"
+    local indicator="${2:-}"
+
+    echo ""
+    term_panel_open mac-ops "mac-ops ยท $tag" "$indicator"
+    term_panel_vert
+    term_summary_line "$((PASS_COUNT+FAIL_COUNT+WARN_COUNT+INFO_COUNT)) checks ยท $FAIL_COUNT fail ยท $WARN_COUNT warn"
+    term_panel_vert
+
+    # Render in order: fail, warn, pass (count only), info (count only)
+    local order=(fail warn)
+    local labels=(failing warning)
+    local i
+    for i in 0 1; do
+        local st="${order[$i]}"
+        local label="${labels[$i]}"
+        local count=0
+        local entries=()
+        local entry
+        for entry in ${MAC_PANEL_FINDINGS[@]+"${MAC_PANEL_FINDINGS[@]}"}; do
+            [[ "${entry%%|*}" == "$st" ]] && { entries+=("$entry"); count=$((count+1)); }
+        done
+        [[ "$count" -eq 0 ]] && continue
+        term_section "$st" "$label" "$count"
+        local n=${#entries[@]}
+        local idx=0
+        for entry in "${entries[@]}"; do
+            local rest="${entry#*|}"
+            local section_name="${rest%%|*}"; rest="${rest#*|}"
+            local lbl="${rest%%|*}"; rest="${rest#*|}"
+            local detail="$rest"
+            local connector
+            if [[ $((idx + 1)) -eq $n ]]; then connector="$TERM_TREE_LAST"; else connector="$TERM_TREE_BRANCH"; fi
+            local sec_short="${section_name%% *}"
+            sec_short="${sec_short%.}"
+            local one_line
+            if [[ -n "$detail" ]]; then
+                one_line="$(printf '%-40s %s' "$lbl" "$detail")"
+            else
+                one_line="$lbl"
+            fi
+            # 100-char hard truncate
+            (( ${#one_line} > 100 )) && one_line="${one_line:0:97}..."
+            printf '%s   %s [%s] %s\n' \
+                "$(term_color dim "$TERM_TREE_VERT")" \
+                "$connector" \
+                "$(term_color dim "$sec_short")" \
+                "$one_line"
+        done
+        term_panel_vert
+        idx=$((idx+1))
+    done
+
+    # Pass + info counts on a single line
+    term_section "ok" "pass" "$PASS_COUNT"
+    if [[ "$INFO_COUNT" -gt 0 ]]; then
+        term_section "info" "info" "$INFO_COUNT"
+    fi
+    term_panel_vert
+
+    # Determine overall health indicator
+    local health_state="healthy"
+    [[ "$WARN_COUNT" -gt 0 ]] && health_state="warning"
+    [[ "$FAIL_COUNT" -gt 0 ]] && health_state="critical"
+    local right_health
+    right_health="$(term_health "$health_state" "$FAIL_COUNT fail ยท $WARN_COUNT warn")"
+    term_panel_close "" "$right_health"
+    echo ""
+
+    # Also emit a plain SUMMARY line for parseability
+    echo "  PASS: $PASS_COUNT    FAIL: $FAIL_COUNT    WARN: $WARN_COUNT    INFO: $INFO_COUNT"
+    if [[ -n "$FIRST_FAIL" ]]; then
+        echo "  First failure: $FIRST_FAIL"
+    fi
+}

+ 5 - 2
skills/mac-ops/scripts/health-audit.sh

@@ -45,6 +45,8 @@ set -- ${SAVED_ARGS[@]+"${SAVED_ARGS[@]}"}
 source "$(dirname "$0")/_lib/common.sh"
 parse_common_flags "$@"
 maybe_filter_self "$@"
+source "$(dirname "$0")/_lib/panel.sh"
+panel_init
 
 # ----------------------------------------------------------------------------
 section "1. HARDWARE HEALTH"
@@ -243,9 +245,10 @@ note "  Uptime:     $(uptime | awk -F'up ' '{split($2,a,","); print a[1]}')"
 note "  Hostname:   $(scutil --get LocalHostName 2>/dev/null || hostname)"
 
 # ----------------------------------------------------------------------------
-emit_summary
+hostname_short=$(scutil --get LocalHostName 2>/dev/null | head -c 30 || hostname -s | head -c 30)
+panel_render "health-audit" "$hostname_short"
 
-if [[ "$JSON_MODE" -eq 0 ]] && [[ -n "$FIRST_FAIL" ]]; then
+if [[ "$JSON_MODE" -eq 0 ]] && [[ "$MAC_PANEL_ENABLED" -eq 0 ]] && [[ -n "$FIRST_FAIL" ]]; then
     case "$FIRST_FAIL" in
         *"PANIC"*|*"panic"*)
             echo "  Next: scripts/panic-triage.sh  # decode the most recent panic + pre-panic timeline" ;;

+ 66 - 2
skills/mac-ops/scripts/quickrun.sh

@@ -40,6 +40,8 @@ done
 source "$(dirname "$0")/_lib/common.sh"
 parse_common_flags "$@"
 maybe_filter_self "$@"
+source "$(dirname "$0")/_lib/panel.sh"
+panel_init
 
 SCRIPTS_DIR="$(dirname "$0")"
 
@@ -60,9 +62,10 @@ run_subscript() {
     note "  $label"
     note "โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•"
 
-    # Capture sub-script output (suppress its own SUMMARY since we'll do our own)
+    # Capture sub-script output (suppress its own SUMMARY since we'll do our own).
+    # Force NO_PANEL so children emit parseable plain text โ€” we render our own panel.
     local out
-    out=$(bash "$SCRIPTS_DIR/$script" --quiet "$@" 2>&1 || true)
+    out=$(NO_PANEL=1 bash "$SCRIPTS_DIR/$script" --quiet "$@" 2>&1 || true)
 
     # Echo the sub-script's findings (everything before its SUMMARY)
     echo "$out" | sed -n '/^=== /,/^=== SUMMARY ===/p' | sed '/^=== SUMMARY ===/q' | sed '$d'
@@ -109,6 +112,67 @@ if [[ "$JSON_MODE" -eq 1 ]]; then
     printf '{"type":"quickrun_summary","pass":%d,"fail":%d,"warn":%d,"info":%d,"fail_count":%d,"warn_count":%d}\n' \
         "$TOTAL_PASS" "$TOTAL_FAIL" "$TOTAL_WARN" "$TOTAL_INFO" \
         "${#ALL_FAILS[@]}" "${#ALL_WARNS[@]}"
+elif panel_enabled; then
+    hostname_short=$(scutil --get LocalHostName 2>/dev/null | head -c 30 || hostname -s | head -c 30)
+    echo ""
+    term_panel_open mac-ops "mac-ops ยท quickrun" "$hostname_short"
+    term_panel_vert
+    term_summary_line "$((TOTAL_PASS+TOTAL_FAIL+TOTAL_WARN+TOTAL_INFO)) checks across 5 audits ยท $TOTAL_FAIL fail ยท $TOTAL_WARN warn"
+    term_panel_vert
+
+    if [[ "${#ALL_FAILS[@]}" -gt 0 ]]; then
+        term_section "FAILED" "failing" "${#ALL_FAILS[@]}"
+        n=${#ALL_FAILS[@]}
+        i=0
+        for f in "${ALL_FAILS[@]}"; do
+            i=$((i+1))
+            if [[ "$i" -eq "$n" ]]; then connector="$TERM_TREE_LAST"; else connector="$TERM_TREE_BRANCH"; fi
+            line="$f"
+            (( ${#line} > 100 )) && line="${line:0:97}..."
+            printf '%s   %s %s\n' \
+                "$(term_color dim "$TERM_TREE_VERT")" \
+                "$connector" \
+                "$line"
+            [[ "$i" -ge 10 ]] && { printf '%s   %s %s\n' "$(term_color dim "$TERM_TREE_VERT")" "$TERM_TREE_LAST" "$(term_color dim "... +$((n-10)) more")"; break; }
+        done
+        term_panel_vert
+    fi
+
+    if [[ "${#ALL_WARNS[@]}" -gt 0 ]]; then
+        term_section "WARN" "warning" "${#ALL_WARNS[@]}"
+        n=${#ALL_WARNS[@]}
+        i=0
+        for w in "${ALL_WARNS[@]}"; do
+            i=$((i+1))
+            if [[ "$i" -eq "$n" ]]; then connector="$TERM_TREE_LAST"; else connector="$TERM_TREE_BRANCH"; fi
+            line="$w"
+            (( ${#line} > 100 )) && line="${line:0:97}..."
+            printf '%s   %s %s\n' \
+                "$(term_color dim "$TERM_TREE_VERT")" \
+                "$connector" \
+                "$line"
+            [[ "$i" -ge 10 ]] && { printf '%s   %s %s\n' "$(term_color dim "$TERM_TREE_VERT")" "$TERM_TREE_LAST" "$(term_color dim "... +$((n-10)) more")"; break; }
+        done
+        term_panel_vert
+    fi
+
+    term_section "OK" "pass" "$TOTAL_PASS"
+    if [[ "$TOTAL_INFO" -gt 0 ]]; then
+        term_section "info" "info" "$TOTAL_INFO"
+    fi
+    term_panel_vert
+
+    health_state="healthy"
+    [[ "$TOTAL_WARN" -gt 0 ]] && health_state="warning"
+    [[ "$TOTAL_FAIL" -gt 0 ]] && health_state="critical"
+    right_health="$(term_health "$health_state" "$TOTAL_FAIL fail ยท $TOTAL_WARN warn")"
+    term_panel_close "" "$right_health"
+    echo ""
+
+    if [[ "${#ALL_FAILS[@]}" -eq 0 ]] && [[ "${#ALL_WARNS[@]}" -eq 0 ]]; then
+        echo "  โœ“ System looks clean. No FAILs or WARNs across 5 audits."
+    fi
+    echo "  Next: drill any FAIL into its source script (--verbose for detail)"
 else
     echo
     echo "  Aggregate: PASS $TOTAL_PASS    FAIL $TOTAL_FAIL    WARN $TOTAL_WARN    INFO $TOTAL_INFO"