Skill: honouralexwill/saturnday (version 1.8.1)

--- name: saturnday version: 1.8.0 description: Governed AI software execution — 48+ deterministic governance checks for Python with additional checks for TypeScript, DevOps, and project metadata. LLM-powered security triage to filter false positives. Build projects from a brief with per-commit governance, produce governed documents, inspect release artefacts, or auto-fix findings. Six backends — Claude Code, Codex, OpenClaude, Cursor, OpenAI API, Anthropic API. Requires pip install saturnday. homepage: https://www.saturnday.dev source: https://github.com/honouralexwill/saturnday license: MIT metadata: openclaw: requires: bins: - python3 - git optional_bins: - node optional_env: - ANTHROPIC_API_KEY - OPENAI_API_KEY - OPENCLAUDE_API_KEY - CURSOR_API_KEY install: - cmd: pip install saturnday - cmd: echo "Saturnday installed. Run 'saturnday version' to verify." notes: > Scan mode requires only python3 and git. Guard mode requires python3, git, and optionally node for TypeScript checks. Run mode requires python3, git, and at least one AI backend API key or CLI tool (claude, codex, openclaude, or cursor). All API keys are optional — set only the one for your chosen backend. Run and Guard modes modify the target repository (git commits, evidence directories). Run mode transmits repository contents to the chosen AI backend for code generation. --- # Saturnday Govern AI-built software from scan to build to repair — all from the terminal. Saturnday runs 48+ deterministic governance checks on Python projects (with additional checks for TypeScript, DevOps files, and project metadata), builds projects from a brief with ticketed execution and per-commit governance, produces governed documents, inspects release artefacts, and auto-fixes findings with evidence. **Requires:** `pip install saturnday` (Python 3.10+) Verify installation: ```bash saturnday version ``` If saturnday is not installed, run: ```bash pip install saturnday ``` --- ## Three Modes Saturnday has three core modes plus document and release modes. Choose based on the task: | Mode | When to use | Command | |------|-------------|---------| | **Scan** | Check a skill or repo for issues | `python scripts/scan.py <path>` | | **Guard** | Full governance on a git repo | `python scripts/guard.py <path>` | | **Run** | Build a project from a brief | `python scripts/run.py <path> --brief "..."` | | **Document** | Produce a governed document | `saturnday start --document --doc-spec spec.yaml` | | **Release** | Inspect artefacts before publishing | `saturnday release-preflight --repo <path>` | --- ## Mode 1: Scan **Use when:** checking an OpenClaw skill for security risks, hallucinated imports, fake tests, or quality issues before installing or publishing. ```bash python scripts/scan.py <skill-directory-path> ``` Or directly: ```bash saturnday scan --skill <skill-directory-path> --output /tmp/scan-results --format both ``` ### What it checks - 19 Python security checks (SQL injection, auth bypass, CSRF, XSS, hardcoded secrets) - 19 TypeScript security checks - Hallucinated import detection (packages that don't exist) - No-assert and fake test detection - Dead code (cross-file analysis) - Dependency declaration verification - Project hygiene (README, LICENSE) - Dockerfile checks (unpinned images, root user, secrets in build, missing dockerignore) - GitHub Actions (SHA pinning, broad permissions, pull_request_target, plaintext secrets) - GitLab CI (secrets in YAML, docker-in-docker patterns) - Jenkins (hardcoded credentials) - Terraform (hardcoded credentials, public CIDR) - Kubernetes (privileged containers, unpinned images, secrets in manifests) - Optional Trivy integration for deep IaC analysis ### Output ``` Disposition: PASS or FAIL Findings: list with check name, severity, file, line, description Evidence: timestamped directory with full results ``` --- ## Mode 2: Guard **Use when:** running full governance on any git repository — before merging, before deploying, or for audit. ```bash python scripts/guard.py <repo-path> ``` For staged changes only (pre-commit): ```bash python scripts/guard.py <repo-path> --staged ``` Or directly: ```bash saturnday governance --repo <path> --full ``` ### What it checks 48+ deterministic checks for Python projects: security (SQL injection, XSS, CSRF, auth bypass, hardcoded secrets, WebSocket security, OAuth, token handling, rate limiting, IDOR, user enumeration, cookie security), AI-specific (hallucinated imports, fake tests, dead code, placeholders), quality (syntax, dependencies, Python version compat, code quality, blast radius, project hygiene, typosquat detection). Additional checks activate for DevOps files (Dockerfile, GitHub Actions, GitLab CI, Jenkins, Terraform, Kubernetes, config security) and TypeScript/JavaScript code. ### Ratchet baselines Prevent regressions: ```bash saturnday baseline generate --repo <path> ``` ### Policy exemptions Create `.saturnday-policy.yaml`: ```yaml expected_findings: - declared_not_installed - package_not_importable ``` --- ## Mode 3: Run **Use when:** building a project from a description. This is the full governed execution pipeline. ```bash python scripts/run.py <project-directory> --brief "build a REST API with auth and tests" --backend anthropic ``` Or interactively: ```bash cd <project-directory> saturnday start ``` ### Backends | Backend | Value | Auth | |---------|-------|------| | Claude Code CLI | `claude-cli` | Claude Pro subscription | | Codex CLI | `codex-cli` | OpenAI subscription | | OpenClaude | `openclaude` | OpenClaude CLI | | Cursor CLI | `cursor-cli` | Cursor | | OpenAI API | `openai` | OPENAI_API_KEY | | Anthropic API | `anthropic` | ANTHROPIC_API_KEY | ### What happens during a run 1. **Security triage** — LLM-powered data flow analysis filters false positives from security findings so you only review what matters 2. **Brief enrichment** — vague briefs are auto-enriched with technical detail before planning 2. **Planning** — 3-stage planner generates tickets with acceptance criteria and scope constraints 3. **Execution** — each ticket executed sequentially, governance checks after every commit 4. **Auto-install** — dependencies are automatically installed between tickets when pyproject.toml or package.json changes 5. **Retry** — if governance fails, revert, feed findings back, retry (up to 3 attempts) 6. **Ungoverned commit** — if a ticket still fails after retries and auto-repair, the code is committed with a `[GOVERNANCE: review required]` tag so the project stays complete 7. **Auto-repair** — after all tickets, ungoverned and failed tickets go through the repair pipeline again 8. **Definition of done** — evaluates whether plan goals are met 9. **Review report** — for ungoverned tickets: `review-required.md` with findings, remediation tips, and copy-paste fix prompts for each ticket 10. **Progress log** — live `saturnday-progress.log` with ticket outcomes, governance results, and counterfactual messages showing what ungoverned AI coders would have done wrong 11. **Evidence** — timestamped directory with per-ticket results, governance evidence, analytics ### Resume interrupted runs Saturnday auto-detects prior runs. If you restart `saturnday run` on a project that already has a ledger, tickets that already passed are skipped automatically. You can also resume explicitly: ```bash saturnday resume --repo <path> --backend anthropic ``` --- ## Document Mode **Use when:** producing governed documents (specs, reports, audits) with section-level checks and cross-section consistency validation. ```bash saturnday start --document --doc-spec doc-spec.yaml --backend claude-cli ``` The pipeline: parse spec, plan sections, generate each section, run section-level checks (structure, placeholders, citations, evidence coverage), retry failures, run cross-section consistency checks, and produce a signed-off evidence pack. --- ## Release Preflight **Use when:** inspecting built artefacts (wheels, sdists, npm tarballs, OCI images) before publishing. ```bash saturnday release-preflight --repo <path> --type python saturnday release-preflight --wheel dist/mypackage-1.0-py3-none-any.whl ``` Runs 5 release-security checks: source map blocking, secrets in artefact, internal file blocking, allowlist manifest validation, and release diff comparison. Separate from code governance. --- ## When to Use Each Mode - **"Scan this skill before I install it"** → Scan - **"Check this repo for security issues"** → Guard - **"Build me a project from this description"** → Run - **"Audit this codebase"** → Guard - **"Produce a governed document"** → Document mode - **"Check this wheel before publishing"** → Release preflight - **"Fix the findings"** → use `saturnday repair --repo <path>` directly ## Prerequisites - `pip install saturnday` (Python 3.10+) - Node.js 18+ (for TypeScript checks) - Git (for Guard and Run modes) - At least one AI coder backend for Run mode

path: scripts/guard.py
content: #!/usr/bin/env python3
"""Saturnday governance runner for OpenClaw.

Runs full or staged governance checks on a git repository using saturnday.
Requires: pip install saturnday

Usage:
    python scripts/guard.py <repo-path> [--staged] [--policy <path>]
"""

import json
import subprocess
import sys
from pathlib import Path


def check_saturnday_installed() -> bool:
    """Verify saturnday is installed and accessible."""
    try:
        result = subprocess.run(
            ["saturnday", "version"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        return result.returncode == 0
    except FileNotFoundError:
        return False


def run_governance(
    repo_path: str,
    *,
    staged: bool = False,
    policy_path: str | None = None,
) -> dict:
    """Run saturnday governance on a repository.

    Args:
        repo_path: Path to the git repository.
        staged: If True, check only staged changes. If False, full repo scan.
        policy_path: Optional path to .saturnday-policy.yaml.

    Returns:
        Dict with disposition, checks run, findings, and evidence path.

    Raises:
        RuntimeError: If saturnday is not installed or repo is invalid.
    """
    repo = Path(repo_path).resolve()
    if not repo.is_dir():
        raise RuntimeError(f"Not a directory: {repo}")
    if not (repo / ".git").exists():
        raise RuntimeError(f"Not a git repository: {repo}")

    if not check_saturnday_installed():
        raise RuntimeError(
            "saturnday is not installed. Install with: pip install saturnday"
        )

    cmd = ["saturnday", "governance", "--repo", str(repo)]

    if staged:
        cmd.append("--staged")
    else:
        cmd.append("--full")

    if policy_path:
        cmd.extend(["--policy", policy_path])

    result = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        timeout=600,
    )

    output_text = result.stdout + result.stderr
    disposition = "PASS" if result.returncode == 0 else "FAIL"

    # Parse findings count from output
    findings_count = 0
    evidence_path = ""
    for line in output_text.splitlines():
        line_stripped = line.strip()
        if line_stripped.startswith("Total findings:"):
            try:
                findings_count = int(line_stripped.split(":")[1].strip())
            except (ValueError, IndexError):
                pass
        if line_stripped.startswith("Evidence:"):
            evidence_path = line_stripped.split(":", 1)[1].strip()

    return {
        "disposition": disposition,
        "exit_code": result.returncode,
        "findings_count": findings_count,
        "evidence_path": evidence_path,
        "output": output_text.strip(),
    }


def main() -> int:
    """CLI entry point."""
    if len(sys.argv) < 2:
        print("Usage: python scripts/guard.py <repo-path> [--staged] [--policy <path>]")
        return 2

    repo_path = sys.argv[1]
    staged = False
    policy_path = None

    args = sys.argv[2:]
    i = 0
    while i < len(args):
        if args[i] == "--staged":
            staged = True
            i += 1
        elif args[i] == "--policy" and i + 1 < len(args):
            policy_path = args[i + 1]
            i += 2
        else:
            i += 1

    try:
        result = run_governance(repo_path, staged=staged, policy_path=policy_path)
    except RuntimeError as exc:
        print(f"Error: {exc}", file=sys.stderr)
        return 2

    print(json.dumps(result, indent=2))
    return 0 if result["disposition"] == "PASS" else 1


if __name__ == "__main__":
    sys.exit(main())


path: scripts/run.py
content: #!/usr/bin/env python3
"""Saturnday governed execution for OpenClaw.

Runs the full saturnday pipeline: plan → execute → govern → evidence.
Requires: pip install saturnday

Usage:
    python scripts/run.py <project-directory> --brief "description" [--backend claude-cli]
"""

import json
import os
import subprocess
import sys
from pathlib import Path


VALID_BACKENDS = {"claude-cli", "codex-cli", "cursor-cli", "openai", "anthropic"}


def check_saturnday_installed() -> bool:
    """Verify saturnday is installed and accessible."""
    try:
        result = subprocess.run(
            ["saturnday", "version"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        return result.returncode == 0
    except FileNotFoundError:
        return False


def ensure_git_repo(project_path: Path) -> None:
    """Initialize git if the project directory is not already a repo."""
    if not (project_path / ".git").exists():
        subprocess.run(
            ["git", "init"],
            cwd=str(project_path),
            capture_output=True,
            check=True,
        )
        subprocess.run(
            ["git", "commit", "--allow-empty", "-m", "init"],
            cwd=str(project_path),
            capture_output=True,
            check=True,
        )


def detect_backend() -> str | None:
    """Detect which AI coder backends are available."""
    checks = {
        "claude-cli": "claude",
        "codex-cli": "codex",
        "cursor-cli": "agent",
    }
    for backend, binary in checks.items():
        try:
            result = subprocess.run(
                ["which", binary],
                capture_output=True,
                text=True,
            )
            if result.returncode == 0:
                return backend
        except FileNotFoundError:
            continue

    # Check API backends
    if os.environ.get("OPENAI_API_KEY"):
        return "openai"
    if os.environ.get("ANTHROPIC_API_KEY"):
        return "anthropic"

    return None


def run_plan(project_path: str, brief: str, backend: str) -> dict:
    """Generate a plan from a brief.

    Args:
        project_path: Path to the project directory.
        brief: Natural-language project description.
        backend: AI coder backend to use.

    Returns:
        Dict with plan path and plan summary.
    """
    project = Path(project_path).resolve()
    project.mkdir(parents=True, exist_ok=True)
    ensure_git_repo(project)

    cmd = [
        "saturnday", "plan",
        "--brief", brief,
        "--repo", str(project),
        "--backend", backend,
    ]

    result = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        timeout=600,
        cwd=str(project),
    )

    output_text = result.stdout + result.stderr

    plan_path = project / "plan.json"
    plan_data = {}
    if plan_path.exists():
        try:
            plan_data = json.loads(plan_path.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError):
            pass

    return {
        "success": result.returncode == 0,
        "plan_path": str(plan_path) if plan_path.exists() else None,
        "tickets": len(plan_data.get("tickets", [])),
        "phases": len(plan_data.get("phases", [])),
        "output": output_text.strip(),
    }


def run_execute(project_path: str, backend: str) -> dict:
    """Execute a plan under governance.

    Args:
        project_path: Path to the project directory containing plan.json.
        backend: AI coder backend to use.

    Returns:
        Dict with pass/fail/skip counts, DoD status, and evidence path.
    """
    project = Path(project_path).resolve()
    plan_path = project / "plan.json"

    if not plan_path.exists():
        raise RuntimeError(f"No plan.json found in {project}. Run planning first.")

    cmd = [
        "saturnday", "run",
        "--plan", str(plan_path),
        "--repo", str(project),
        "--backend", backend,
    ]

    result = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        timeout=28800,  # 8 hours — complex projects need time
        cwd=str(project),
    )

    output_text = result.stdout + result.stderr

    # Parse results from output
    passed = 0
    failed = 0
    skipped = 0
    dod_met = False

    for line in output_text.splitlines():
        stripped = line.strip()
        if "Passed:" in stripped and "Failed:" in stripped:
            parts = stripped.split()
            for i, part in enumerate(parts):
                if part == "Passed:" and i + 1 < len(parts):
                    try:
                        passed = int(parts[i + 1])
                    except ValueError:
                        pass
                if part == "Failed:" and i + 1 < len(parts):
                    try:
                        failed = int(parts[i + 1])
                    except ValueError:
                        pass
                if part == "Skipped:" and i + 1 < len(parts):
                    try:
                        skipped = int(parts[i + 1])
                    except ValueError:
                        pass
        if "DoD:" in stripped and "MET" in stripped and "NOT" not in stripped:
            dod_met = True

    return {
        "success": result.returncode == 0,
        "passed": passed,
        "failed": failed,
        "skipped": skipped,
        "dod_met": dod_met,
        "exit_code": result.returncode,
        "output": output_text.strip(),
    }


def run_full(project_path: str, brief: str, backend: str) -> dict:
    """Run the full pipeline: plan then execute.

    Args:
        project_path: Path to the project directory.
        brief: Natural-language project description.
        backend: AI coder backend to use.

    Returns:
        Dict with plan and execution results.
    """
    print(f"=== Saturnday Run ===")
    print(f"Project: {project_path}")
    print(f"Backend: {backend}")
    print(f"Brief: {brief[:100]}{'...' if len(brief) > 100 else ''}")
    print()

    # Stage 1: Plan
    print("--- stage: planning ---")
    plan_result = run_plan(project_path, brief, backend)
    if not plan_result["success"]:
        print(f"Planning failed:\n{plan_result['output']}")
        return {"stage": "plan", "error": "Planning failed", **plan_result}

    print(f"Plan generated: {plan_result['tickets']} tickets, {plan_result['phases']} phases")
    print()

    # Stage 2: Execute
    print("--- stage: execute ---")
    exec_result = run_execute(project_path, backend)
    print(f"Passed: {exec_result['passed']}  Failed: {exec_result['failed']}  Skipped: {exec_result['skipped']}")
    print(f"DoD: {'MET' if exec_result['dod_met'] else 'NOT MET'}")

    return {
        "stage": "complete",
        "plan": plan_result,
        "execution": exec_result,
        "dod_met": exec_result["dod_met"],
    }


def main() -> int:
    """CLI entry point."""
    if len(sys.argv) < 2:
        print("Usage: python scripts/run.py <project-dir> --brief \"description\" [--backend claude-cli]")
        return 2

    project_path = sys.argv[1]
    brief = None
    backend = None

    args = sys.argv[2:]
    i = 0
    while i < len(args):
        if args[i] == "--brief" and i + 1 < len(args):
            brief = args[i + 1]
            i += 2
        elif args[i] == "--backend" and i + 1 < len(args):
            backend = args[i + 1]
            i += 2
        else:
            i += 1

    if not brief:
        print("Error: --brief is required", file=sys.stderr)
        return 2

    if not check_saturnday_installed():
        print("Error: saturnday is not installed. Install with: pip install saturnday", file=sys.stderr)
        return 2

    if backend and backend not in VALID_BACKENDS:
        print(f"Error: invalid backend '{backend}'. Valid: {', '.join(sorted(VALID_BACKENDS))}", file=sys.stderr)
        return 2

    if not backend:
        backend = detect_backend()
        if not backend:
            print("Error: no AI coder backend detected. Install claude, codex, or agent CLI.", file=sys.stderr)
            return 2
        print(f"Detected backend: {backend}")

    result = run_full(project_path, brief, backend)
    print()
    print(json.dumps(result, indent=2, default=str))

    if result.get("dod_met"):
        return 0
    return 1


if __name__ == "__main__":
    sys.exit(main())


path: scripts/scan.py
content: #!/usr/bin/env python3
"""Saturnday skill scanner for OpenClaw.

Scans a skill directory using saturnday scan and returns structured results.
Requires: pip install saturnday

Usage:
    python scripts/scan.py <skill-directory-path> [--format json|text] [--output <dir>]
"""

import json
import subprocess
import sys
import tempfile
from pathlib import Path


def check_saturnday_installed() -> bool:
    """Verify saturnday is installed and accessible."""
    try:
        result = subprocess.run(
            ["saturnday", "version"],
            capture_output=True,
            text=True,
            timeout=10,
        )
        return result.returncode == 0
    except FileNotFoundError:
        return False


def run_scan(skill_path: str, output_dir: str | None = None, fmt: str = "json") -> dict:
    """Run saturnday scan on a skill directory.

    Args:
        skill_path: Path to the skill directory to scan.
        output_dir: Optional output directory for evidence. Uses temp dir if None.
        fmt: Output format — 'json', 'markdown', or 'both'.

    Returns:
        Dict with disposition, findings count, and output path.

    Raises:
        RuntimeError: If saturnday is not installed or scan fails to execute.
    """
    skill_path_resolved = Path(skill_path).resolve()
    if not skill_path_resolved.is_dir():
        raise RuntimeError(f"Not a directory: {skill_path_resolved}")

    if not check_saturnday_installed():
        raise RuntimeError(
            "saturnday is not installed. Install with: pip install saturnday"
        )

    if output_dir is None:
        output_dir = tempfile.mkdtemp(prefix="saturnday-scan-")

    # saturnday scan accepts: json, markdown, both
    scan_fmt = fmt if fmt in ("json", "markdown", "both") else "json"
    cmd = [
        "saturnday", "scan",
        "--skill", str(skill_path_resolved),
        "--output", output_dir,
        "--format", scan_fmt,
    ]

    result = subprocess.run(
        cmd,
        capture_output=True,
        text=True,
        timeout=300,
    )

    # Parse output for disposition
    output_text = result.stdout + result.stderr
    disposition = "PASS" if result.returncode == 0 else "FAIL"

    # Try to read the report.json if it exists
    report_path = Path(output_dir) / "report.json"
    report_data = {}
    if report_path.exists():
        try:
            report_data = json.loads(report_path.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError):
            pass

    # Extract findings count from nested summary or top level
    findings_count = report_data.get("total_findings", 0)
    if findings_count == 0:
        summary = report_data.get("summary", {})
        findings_count = summary.get("total_findings", 0)

    return {
        "disposition": disposition,
        "exit_code": result.returncode,
        "findings_count": findings_count,
        "output_dir": output_dir,
        "output": output_text.strip(),
        "report": report_data,
    }


def main() -> int:
    """CLI entry point."""
    if len(sys.argv) < 2:
        print("Usage: python scripts/scan.py <skill-directory-path> [--format json|text] [--output <dir>]")
        return 2

    skill_path = sys.argv[1]
    output_dir = None

    # Simple arg parsing
    args = sys.argv[2:]
    i = 0
    while i < len(args):
        if args[i] == "--output" and i + 1 < len(args):
            output_dir = args[i + 1]
            i += 2
        else:
            i += 1

    try:
        result = run_scan(skill_path, output_dir=output_dir)
    except RuntimeError as exc:
        print(f"Error: {exc}", file=sys.stderr)
        return 2

    print(json.dumps(result, indent=2))

    return 0 if result["disposition"] == "PASS" else 1


if __name__ == "__main__":
    sys.exit(main())


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