Skill: martok9803/martok9803-ci-whisperer (version 0.2.0)

--- name: ci-whisperer description: "Analyze GitHub Actions failures and propose fixes. Use when a user shares a failing GitHub Actions run URL/id, says 'CI is failing', asks 'why did this workflow fail', wants logs summarized, wants the minimal fix, or wants an automated PR to address the failure. Uses the GitHub CLI (`gh`) and GitHub API to fetch run metadata/logs safely and produce a concise root-cause + next steps report." --- # CI Whisperer Fetch GitHub Actions run details, pinpoint the failure, and propose a minimal fix. This skill is meant to feel like a senior engineer doing a fast “CI autopsy”. ## Modes ### Read-only mode (default) - Collect evidence, explain root cause, propose fixes. - **No pushes, no PRs, no branch creation.** ### PR fix mode (opt-in) PR fix mode is allowed only when **both** are true: 1) The user explicitly asks to open a PR. 2) A local toggle is enabled (the “on/off button”): - env var: `CI_WHISPERER_WRITE=1` If the toggle is not enabled, refuse politely and explain how to enable it. ## Workflow ### 1) Identify target run Accept any of: - workflow run URL - run id - PR number (then locate latest run) Always determine: - `owner/repo` - run id If the user didn’t specify the repo, ask for it (or infer from context). ### 2) Gather evidence (tool-backed) Prefer deterministic tooling. Use `/usr/bin/gh` when the system has multiple `gh` binaries. Suggested commands: - `gh run view <run-id> --repo owner/repo --json status,conclusion,createdAt,updatedAt,event,headBranch,headSha,url,name` - `gh run view <run-id> --repo owner/repo --log-failed` - `gh run view <run-id> --repo owner/repo --log` (only if needed; can be noisy) If `gh` is not authenticated, stop and ask the user to run: - `/usr/bin/gh auth login` ### 3) Produce a "CI Autopsy" report Return: - failing job(s) and step(s) - the exact error excerpt (short; redact secrets) - likely root cause(s) ranked - minimal fix options - confidence level ### 4) (Optional) Open a PR (only with explicit approval + write toggle) If the user asks to fix it and `CI_WHISPERER_WRITE=1`: - create a branch - apply minimal changes - run local lint/tests if available - open PR with a clear description and link to the failing run If the user asks but write mode is OFF: - provide the patch/diff instructions, but do not push. ## Safety - Never print tokens. - Don’t open PRs or push changes unless explicitly requested. - If logs contain secrets, redact before quoting. ## Bundled scripts Use scripts for repeatable fetching and parsing: - `scripts/ci_autopsy.py` (fetch run metadata + failed logs)

path: references/config.md
content: # CI Whisperer configuration

## GitHub auth

CI Whisperer relies on GitHub CLI (`gh`). Authenticate on the host:

```bash
/usr/bin/gh auth login
```

## PR fix mode toggle (on/off)

By default CI Whisperer is read-only.

To allow PR creation (still requires explicit user approval each time):

```bash
export CI_WHISPERER_WRITE=1
```

Recommended: set this only for sessions where you actually want auto-fix PRs.

## Security notes

- Never paste GitHub tokens into chat.
- Redact secrets from logs before quoting.


path: scripts/ci_autopsy.py
content: #!/usr/bin/env python3
"""CI Autopsy helper for GitHub Actions.

Uses the GitHub CLI (`gh`). Designed to be called by an OpenClaw agent.

Examples:
  python3 ci_autopsy.py view --repo owner/repo --run-id 123
  python3 ci_autopsy.py failed-logs --repo owner/repo --run-id 123

Notes:
- Uses /usr/bin/gh if present to avoid shadowed Node "gh" shims.
- Does not print auth tokens.
"""

from __future__ import annotations

import argparse
import json
import os
import shutil
import subprocess
import sys


def gh_bin() -> str:
    if os.path.exists("/usr/bin/gh"):
        return "/usr/bin/gh"
    p = shutil.which("gh")
    if not p:
        raise SystemExit("gh not found. Install GitHub CLI and authenticate: gh auth login")
    return p


def run(cmd: list[str]) -> str:
    p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
    if p.returncode != 0:
        raise SystemExit(p.stdout.strip())
    return p.stdout


def cmd_view(args: argparse.Namespace) -> None:
    fields = "status,conclusion,createdAt,updatedAt,event,headBranch,headSha,url,name,workflowDatabaseId"
    out = run([
        gh_bin(), "run", "view", str(args.run_id),
        "--repo", args.repo,
        "--json", fields,
    ])
    print(out)


def cmd_failed_logs(args: argparse.Namespace) -> None:
    out = run([
        gh_bin(), "run", "view", str(args.run_id),
        "--repo", args.repo,
        "--log-failed",
    ])
    print(out)


def cmd_list(args: argparse.Namespace) -> None:
    out = run([
        gh_bin(), "run", "list",
        "--repo", args.repo,
        "--limit", str(args.limit),
        "--json", "databaseId,status,conclusion,createdAt,updatedAt,event,headBranch,headSha,workflowName,displayTitle,url",
    ])
    # Pretty-print a compact view
    data = json.loads(out)
    for r in data:
        print(f"{r['databaseId']} {r['conclusion'] or r['status']} {r['workflowName']} :: {r['displayTitle']} ({r['url']})")


def main() -> None:
    ap = argparse.ArgumentParser()
    sub = ap.add_subparsers(dest="cmd", required=True)

    p_list = sub.add_parser("list")
    p_list.add_argument("--repo", required=True, help="owner/repo")
    p_list.add_argument("--limit", type=int, default=10)
    p_list.set_defaults(func=cmd_list)

    p_view = sub.add_parser("view")
    p_view.add_argument("--repo", required=True, help="owner/repo")
    p_view.add_argument("--run-id", type=int, required=True)
    p_view.set_defaults(func=cmd_view)

    p_logs = sub.add_parser("failed-logs")
    p_logs.add_argument("--repo", required=True, help="owner/repo")
    p_logs.add_argument("--run-id", type=int, required=True)
    p_logs.set_defaults(func=cmd_failed_logs)

    args = ap.parse_args()
    args.func(args)


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (MEDIUM)