feat(skills): vendor the outsource skill (explore/review/write)#2
feat(skills): vendor the outsource skill (explore/review/write)#2OriNachum wants to merge 1 commit into
Conversation
Cite-don't-import copy of the canonical outsource skill from guildmaster: hand a scoped repo task to convertible (a *different* engine/mind) for a diverse second opinion — outsource review|explore|write. Portable wrapper; resolves the convertible CLI from PATH. Patch bump per version-check rule. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review Summary by QodoVendor outsource skill with convertible integration
WalkthroughsDescription• Vendors the canonical outsource skill from guildmaster into .claude/skills/outsource/ • Enables delegating scoped repo tasks to convertible (different engine/mind) for diverse second opinions • Provides three verbs: explore (read-only investigation), review (independent diff review), write (implementation) • Read-only verbs run isolated in throwaway git worktrees to prevent side effects • Patch version bump from 0.1.4 to 0.1.5 per version-check CI rule Diagramflowchart LR
User["User invokes outsource verb"] -->|explore| ReadOnly1["Read-only worktree investigation"]
User -->|review| ReadOnly2["Read-only worktree diff review"]
User -->|write| InPlace["In-place drive branch/PR"]
ReadOnly1 --> Convertible["convertible drive CLI"]
ReadOnly2 --> Convertible
InPlace --> Convertible
Convertible --> Result["TaskResult JSON parsed & printed"]
File Changes1. .claude/skills/outsource/scripts/outsource.sh
|
Code Review by Qodo
Context used✅ Compliance rules (platform):
20 rules 1. outsource.sh error format violates
|
|
| cat >&2 <<'EOF' | ||
| error: convertible CLI not found. | ||
| hint: install it with `uv tool install convertible-cli` (or `pipx install convertible-cli`), | ||
| or run from inside the convertible checkout with `uv` available. | ||
| https://github.com/agentculture/convertible | ||
| EOF | ||
| return 1 | ||
| } | ||
|
|
||
| usage() { | ||
| cat <<'EOF' | ||
| outsource — hand a scoped repo task to convertible (a different engine/mind). | ||
|
|
||
| Usage: | ||
| outsource explore "<question or area>" Read-only investigation -> findings (no side effects) | ||
| outsource review "<what to focus on>" Diverse second-opinion on the committed diff (no side effects) | ||
| outsource write "<task>" [--pr] Implement a change (drive branch, or PR with --pr) | ||
|
|
||
| Options: | ||
| --repo PATH Target repo (default: .) | ||
| --base BRANCH Base for `review` diff (default: main) | ||
| --engine NAME Engine wheel (default: $CONVERTIBLE_ENGINE or vllm-openai) | ||
| --model NAME Model (default: $CONVERTIBLE_MODEL or mmangkad/Qwen3.6-27B-NVFP4) | ||
| --base-url URL OpenAI base URL (default: $CONVERTIBLE_BASE_URL or http://localhost:8001/v1) | ||
| --max-steps N Loop step budget (default: 20) | ||
| --timeout N Per-request timeout, seconds (default: $CONVERTIBLE_TIMEOUT or 300) | ||
| --allow-dirty (write) allow running on a dirty tree | ||
| --pr (write) push + open a PR instead of a local drive branch | ||
|
|
||
| explore/review run in a throwaway git worktree at HEAD — they cannot touch your | ||
| working tree or branch. review compares <base>...HEAD (committed changes only). | ||
| EOF | ||
| } | ||
|
|
||
| # ── parse the verb ────────────────────────────────────────────────────────── | ||
| VERB="${1:-}" | ||
| case "$VERB" in | ||
| explore | review | write) shift ;; | ||
| -h | --help) usage; exit 0 ;; | ||
| "") usage >&2; exit 2 ;; | ||
| *) | ||
| echo "error: unknown verb '$VERB' (expected explore|review|write)" >&2 | ||
| echo "hint: run 'outsource --help'" >&2 | ||
| exit 2 | ||
| ;; | ||
| esac | ||
|
|
||
| # Required external tools — fail fast with a clear message, not an opaque | ||
| # mid-run error, if the environment is missing one. | ||
| require_tools() { | ||
| local missing=() t | ||
| for t in python3 git grep mktemp; do | ||
| command -v "$t" >/dev/null 2>&1 || missing+=("$t") | ||
| done | ||
| if [[ ${#missing[@]} -gt 0 ]]; then | ||
| echo "error: missing required tool(s): ${missing[*]}" >&2 | ||
| echo "hint: outsource needs python3, git, grep, and mktemp on PATH." >&2 | ||
| exit 2 | ||
| fi | ||
| } | ||
|
|
||
| # Guard a value-taking flag: a trailing flag with no value would otherwise | ||
| # dereference an unset $2 and abort under `set -u`. | ||
| need_value() { # $1 = remaining arg count ($#), $2 = flag name | ||
| [[ "$1" -ge 2 ]] || { | ||
| echo "error: $2 requires a value" >&2 | ||
| echo "hint: run 'outsource --help'" >&2 | ||
| exit 2 | ||
| } | ||
| } | ||
|
|
||
| require_tools | ||
|
|
||
| # ── defaults + flag parsing ───────────────────────────────────────────────── | ||
| REPO="." | ||
| BASE="main" | ||
| ENGINE="${CONVERTIBLE_ENGINE:-vllm-openai}" | ||
| MODEL="${CONVERTIBLE_MODEL:-mmangkad/Qwen3.6-27B-NVFP4}" | ||
| BASE_URL="${CONVERTIBLE_BASE_URL:-http://localhost:8001/v1}" | ||
| MAX_STEPS=20 | ||
| TIMEOUT="${CONVERTIBLE_TIMEOUT:-300}" | ||
| ALLOW_DIRTY=0 | ||
| OPEN_PR=0 | ||
| ARG="" | ||
|
|
||
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --repo) need_value "$#" "$1"; REPO="$2"; shift 2 ;; | ||
| --base) need_value "$#" "$1"; BASE="$2"; shift 2 ;; | ||
| --engine) need_value "$#" "$1"; ENGINE="$2"; shift 2 ;; | ||
| --model) need_value "$#" "$1"; MODEL="$2"; shift 2 ;; | ||
| --base-url) need_value "$#" "$1"; BASE_URL="$2"; shift 2 ;; | ||
| --max-steps) need_value "$#" "$1"; MAX_STEPS="$2"; shift 2 ;; | ||
| --timeout) need_value "$#" "$1"; TIMEOUT="$2"; shift 2 ;; | ||
| --allow-dirty) ALLOW_DIRTY=1; shift ;; | ||
| --pr) OPEN_PR=1; shift ;; | ||
| -h | --help) usage; exit 0 ;; | ||
| --) shift; while [[ $# -gt 0 ]]; do ARG="${ARG:+$ARG }$1"; shift; done ;; | ||
| -*) echo "error: unknown option '$1'" >&2; echo "hint: run 'outsource --help'" >&2; exit 2 ;; | ||
| *) ARG="${ARG:+$ARG }$1"; shift ;; | ||
| esac | ||
| done | ||
|
|
||
| [[ -n "$ARG" ]] || { echo "error: $VERB needs a description argument" >&2; usage >&2; exit 2; } | ||
| [[ -d "$REPO" ]] || { echo "error: --repo is not a directory: $REPO" >&2; exit 2; } | ||
| REPO="$(cd "$REPO" && pwd)" | ||
|
|
||
| resolve_convertible || exit 2 | ||
|
|
||
| # Per-request timeout is config (no drive flag); EngineConfig reads it from env. | ||
| # A local model can be slow on a growing context, so default generously. | ||
| export CONVERTIBLE_TIMEOUT="$TIMEOUT" | ||
| COMMON_FLAGS=(--engine "$ENGINE" --model "$MODEL" --base-url "$BASE_URL" --max-steps "$MAX_STEPS" --json) | ||
|
|
||
| # ── render an instruction from a prompt template ──────────────────────────── | ||
| render_prompt() { | ||
| local file="$PROMPTS_DIR/$1.md" | ||
| [[ -f "$file" ]] || { echo "error: missing prompt template: $file" >&2; exit 2; } | ||
| ARG="$ARG" BASE="$BASE" python3 - "$file" <<'PY' | ||
| import os, sys | ||
| tpl = open(sys.argv[1], encoding="utf-8").read() | ||
| sys.stdout.write(tpl.replace("$ARGUMENTS", os.environ["ARG"]).replace("$BASE", os.environ["BASE"])) | ||
| PY | ||
| } | ||
|
|
||
| # ── print the TaskResult that convertible emitted as JSON on stdout ───────── | ||
| # Reads JSON on stdin; prints a human/agent-readable digest; exits non-zero if | ||
| # the drive failed. | ||
| print_result() { | ||
| # NOTE: must be `python3 -c`, not `python3 - <<HEREDOC`: a heredoc becomes | ||
| # python's stdin (the script source), which would shadow the piped JSON and | ||
| # leave sys.stdin.read() empty. The script body uses no single quotes. | ||
| python3 -c ' | ||
| import sys, json | ||
| raw = sys.stdin.read().strip() | ||
| if not raw: | ||
| sys.stderr.write("error: convertible produced no result on stdout (see diagnostics above)\n") | ||
| sys.exit(2) | ||
| try: | ||
| d = json.loads(raw) | ||
| except Exception: | ||
| sys.stderr.write("error: could not parse convertible --json output:\n") | ||
| sys.stderr.write(raw[:2000] + "\n") | ||
| sys.exit(2) | ||
| print("status:", d.get("status")) |
There was a problem hiding this comment.
1. outsource.sh error format violates 📘 Rule violation ✧ Quality
Several new error paths print messages that are not exactly two non-empty lines in the required error:/hint: format, and some omit a hint: line entirely. This breaks the standardized CLI error contract and reduces usability/scripting consistency.
Agent Prompt
## Issue description
CLI errors must be exactly two non-empty lines in text mode:
1) `error: <message>`
2) `hint: <remediation>`
The new `outsource.sh` script has multiple error sites that either (a) print more than two lines, or (b) print an `error:` without any `hint:`.
## Issue Context
Compliance rule requires a consistent, two-line remediation format for user-facing CLI failures.
## Fix Focus Areas
- .claude/skills/outsource/scripts/outsource.sh[42-49]
- .claude/skills/outsource/scripts/outsource.sh[146-146]
- .claude/skills/outsource/scripts/outsource.sh[159-160]
- .claude/skills/outsource/scripts/outsource.sh[177-185]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
| usage() { | ||
| cat <<'EOF' | ||
| outsource — hand a scoped repo task to convertible (a different engine/mind). | ||
|
|
||
| Usage: | ||
| outsource explore "<question or area>" Read-only investigation -> findings (no side effects) | ||
| outsource review "<what to focus on>" Diverse second-opinion on the committed diff (no side effects) | ||
| outsource write "<task>" [--pr] Implement a change (drive branch, or PR with --pr) | ||
|
|
||
| Options: | ||
| --repo PATH Target repo (default: .) | ||
| --base BRANCH Base for `review` diff (default: main) | ||
| --engine NAME Engine wheel (default: $CONVERTIBLE_ENGINE or vllm-openai) | ||
| --model NAME Model (default: $CONVERTIBLE_MODEL or mmangkad/Qwen3.6-27B-NVFP4) | ||
| --base-url URL OpenAI base URL (default: $CONVERTIBLE_BASE_URL or http://localhost:8001/v1) | ||
| --max-steps N Loop step budget (default: 20) | ||
| --timeout N Per-request timeout, seconds (default: $CONVERTIBLE_TIMEOUT or 300) | ||
| --allow-dirty (write) allow running on a dirty tree | ||
| --pr (write) push + open a PR instead of a local drive branch | ||
|
|
||
| explore/review run in a throwaway git worktree at HEAD — they cannot touch your | ||
| working tree or branch. review compares <base>...HEAD (committed changes only). | ||
| EOF | ||
| } | ||
|
|
||
| # ── parse the verb ────────────────────────────────────────────────────────── | ||
| VERB="${1:-}" | ||
| case "$VERB" in | ||
| explore | review | write) shift ;; | ||
| -h | --help) usage; exit 0 ;; | ||
| "") usage >&2; exit 2 ;; | ||
| *) | ||
| echo "error: unknown verb '$VERB' (expected explore|review|write)" >&2 | ||
| echo "hint: run 'outsource --help'" >&2 | ||
| exit 2 | ||
| ;; | ||
| esac | ||
|
|
||
| # Required external tools — fail fast with a clear message, not an opaque | ||
| # mid-run error, if the environment is missing one. | ||
| require_tools() { | ||
| local missing=() t | ||
| for t in python3 git grep mktemp; do | ||
| command -v "$t" >/dev/null 2>&1 || missing+=("$t") | ||
| done | ||
| if [[ ${#missing[@]} -gt 0 ]]; then | ||
| echo "error: missing required tool(s): ${missing[*]}" >&2 | ||
| echo "hint: outsource needs python3, git, grep, and mktemp on PATH." >&2 | ||
| exit 2 | ||
| fi | ||
| } | ||
|
|
||
| # Guard a value-taking flag: a trailing flag with no value would otherwise | ||
| # dereference an unset $2 and abort under `set -u`. | ||
| need_value() { # $1 = remaining arg count ($#), $2 = flag name | ||
| [[ "$1" -ge 2 ]] || { | ||
| echo "error: $2 requires a value" >&2 | ||
| echo "hint: run 'outsource --help'" >&2 | ||
| exit 2 | ||
| } | ||
| } | ||
|
|
||
| require_tools | ||
|
|
||
| # ── defaults + flag parsing ───────────────────────────────────────────────── | ||
| REPO="." | ||
| BASE="main" | ||
| ENGINE="${CONVERTIBLE_ENGINE:-vllm-openai}" | ||
| MODEL="${CONVERTIBLE_MODEL:-mmangkad/Qwen3.6-27B-NVFP4}" | ||
| BASE_URL="${CONVERTIBLE_BASE_URL:-http://localhost:8001/v1}" | ||
| MAX_STEPS=20 | ||
| TIMEOUT="${CONVERTIBLE_TIMEOUT:-300}" | ||
| ALLOW_DIRTY=0 | ||
| OPEN_PR=0 | ||
| ARG="" | ||
|
|
||
| while [[ $# -gt 0 ]]; do | ||
| case "$1" in | ||
| --repo) need_value "$#" "$1"; REPO="$2"; shift 2 ;; | ||
| --base) need_value "$#" "$1"; BASE="$2"; shift 2 ;; | ||
| --engine) need_value "$#" "$1"; ENGINE="$2"; shift 2 ;; | ||
| --model) need_value "$#" "$1"; MODEL="$2"; shift 2 ;; | ||
| --base-url) need_value "$#" "$1"; BASE_URL="$2"; shift 2 ;; | ||
| --max-steps) need_value "$#" "$1"; MAX_STEPS="$2"; shift 2 ;; | ||
| --timeout) need_value "$#" "$1"; TIMEOUT="$2"; shift 2 ;; | ||
| --allow-dirty) ALLOW_DIRTY=1; shift ;; | ||
| --pr) OPEN_PR=1; shift ;; | ||
| -h | --help) usage; exit 0 ;; | ||
| --) shift; while [[ $# -gt 0 ]]; do ARG="${ARG:+$ARG }$1"; shift; done ;; | ||
| -*) echo "error: unknown option '$1'" >&2; echo "hint: run 'outsource --help'" >&2; exit 2 ;; | ||
| *) ARG="${ARG:+$ARG }$1"; shift ;; | ||
| esac | ||
| done | ||
|
|
||
| [[ -n "$ARG" ]] || { echo "error: $VERB needs a description argument" >&2; usage >&2; exit 2; } | ||
| [[ -d "$REPO" ]] || { echo "error: --repo is not a directory: $REPO" >&2; exit 2; } | ||
| REPO="$(cd "$REPO" && pwd)" | ||
|
|
||
| resolve_convertible || exit 2 | ||
|
|
||
| # Per-request timeout is config (no drive flag); EngineConfig reads it from env. | ||
| # A local model can be slow on a growing context, so default generously. | ||
| export CONVERTIBLE_TIMEOUT="$TIMEOUT" | ||
| COMMON_FLAGS=(--engine "$ENGINE" --model "$MODEL" --base-url "$BASE_URL" --max-steps "$MAX_STEPS" --json) | ||
|
|
There was a problem hiding this comment.
2. outsource missing --json 📘 Rule violation ◔ Observability
The new outsource CLI does not accept a --json flag for structured output even though it introduces new verbs (explore, review, write). This violates the requirement that CLI verbs support --json with structured stdout output.
Agent Prompt
## Issue description
New/modified CLI verbs must support a `--json` flag and, when enabled, emit valid JSON to stdout (with errors to stderr).
The new `outsource` script adds/defines verbs but provides no `--json` flag in usage text or option parsing, and always prints a human-formatted digest.
## Issue Context
Compliance rule 847437 requires `--json` support for CLI verbs so callers can reliably parse outputs.
## Fix Focus Areas
- .claude/skills/outsource/scripts/outsource.sh[51-74]
- .claude/skills/outsource/scripts/outsource.sh[127-143]
- .claude/skills/outsource/scripts/outsource.sh[167-198]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Summary
outsourceskill (explore/review/write) from guildmaster into.claude/skills/outsource/convertible(a different engine/mind) for a diverse second opinion —outsource reviewgives an independent diff review,outsource exploregives a fresh read of an area,outsource writedelegates a small implementationINBOUND_ORIGINS=guildmaster; resolves the convertible CLI from PATH; read-only verbs run in an isolated throwaway git worktree.claude/skills/**is excluded by repo config — 0 errors, no changes neededTest plan
.claude/skills/outsource/SKILL.mdis present with correct frontmattername: outsource.claude/skills/outsource/scripts/directory exists withoutsource.shentry pointnpx markdownlint-cli2 ".claude/skills/outsource/**/*.md"— expect 0 errors (excluded by repo config)uv run pytest -n auto -v— all tests passsloth --versionreports0.1.5convertibleis on PATH, runoutsource reviewagainst a test diff🤖 Generated with Claude Code