Skill: manjaroblack/openclaw-contributor (version 0.1.1)

--- name: openclaw-contributor description: Contribute to the OpenClaw core repository using the repo's own CONTRIBUTING.md rules. Use when working in `openclaw/openclaw` or a fork to triage issues, plan a focused fix, choose the right validation commands, prepare AI-assisted PRs, route changes to the right subsystem maintainers, or avoid breaking OpenClaw contribution norms. --- # OpenClaw contributor Contribute to OpenClaw the way the repo expects. Start by reading the repo-root `CONTRIBUTING.md` in the target checkout. Treat it as the source of truth over generic PR habits. ## Workflow 1. Confirm scope. - Small bugfixes and focused docs fixes can go straight to a PR. - New features, large refactors, or architecture changes should start with a GitHub Discussion or Discord conversation first. 2. Inspect the changed area before editing. - Read nearby implementation and tests. - Look for existing branch/work in `upstream/*` before duplicating effort. 3. Generate a validation plan. - Run `scripts/recommend_checks.py --repo <openclaw-repo>`. - Use its output to choose validation commands and maintainer routing hints. 4. Keep the patch tight. - One logical change per PR. - Add or update regression tests with the fix when possible. - Avoid mixing runtime fixes, refactors, docs, and feature work in one branch. 5. Validate before opening the PR. - Default expectation from OpenClaw is: - `pnpm build` - `pnpm check` - `pnpm test` - For docs-only or subsystem-specific work, use the slimmer commands recommended by `scripts/recommend_checks.py`. 6. Prepare the PR for maintainers. - Explain what changed and why. - Mark AI-assisted work in the PR title or description. - State testing level clearly. - Include screenshots for UI or visual changes. - Optionally generate a draft with `scripts/generate_pr_body.py`. ## Non-negotiables - Follow `CONTRIBUTING.md`, not generic habits. - Keep PRs focused. - Prefer source-level fixes over patching built artifacts. - Add tests for regressions when practical. - Be transparent about AI assistance. - For UI changes, preserve Control UI legacy decorator style unless the build tooling is intentionally being changed too. ## Use bundled resources - `references/contributing-checklist.md` - Read when you need the distilled OpenClaw-specific PR checklist, maintainer hints, or validation command matrix. - `references/pr-template.md` - Read when you need a maintainer-friendly OpenClaw PR structure with AI-assistance disclosure and validation sections. - `scripts/recommend_checks.py` - Run in an OpenClaw checkout to derive recommended validation commands and maintainer hints from the actual diff. - Example: - `python3 skills/openclaw-contributor/scripts/recommend_checks.py --repo /path/to/openclaw` - `python3 skills/openclaw-contributor/scripts/recommend_checks.py --repo /path/to/openclaw --base upstream/main --json` - `scripts/generate_pr_body.py` - Generate a PR-body draft using the diff-aware recommendations. - Example: - `python3 skills/openclaw-contributor/scripts/generate_pr_body.py --repo /path/to/openclaw --title "fix(web-search): honor OpenRouter-backed Perplexity runtime path" --summary "Honor OpenRouter-backed Perplexity config in runtime web_search path" --why "Current runtime ignores configured baseUrl and sends OpenRouter keys to Perplexity direct"` ## Related skills If they are available locally, use them alongside this skill: - `github` for GH CLI operations - `Pull Request` before opening the PR - `code-review` before final submission or while addressing review comments ## Output standard When asked to contribute upstream, finish with: - branch name - files changed - validation run (or what remains and why) - PR/discussion recommendation - any maintainer/subsystem routing hints

path: CHANGELOG.md
content: # Changelog

## 0.1.1

- Add README with install instructions, usage guidance, and public links
- Add changelog to the repo/skill folder
- Prepare GitHub release metadata and ClawHub follow-up publish

## 0.1.0

- Initial public release of the OpenClaw Contributor skill
- Includes workflow guidance, reference docs, and helper scripts for OpenClaw upstream PRs


path: README.md
content: # OpenClaw Contributor

[![ClawHub](https://img.shields.io/badge/ClawHub-openclaw--contributor-7A2CF5)](https://clawhub.ai/manjaroblack/openclaw-contributor)
[![GitHub release](https://img.shields.io/github/v/release/manjaroblack/openclaw-contributor-skill?display_name=tag)](https://github.com/manjaroblack/openclaw-contributor-skill/releases)
[![GitHub](https://img.shields.io/badge/GitHub-manjaroblack%2Fopenclaw--contributor--skill-181717?logo=github&logoColor=white)](https://github.com/manjaroblack/openclaw-contributor-skill)

Contribute to the OpenClaw core repository the way the repo actually expects.

This skill helps with upstream OpenClaw work in `openclaw/openclaw` or a fork: scope checks, validation planning, PR prep, AI-assistance disclosure, and maintainer-friendly handoff.

## Install

```bash
clawhub install openclaw-contributor
```

ClawHub page:

- https://clawhub.ai/manjaroblack/openclaw-contributor

GitHub repo:

- https://github.com/manjaroblack/openclaw-contributor-skill

## What it does

- Tells the agent to start with the target repo's `CONTRIBUTING.md`
- Keeps changes focused instead of mixing unrelated work
- Recommends validation commands before opening a PR
- Encourages regression tests where practical
- Generates maintainer-friendly PR bodies and routing hints
- Calls out OpenClaw-specific UI and contribution norms

## Included resources

- `SKILL.md` — main workflow and rules
- `references/contributing-checklist.md` — distilled checklist and validation matrix
- `references/pr-template.md` — PR structure with AI-assistance disclosure
- `references/example-perplexity-check-plan.txt` — example validation planning artifact
- `scripts/recommend_checks.py` — diff-aware validation recommendations
- `scripts/generate_pr_body.py` — draft a PR body from repo context

## Typical usage

Use this when you want an agent to:

- triage an OpenClaw issue
- plan a tight upstream fix
- choose the right validation commands
- prepare an AI-assisted PR without annoying maintainers
- route work to the right subsystem owners

## Example prompts

- "Help me contribute a fix to openclaw/openclaw."
- "Plan the validation steps for this OpenClaw PR."
- "Draft a maintainer-friendly PR body for this OpenClaw change."
- "Figure out which checks I should run before opening this upstream PR."

## Versioning

Published on ClawHub as `openclaw-contributor`.

Use the GitHub releases page for repo-level notes and the ClawHub changelog for published skill version history.


path: references/contributing-checklist.md
content: # OpenClaw contribution checklist

Use this when working in the `openclaw/openclaw` repo.

## Contribution policy

- Small bugfixes: open a focused PR.
- New features or architecture changes: start a GitHub Discussion or ask in Discord first.
- Keep one logical change per PR. Do not mix unrelated fixes.

## Required before opening a PR

- Test locally in a real OpenClaw instance when possible.
- Run the standard checks unless the change is docs-only:
  - `pnpm build`
  - `pnpm check`
  - `pnpm test`
- Ensure CI is expected to pass.
- Describe both **what** changed and **why**.
- Include screenshots for UI/visual changes: one before/problem and one after/fix.

## AI-assisted PR rules

- Mark the PR as AI-assisted in the title or description.
- State testing level clearly: untested / lightly tested / fully tested.
- Include prompts or session logs when helpful.
- Confirm you understand the code you are submitting.

## Control UI note

The Control UI uses Lit with legacy decorators. Keep reactive fields in legacy style:

```ts
@state() foo = "bar";
@property({ type: Number }) count = 0;
```

Do not switch to standard decorators unless the UI build tooling is updated too.

## Subsystem maintainer hints

Use these as routing hints, not hard rules:

- Peter Steinberger — overall maintainer / repo direction
- Shadow — Discord, ClawHub, moderation
- Vignesh — Memory (QMD), formal modeling, TUI, IRC
- Jos / Ayaan — Telegram
- Tyler Yust — agents, subagents, cron, BlueBubbles, macOS app
- Mariano Belinky / Vincent Koc / Josh Avant — security/auth/core hardening
- Val Alexander — UI/UX, docs, agent dev experience
- Gustavo Madeira Santana — agents, CLI, web UI
- Jonathan Taylor — ACP subsystem, gateway features/bugs, Gog/Mog/Sog CLI's

## Useful repo commands

- Core validation: `pnpm build && pnpm check && pnpm test`
- Fast unit-ish run: `pnpm test:fast`
- Gateway-heavy changes: `pnpm test:gateway`
- UI changes: `pnpm test:ui`
- Extensions: `pnpm test:extensions`
- Docs-only: `pnpm format:docs:check && pnpm lint:docs && pnpm docs:check-links`
- iOS: `pnpm ios:gen && pnpm ios:build`
- Android: `pnpm android:lint && pnpm android:test`

## Good-first workflow

1. Confirm whether the work is a bugfix or a feature.
2. Read `CONTRIBUTING.md` and inspect similar files/tests first.
3. Keep the branch focused.
4. Add or update regression tests with the fix.
5. Run appropriate validation for the touched subsystem.
6. Prepare a clear PR description with testing notes and AI disclosure.


path: references/example-perplexity-check-plan.txt
content: Repo: /root/.openclaw/workspace/upstream/openclaw
Diff: main...upstream/fix/perplexity-web-search-provider

Changed files:
- CHANGELOG.md
- src/config/config.web-search-provider.test.ts
- src/config/schema.ts
- src/config/zod-schema.agent-runtime.ts

Recommended commands:
- pnpm build
- pnpm check
- pnpm test
- pnpm format:docs:check
- pnpm lint:docs

Maintainers to consider:
- Peter Steinberger (@steipete) / appropriate subsystem maintainer based on touched area

Warnings / PR notes:
- Mark AI-assisted work in the PR title or description and state the testing level.
- Keep the PR focused: one bugfix/feature per PR; use Discussions first for new features or architecture changes.


path: references/pr-template.md
content: # OpenClaw PR template guide

Use this shape for OpenClaw PRs unless the repo or maintainers ask for something different.

## Title

Prefer a focused, subsystem-aware title.

Examples:

- `fix(web-search): honor OpenRouter-backed Perplexity runtime path`
- `[AI-assisted] fix(daemon): prefer stable wrapper path for user service launch`

## Body

```md
## Summary
- One sentence on the user-visible or maintainer-visible change.
- One sentence on the technical fix.

## Why
- What was broken, misleading, or brittle.
- Why this approach was chosen instead of alternatives.

## Validation
- `pnpm build`
- `pnpm check`
- `pnpm test`
- any subsystem-specific commands

## Screenshots
- before/after screenshots for UI or visual changes

## AI assistance
- AI-assisted: yes/no
- Testing level: untested / lightly tested / fully tested
- I reviewed the code and understand what it does.
```

## Notes

- Keep PRs focused.
- Call out risk areas plainly.
- Mention if this builds on an existing branch/PR.
- For security/auth changes, explain impact and rollback clearly.


path: scripts/generate_pr_body.py
content: #!/usr/bin/env python3
import argparse
import json
import subprocess
import sys
from pathlib import Path

SCRIPT_DIR = Path(__file__).resolve().parent
RECOMMENDER = SCRIPT_DIR / 'recommend_checks.py'


def run(cmd):
    result = subprocess.run(cmd, text=True, capture_output=True)
    if result.returncode != 0:
        raise RuntimeError(result.stderr.strip() or result.stdout.strip() or f'command failed: {cmd}')
    return result.stdout


def get_plan(repo: str, base: str | None, head: str) -> dict:
    cmd = ['python3', str(RECOMMENDER), '--repo', repo, '--head', head, '--json']
    if base:
        cmd.extend(['--base', base])
    return json.loads(run(cmd))


def bullets(lines):
    return '\n'.join(f'- {line}' for line in lines) if lines else '- none'


def main():
    parser = argparse.ArgumentParser(description='Generate an OpenClaw-flavored PR body from a repo diff.')
    parser.add_argument('--repo', required=True, help='Path to the OpenClaw git repo')
    parser.add_argument('--base', help='Base ref for git diff')
    parser.add_argument('--head', default='HEAD', help='Head ref for git diff')
    parser.add_argument('--title', required=True, help='Planned PR title')
    parser.add_argument('--summary', action='append', default=[], help='Summary bullet (repeatable)')
    parser.add_argument('--why', action='append', default=[], help='Why bullet (repeatable)')
    parser.add_argument('--issue', help='Issue/Discussion/PR reference to mention')
    parser.add_argument('--testing-level', default='lightly tested', choices=['untested', 'lightly tested', 'fully tested'])
    parser.add_argument('--ai-assisted', default='yes', choices=['yes', 'no'])
    parser.add_argument('--output', help='Write markdown to a file instead of stdout')
    args = parser.parse_args()

    plan = get_plan(args.repo, args.base, args.head)

    lines = []
    lines.append(f'# {args.title}')
    lines.append('')
    if args.issue:
        lines.append(f'Related: {args.issue}')
        lines.append('')
    lines.append('## Summary')
    lines.append(bullets(args.summary or ['TODO: summarize the change in 1-2 bullets.']))
    lines.append('')
    lines.append('## Why')
    lines.append(bullets(args.why or ['TODO: explain what was broken or brittle and why this fix is the right scope.']))
    lines.append('')
    lines.append('## Files touched')
    lines.append(bullets(plan.get('changedFiles', [])))
    lines.append('')
    lines.append('## Validation')
    lines.append(bullets(plan.get('recommendedCommands', [])))
    lines.append('')
    lines.append('## Maintainer routing hints')
    lines.append(bullets(plan.get('maintainersToConsider', [])))
    lines.append('')
    lines.append('## Screenshots')
    lines.append('- Add before/after screenshots for UI or visual changes if applicable.')
    lines.append('')
    lines.append('## AI assistance')
    lines.append(f'- AI-assisted: {args.ai_assisted}')
    lines.append(f'- Testing level: {args.testing_level}')
    lines.append('- I reviewed the code and understand what it does.')
    lines.append('')
    lines.append('## Notes')
    lines.append(bullets(plan.get('warnings', [])))
    body = '\n'.join(lines) + '\n'

    if args.output:
        Path(args.output).write_text(body)
    else:
        print(body)


if __name__ == '__main__':
    try:
        main()
    except Exception as exc:
        print(f'error: {exc}', file=sys.stderr)
        sys.exit(1)


path: scripts/recommend_checks.py
content: #!/usr/bin/env python3
import argparse
import json
import subprocess
import sys
from pathlib import Path


def run(cmd, cwd):
    result = subprocess.run(cmd, cwd=cwd, text=True, capture_output=True)
    if result.returncode != 0:
        raise RuntimeError(f"command failed ({' '.join(cmd)}): {result.stderr.strip() or result.stdout.strip()}")
    return result.stdout


def git_changed_files(repo: Path, base: str, head: str) -> list[str]:
    out = run(["git", "diff", "--name-only", f"{base}...{head}"], repo)
    return [line.strip() for line in out.splitlines() if line.strip()]


def detect_default_base(repo: Path) -> str:
    for candidate in ("upstream/main", "origin/main", "main"):
        result = subprocess.run(["git", "rev-parse", "--verify", candidate], cwd=repo, text=True, capture_output=True)
        if result.returncode == 0:
            return candidate
    return "main"


def classify(files: list[str]) -> dict:
    categories = {
        "docs": [],
        "ui": [],
        "ios": [],
        "android": [],
        "extensions": [],
        "gateway": [],
        "channels": [],
        "memory": [],
        "security": [],
        "tests": [],
        "skills": [],
        "misc": [],
    }

    for f in files:
        if f.startswith("docs/") or f in {"README.md", "CONTRIBUTING.md", "SECURITY.md", "VISION.md", "CHANGELOG.md"}:
            categories["docs"].append(f)
        elif f.startswith("ui/") or f.startswith("src/ui/"):
            categories["ui"].append(f)
        elif f.startswith("apps/ios/") or f.startswith("apps/macos/"):
            categories["ios"].append(f)
        elif f.startswith("apps/android/"):
            categories["android"].append(f)
        elif f.startswith("extensions/"):
            categories["extensions"].append(f)
        elif f.startswith("src/gateway") or f.startswith("src/daemon") or f.startswith("src/process"):
            categories["gateway"].append(f)
        elif any(seg in f for seg in ["telegram", "discord", "whatsapp", "slack", "signal", "imessage", "bluebubbles", "matrix", "line", "msteams", "irc", "zalo"]):
            categories["channels"].append(f)
        elif "memory" in f or "qmd" in f:
            categories["memory"].append(f)
        elif any(seg in f.lower() for seg in ["auth", "security", "secret", "token", "oauth", "webhook"]):
            categories["security"].append(f)
        elif ".test." in f or f.startswith("test/") or f.startswith("vitest"):
            categories["tests"].append(f)
        elif f.startswith("skills/"):
            categories["skills"].append(f)
        else:
            categories["misc"].append(f)

    return categories


def unique_ordered(items):
    seen = set()
    out = []
    for item in items:
        if item not in seen:
            seen.add(item)
            out.append(item)
    return out


def recommended_commands(categories: dict, files: list[str]) -> list[str]:
    commands = []
    docs_only = files and all(f in categories["docs"] or f in categories["tests"] for f in files)

    if docs_only:
        commands.extend([
            "pnpm format:docs:check",
            "pnpm lint:docs",
            "pnpm docs:check-links",
        ])
        return unique_ordered(commands)

    commands.extend([
        "pnpm build",
        "pnpm check",
        "pnpm test",
    ])

    if categories["ui"]:
        commands.append("pnpm test:ui")
    if categories["ios"]:
        commands.extend(["pnpm ios:gen", "pnpm ios:build"])
    if categories["android"]:
        commands.extend(["pnpm android:lint", "pnpm android:test"])
    if categories["extensions"]:
        commands.append("pnpm test:extensions")
        if any("voice-call" in f for f in categories["extensions"]):
            commands.append("pnpm test:voicecall:closedloop")
    if categories["gateway"] or categories["security"] or categories["channels"]:
        commands.append("pnpm test:gateway")
    if categories["memory"]:
        commands.append("pnpm test:fast")
    if categories["docs"] and not docs_only:
        commands.extend(["pnpm format:docs:check", "pnpm lint:docs"])

    return unique_ordered(commands)


def maintainers(categories: dict) -> list[str]:
    owners = []
    if categories["memory"]:
        owners.append("Vignesh (@vignesh07) — Memory (QMD), formal modeling, TUI, IRC, Lobster")
    if categories["channels"]:
        if any("telegram" in f for f in categories["channels"]):
            owners.append("Jos (@joshp123) / Ayaan Zaidi (@obviyus) — Telegram")
        if any("discord" in f for f in categories["channels"]):
            owners.append("Shadow (@thewilloftheshadow) — Discord")
    if categories["gateway"]:
        owners.append("Josh Avant (@joshavant) / Jonathan Taylor (@visionik) — Gateway / ACP / core CLI")
    if categories["security"]:
        owners.append("Mariano Belinky (@mbelinky) / Vincent Koc (@vincentkoc) / Josh Avant (@joshavant) — Security/Auth")
    if categories["ui"]:
        owners.append("Val Alexander (@BunsDev) / Gustavo Madeira Santana (@gumadeiras) — UI/UX / web UI")
    if categories["ios"]:
        owners.append("Ayaan Zaidi (@obviyus) / Nimrod Gutman (@ngutman) / Mariano Belinky (@mbelinky) — iOS/macOS")
    if categories["extensions"] or categories["misc"]:
        owners.append("Peter Steinberger (@steipete) / appropriate subsystem maintainer based on touched area")
    return unique_ordered(owners)


def warnings(categories: dict, files: list[str]) -> list[str]:
    out = []
    if categories["ui"]:
        out.append("Include before/after screenshots for UI or visual changes.")
        out.append("Keep Control UI decorators in legacy style (`@state() foo = ...`, `@property(...) count = ...`).")
    if categories["security"]:
        out.append("Keep the PR tightly scoped and explain impact/risk clearly; security/auth changes deserve extra review.")
    if categories["skills"] and len(files) == len(categories["skills"]):
        out.append("Skill contributions belong on ClawHub; only send core-repo skill changes here if they are part of OpenClaw itself.")
    if categories["docs"] and len(files) == len(categories["docs"]):
        out.append("Docs-only changes can usually skip the full build/test path; run docs checks instead.")
    out.append("Mark AI-assisted work in the PR title or description and state the testing level.")
    out.append("Keep the PR focused: one bugfix/feature per PR; use Discussions first for new features or architecture changes.")
    return unique_ordered(out)


def main():
    parser = argparse.ArgumentParser(description="Recommend OpenClaw validation commands and PR notes from a repo diff.")
    parser.add_argument("--repo", default=".", help="Path to the OpenClaw git repo")
    parser.add_argument("--base", help="Base ref for git diff (default: upstream/main if available)")
    parser.add_argument("--head", default="HEAD", help="Head ref for git diff (default: HEAD)")
    parser.add_argument("--json", action="store_true", help="Emit JSON")
    args = parser.parse_args()

    repo = Path(args.repo).resolve()
    if not (repo / ".git").exists():
        raise SystemExit(f"Not a git repo: {repo}")
    if not (repo / "CONTRIBUTING.md").exists() or not (repo / "package.json").exists():
        raise SystemExit(f"Repo does not look like OpenClaw: {repo}")

    base = args.base or detect_default_base(repo)
    files = git_changed_files(repo, base, args.head)
    categories = classify(files)
    data = {
        "repo": str(repo),
        "base": base,
        "head": args.head,
        "changedFiles": files,
        "categories": {k: v for k, v in categories.items() if v},
        "recommendedCommands": recommended_commands(categories, files),
        "maintainersToConsider": maintainers(categories),
        "warnings": warnings(categories, files),
    }

    if args.json:
        print(json.dumps(data, indent=2))
        return

    print(f"Repo: {data['repo']}")
    print(f"Diff: {base}...{args.head}")
    print()
    print("Changed files:")
    if files:
        for f in files:
            print(f"- {f}")
    else:
        print("- (none)")
    print()
    print("Recommended commands:")
    for cmd in data["recommendedCommands"]:
        print(f"- {cmd}")
    print()
    print("Maintainers to consider:")
    if data["maintainersToConsider"]:
        for owner in data["maintainersToConsider"]:
            print(f"- {owner}")
    else:
        print("- No subsystem-specific maintainer hint; use normal reviewer flow.")
    print()
    print("Warnings / PR notes:")
    for note in data["warnings"]:
        print(f"- {note}")


if __name__ == "__main__":
    try:
        main()
    except Exception as exc:
        print(f"error: {exc}", file=sys.stderr)
        sys.exit(1)


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