Contribute Command Center Overview Local-only OSS contribution workflow. The skill itself is the system — there is no separate CLI binary, dashboard, or cloud backend. State lives in three places: 1. GitHub itself — fetched live via for any PR/issue state question. Never cached long-term. 2. Markdown candidate files at — one per issue we're tracking. Frontmatter is the queryable layer (status, scout score, repo, research path, overrides). Body holds claim drafts, PR drafts, scope notes. 3. Markdown repo dossiers at — one per upstream repo we contribute to. Built by the subagent. Frontmatter i…

\\t' read -r id gate; do\n phase=\"${id:0:1}\"\n phase_total[$phase]=$(( ${phase_total[$phase]:-0} + 1 ))\n total=$(( total + 1 ))\n case \"$gate\" in\n *planned*)\n phase_planned[$phase]=$(( ${phase_planned[$phase]:-0} + 1 ))\n planned=$(( planned + 1 ))\n ;;\n *)\n # Real gate ref. Confirm a script file actually exists for it.\n # Gate refs in the catalog look like \"a01-already-assigned\" — match by\n # prefix lower-case + leading digits.\n gate_id_normalized=$(/usr/bin/printf '%s' \"$gate\" | /usr/bin/tr '[:upper:]' '[:lower:]' | /usr/bin/sed 's/[^a-z0-9].*//')\n if /usr/bin/find \"$GATES_DIR\" -maxdepth 1 -name \"${gate_id_normalized}*.sh\" -print -quit 2>/dev/null | /usr/bin/grep -q .; then\n phase_implemented[$phase]=$(( ${phase_implemented[$phase]:-0} + 1 ))\n implemented=$(( implemented + 1 ))\n else\n phase_planned[$phase]=$(( ${phase_planned[$phase]:-0} + 1 ))\n planned=$(( planned + 1 ))\n fi\n ;;\n esac\ndone \u003c \u003c(parse_catalog)\n\n# Render\n/usr/bin/printf '\\nCatalog → Gate coverage\\n'\n/usr/bin/printf '═════════════════════════════════════════\\n'\n/usr/bin/printf '%-8s %10s %12s %10s\\n' \"PHASE\" \"TOTAL\" \"IMPLEMENTED\" \"COVERAGE\"\n/usr/bin/printf '─────────────────────────────────────────\\n'\nfor phase in A B C D E F G; do\n t=${phase_total[$phase]:-0}\n i=${phase_implemented[$phase]:-0}\n if [[ $t -gt 0 ]]; then\n pct=$(( i * 100 / t ))\n /usr/bin/printf '%-8s %10s %12s %9s%%\\n' \"$phase\" \"$t\" \"$i\" \"$pct\"\n fi\ndone\n/usr/bin/printf '─────────────────────────────────────────\\n'\noverall_pct=$(( implemented * 100 / total ))\n/usr/bin/printf '%-8s %10s %12s %9s%%\\n' \"TOTAL\" \"$total\" \"$implemented\" \"$overall_pct\"\n/usr/bin/printf '═════════════════════════════════════════\\n\\n'\n\nif [[ $planned -gt 0 ]]; then\n /usr/bin/printf ' %d catalog mode(s) still planned (no gate file).\\n\\n' \"$planned\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3920,"content_sha256":"995c3ff39919ae4bfda37396e62412b32e7b8a1c113b695308ca1a62cbc24b2e"},{"filename":"scripts/gate-runner.sh","content":"#!/usr/bin/env bash\n# gate-runner.sh — the orchestrator that runs gates for a lifecycle transition.\n#\n# Usage: gate-runner.sh \u003caction> \u003ccandidate-path> [\u003cdossier-path>]\n# action: e.g., \"shortlist→claimed\", \"working→submitted\", \"open-pr\", \"post-comment\"\n#\n# Discovers gates by glob from ~/.contribute-system/gates/\u003cphase>*.sh,\n# filters by which gates apply to this action (per the gate's filename phase\n# letter and the action's lifecycle stage), runs each in turn with a 10-second\n# timeout, aggregates verdicts.\n#\n# Output: one JSON per gate to stderr (for human-readable progress);\n# final aggregated verdict to stdout.\n#\n# Exit code: 0 if all PASS/WARN/INFORM/SKIP; 1 if any BLOCK (unless every\n# BLOCK was overridden via the candidate's overrides: frontmatter).\n\nset -euo pipefail\n\nACTION=\"${1:-}\"\nCANDIDATE=\"${2:-}\"\nDOSSIER=\"${3:-}\"\n\nif [[ -z \"$ACTION\" || -z \"$CANDIDATE\" ]]; then\n echo \"usage: $0 \u003caction> \u003ccandidate-path> [\u003cdossier-path>]\" >&2\n exit 64\nfi\n\n# Discover gates from two dirs, in priority order:\n# 1. Bundled canonical set at skill/scripts/gates/ (discovered via script location)\n# 2. User-override gates at ~/.contribute-system/gates/ (personal additions / overrides)\n# This split means the bundled set is always present (distributable) and users can\n# add custom gates or fork existing ones without modifying the skill package.\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nBUNDLED_GATE_DIR=\"${SCRIPT_DIR}/gates\"\nUSER_GATE_DIR=\"$HOME/.contribute-system/gates\"\nLOG=\"$HOME/.contribute-system/log.jsonl\"\nNOW=$(/usr/bin/date -u +%Y-%m-%dT%H:%M:%SZ)\n\n# Map action → relevant gate phases. Gates are filtered by phase letter\n# (first char of filename). An action runs all gates in its applicable phases.\ncase \"$ACTION\" in\n \"open→shortlist\") PHASES=\"A\" ;;\n \"shortlist→claimed\") PHASES=\"A E\" ;;\n \"claimed→working\") PHASES=\"A B\" ;;\n \"working→submitted\") PHASES=\"B C E F G\" ;;\n \"open-pr\") PHASES=\"C E\" ;;\n \"flip-to-ready\") PHASES=\"C\" ;;\n \"post-comment\") PHASES=\"D\" ;;\n \"open-issue\") PHASES=\"D\" ;;\n *) PHASES=\"A B C D E F G\" ;; # unknown: run everything\nesac\n\n# Read disabled_gates from dossier (per-repo opt-out)\nDISABLED=\"\"\nif [[ -n \"$DOSSIER\" && -f \"$DOSSIER\" ]]; then\n DISABLED=$(/usr/bin/awk '/^---$/{fm=!fm?1:2;next} fm==1 && /^disabled_gates:/{\n sub(/^disabled_gates:[[:space:]]*\\[/,\"\"); sub(/\\][[:space:]]*$/,\"\"); gsub(/[[:space:]]/,\"\"); print; exit\n }' \"$DOSSIER\" 2>/dev/null || /usr/bin/echo \"\")\nfi\n\n# Read repo + branch from candidate frontmatter\nREPO=$(/usr/bin/awk '/^---$/{fm=!fm?1:2;next} fm==1 && /^repo:/{sub(/^repo:[[:space:]]*/,\"\"); print; exit}' \"$CANDIDATE\" 2>/dev/null || /usr/bin/echo \"\")\nBRANCH=$(/usr/bin/awk '/^---$/{fm=!fm?1:2;next} fm==1 && /^branch:/{sub(/^branch:[[:space:]]*/,\"\"); print; exit}' \"$CANDIDATE\" 2>/dev/null || /usr/bin/echo \"\")\n\n# Build the input JSON once (passed to every gate)\nINPUT_JSON=$(jq -nc \\\n --arg candidate \"$CANDIDATE\" \\\n --arg dossier \"$DOSSIER\" \\\n --arg action \"$ACTION\" \\\n --arg repo \"$REPO\" \\\n --arg branch \"$BRANCH\" \\\n '{candidate: $candidate, dossier: $dossier, action: $action, env: {repo: $repo, branch: $branch}}')\n\n# Discover gate scripts for the relevant phases.\n# Bundled gates first, then user-override dir.\n# A user gate with the same basename shadows the bundled one (by position in GATES array).\n# gate-runner runs all discovered gates; SKIP is cheap.\ndeclare -A SEEN_GATES=()\nGATES=()\nfor DIR in \"$BUNDLED_GATE_DIR\" \"$USER_GATE_DIR\" ; do\n [[ -d \"$DIR\" ]] || continue\n for PHASE in $PHASES ; do\n for GATE in \"$DIR\"/${PHASE,,}*.sh ; do\n [[ -f \"$GATE\" && -x \"$GATE\" ]] || continue\n BN=$(/usr/bin/basename \"$GATE\")\n # User-override dir wins — if a gate with same name was already queued from\n # bundled dir, replace it. Simple: add all, let user-dir overwrite.\n [[ -z \"${SEEN_GATES[$BN]:-}\" ]] && GATES+=(\"$GATE\") || true\n SEEN_GATES[$BN]=\"$GATE\"\n done\n done\ndone\n\nif [[ \"${#GATES[@]}\" -eq 0 ]]; then\n /usr/bin/echo '{\"verdict\":\"PASS\",\"gates_run\":0,\"reason\":\"no gates applicable for this action\"}'\n exit 0\nfi\n\n# Run each gate. Aggregate.\nPASS_COUNT=0\nWARN_COUNT=0\nBLOCK_COUNT=0\nINFORM_COUNT=0\nSKIP_COUNT=0\nBLOCKERS=()\nWARNINGS=()\n\n/usr/bin/printf '\\n=== gate-runner: %s — %d gates ===\\n' \"$ACTION\" \"${#GATES[@]}\" >&2\n\nfor GATE in \"${GATES[@]}\" ; do\n GATE_NAME=$(/usr/bin/basename \"$GATE\" .sh)\n GATE_ID=$(/usr/bin/printf '%s' \"$GATE_NAME\" | /usr/bin/cut -d- -f1 | /usr/bin/tr 'a-z' 'A-Z')\n\n # Per-repo opt-out check\n if /usr/bin/echo \",$DISABLED,\" | /usr/bin/grep -qi \",$GATE_ID,\"; then\n /usr/bin/printf ' [%s] SKIP — disabled per dossier\\n' \"$GATE_ID\" >&2\n SKIP_COUNT=$(( SKIP_COUNT + 1 ))\n continue\n fi\n\n # Run the gate with timeout. Capture stdout. Exit non-zero = treat as BLOCK.\n if VERDICT_JSON=$(/usr/bin/timeout 10 bash -c \"/usr/bin/printf '%s' '$INPUT_JSON' | '$GATE'\" 2>/dev/null); then\n : # ok, parse verdict\n else\n VERDICT_JSON=$(jq -nc --arg gid \"$GATE_ID\" '{severity:\"BLOCK\", gate:$gid, reason:\"gate timed out or crashed (>10s or non-zero exit) — fail-closed\", fix_hint:\"check the gate script for bugs; preamble.sh should have caught it\"}')\n fi\n\n # Defensive parse: if the gate returned malformed JSON, treat as BLOCK\n if ! /usr/bin/printf '%s' \"$VERDICT_JSON\" | jq -e . >/dev/null 2>&1; then\n VERDICT_JSON=$(jq -nc --arg gid \"$GATE_ID\" --arg raw \"$VERDICT_JSON\" '{severity:\"BLOCK\", gate:$gid, reason:\"gate returned malformed JSON — fail-closed\", fix_hint:(\"raw stdout: \" + ($raw | tostring))}')\n fi\n\n SEV=$(/usr/bin/printf '%s' \"$VERDICT_JSON\" | jq -r '.severity')\n REASON=$(/usr/bin/printf '%s' \"$VERDICT_JSON\" | jq -r '.reason')\n FIX=$(/usr/bin/printf '%s' \"$VERDICT_JSON\" | jq -r '.fix_hint // \"\"')\n\n case \"$SEV\" in\n PASS) PASS_COUNT=$((PASS_COUNT+1)); /usr/bin/printf ' [%s] \\033[32mPASS\\033[0m — %s\\n' \"$GATE_ID\" \"$REASON\" >&2 ;;\n WARN) WARN_COUNT=$((WARN_COUNT+1)); WARNINGS+=(\"$GATE_ID: $REASON ($FIX)\") ; /usr/bin/printf ' [%s] \\033[33mWARN\\033[0m — %s\\n' \"$GATE_ID\" \"$REASON\" >&2 ;;\n BLOCK) BLOCK_COUNT=$((BLOCK_COUNT+1)); BLOCKERS+=(\"$GATE_ID: $REASON ($FIX)\") ; /usr/bin/printf ' [%s] \\033[31mBLOCK\\033[0m — %s\\n fix: %s\\n' \"$GATE_ID\" \"$REASON\" \"$FIX\" >&2 ;;\n INFORM) INFORM_COUNT=$((INFORM_COUNT+1)); /usr/bin/printf ' [%s] INFO — %s\\n' \"$GATE_ID\" \"$REASON\" >&2 ;;\n SKIP) SKIP_COUNT=$((SKIP_COUNT+1)); /usr/bin/printf ' [%s] SKIP — %s\\n' \"$GATE_ID\" \"$REASON\" >&2 ;;\n *) BLOCK_COUNT=$((BLOCK_COUNT+1)); BLOCKERS+=(\"$GATE_ID: unknown severity '$SEV'\") ; /usr/bin/printf ' [%s] BLOCK — unknown severity: %s\\n' \"$GATE_ID\" \"$SEV\" >&2 ;;\n esac\n\n # Append run to log\n /usr/bin/printf '%s\\n' \"$(jq -nc \\\n --arg ts \"$NOW\" \\\n --arg gate \"$GATE_ID\" \\\n --arg action \"$ACTION\" \\\n --arg repo \"$REPO\" \\\n --arg sev \"$SEV\" \\\n --arg reason \"$REASON\" \\\n '{ts: $ts, event: \"gate_run\", details: {gate: $gate, action: $action, repo: $repo, severity: $sev, reason: $reason}}')\" >> \"$LOG\" 2>/dev/null || true\ndone\n\n# Check overrides on the candidate (any BLOCK gate the user explicitly waived).\nOVERRIDDEN=()\nif [[ -f \"$CANDIDATE\" ]] && /usr/bin/grep -q '^overrides:' \"$CANDIDATE\" 2>/dev/null; then\n while IFS= read -r OG; do\n OVERRIDDEN+=(\"$OG\")\n done \u003c \u003c(/usr/bin/awk '/^overrides:/{flag=1;next} /^[a-z_]+:/{flag=0} flag && /gate:/{sub(/.*gate:[[:space:]]*/,\"\");sub(/[[:space:]]*,.*$/,\"\");print}' \"$CANDIDATE\" 2>/dev/null)\nfi\n\n# Filter BLOCKERS against OVERRIDDEN\nEFFECTIVE_BLOCKS=()\nfor B in \"${BLOCKERS[@]}\"; do\n BID=\"${B%%:*}\"\n IS_OVERRIDDEN=0\n for O in \"${OVERRIDDEN[@]}\"; do\n [[ \"$O\" == \"$BID\" ]] && { IS_OVERRIDDEN=1; break; }\n done\n if [[ \"$IS_OVERRIDDEN\" -eq 0 ]]; then\n EFFECTIVE_BLOCKS+=(\"$B\")\n fi\ndone\n\n# Final verdict\nTOTAL=${#GATES[@]}\nEFFECTIVE_BLOCK_COUNT=${#EFFECTIVE_BLOCKS[@]}\n/usr/bin/printf '\\n=== summary: %d gates · %d PASS · %d WARN · %d BLOCK (%d after overrides) · %d INFORM · %d SKIP ===\\n\\n' \\\n \"$TOTAL\" \"$PASS_COUNT\" \"$WARN_COUNT\" \"$BLOCK_COUNT\" \"$EFFECTIVE_BLOCK_COUNT\" \"$INFORM_COUNT\" \"$SKIP_COUNT\" >&2\n\nif [[ \"$EFFECTIVE_BLOCK_COUNT\" -gt 0 ]]; then\n /usr/bin/echo \"$(jq -nc --argjson b \"$(printf '%s\\n' \"${EFFECTIVE_BLOCKS[@]}\" | jq -R . | jq -s .)\" --argjson w \"$(printf '%s\\n' \"${WARNINGS[@]}\" | jq -R . | jq -s .)\" --argjson n \"$EFFECTIVE_BLOCK_COUNT\" '{verdict: \"BLOCK\", effective_blocks: $n, blockers: $b, warnings: $w}')\"\n exit 1\nelse\n /usr/bin/echo \"$(jq -nc --argjson w \"$(printf '%s\\n' \"${WARNINGS[@]}\" | jq -R . | jq -s .)\" --argjson p \"$PASS_COUNT\" '{verdict: \"PASS\", gates_passed: $p, warnings: $w}')\"\n exit 0\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":8685,"content_sha256":"6eeeb5c64b1c4960553696802f62a0234aa7aa44dbc861ae61e7a580dccd102a"},{"filename":"scripts/gates/a01-already-assigned.sh","content":"#!/usr/bin/env bash\n# Catalog: A1 — Claim already-assigned issue\n# Mitigates: Tracer-Cloud opensre #1129 trap (2026-05-02) — issue assigned to\n# unKnownNG day before our scout run; he was actively asking maintainer\n# clarifying questions in comments. We almost claim-jumped real human work.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\n# Pull issue number from candidate frontmatter\nISSUE_NUM=$(fm_field \"$GATE_CANDIDATE_PATH\" \"issue_number\")\nif [[ -z \"$ISSUE_NUM\" || -z \"$GATE_REPO\" ]]; then\n gate_skip \"no issue_number or repo in candidate\"\nfi\n\n# Live check (this is one of the few gates that MUST be live — assignment\n# state changes too fast to trust the dossier)\nASSIGNEES=$(gh_safe issue view \"$ISSUE_NUM\" --repo \"$GATE_REPO\" --json assignees --jq '[.assignees[].login] | join(\",\")' || /usr/bin/echo \"\")\n\nif [[ -n \"$ASSIGNEES\" ]]; then\n gate_block \"issue is assigned to [$ASSIGNEES]\" \"wait for them to drop it, or pick a different candidate. Override only if you've coordinated with the assignee in a comment.\"\nfi\n\ngate_pass \"no assignees on issue\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1077,"content_sha256":"c2153a9f344b7c1b2ec5e03cb9032733664bed5d2e066444b9c2fc59f23c7698"},{"filename":"scripts/gates/a02-already-shipped.sh","content":"#!/usr/bin/env bash\n# Catalog: A2 — Claim work that's already merged in another PR\n# Mitigates: PostHog #55412 trap (2026-05-02) — issue was open but PR #57145\n# had already merged the principled fix. Without this check we'd have\n# submitted a duplicate and burned an AI policy strike.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nISSUE_NUM=$(fm_field \"$GATE_CANDIDATE_PATH\" \"issue_number\")\nif [[ -z \"$ISSUE_NUM\" || -z \"$GATE_REPO\" ]]; then\n gate_skip \"no issue_number or repo in candidate\"\nfi\n\n# Check each of GitHub's three auto-close keywords. NB: gh search uses --merged\n# (NOT --state=merged), and \"X OR Y OR Z\" is treated as a literal phrase, so\n# we must run separate calls. (Lessons logged in scout's MEMORY.md.)\nSHIPPED_PR=\"\"\nfor KW in closes fixes resolves ; do\n HIT=$(gh_safe search prs --repo=\"$GATE_REPO\" \"$KW #$ISSUE_NUM\" --merged --limit 1 --json url --jq '.[0].url // empty' || /usr/bin/echo \"\")\n if [[ -n \"$HIT\" ]]; then\n SHIPPED_PR=\"$HIT\"\n break\n fi\ndone\n\nif [[ -n \"$SHIPPED_PR\" ]]; then\n gate_block \"issue already shipped in $SHIPPED_PR\" \"the issue is open but its fix has already merged. Post a 'safe to close?' comment on the issue (good citizen move) and pick a different candidate.\"\nfi\n\ngate_pass \"no merged PR claims to close this issue\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1291,"content_sha256":"a8f28e7f3edda8776164227d51605f6b2789bc1788cddc500093e65993ea6b63"},{"filename":"scripts/gates/a03-duplicate-flagged.sh","content":"#!/usr/bin/env bash\n# Catalog: A3 — Issue flagged as duplicate in body or comments\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nISSUE_NUM=$(fm_field \"$GATE_CANDIDATE_PATH\" \"issue_number\")\nif [[ -z \"$ISSUE_NUM\" || -z \"$GATE_REPO\" ]]; then\n gate_skip \"no issue_number or repo in candidate\"\nfi\n\nTEXT=$(gh_safe issue view \"$ISSUE_NUM\" --repo \"$GATE_REPO\" --json body,comments --jq '.body + \" \" + (.comments | map(.body) | join(\" \"))' || /usr/bin/echo \"\")\n\nif [[ -z \"$TEXT\" ]]; then\n gate_inform \"could not fetch issue body/comments\"\nfi\n\nif /usr/bin/printf '%s' \"$TEXT\" | /usr/bin/grep -qiE \"(duplicate of|dupe of|see)[[:space:]]+#[0-9]+\"; then\n gate_warn \"issue body or comments mention being a duplicate\" \"verify with the maintainer before claiming; the underlying issue may be elsewhere\"\nfi\n\ngate_pass \"no duplicate references found\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":851,"content_sha256":"1711f709113349c67fa7f9a0485ba85ca20062e0f51a7e5d4ebdbffa3718bff4"},{"filename":"scripts/gates/a04-issue-age.sh","content":"#!/usr/bin/env bash\n# Catalog: A4 — Issue is too old (stale, abandoned, or contested)\n# Mitigates: stale issues are a real anti-signal — either the maintainer\n# decided not to fix it, the underlying code changed and the issue's premise\n# no longer holds, or someone has been silently working on it for months\n# without progress. None of those are good for our merge probability.\n#\n# Thresholds (configurable via dossier `max_issue_age_days:`):\n# issue_age \u003c= 90 days → PASS\n# issue_age 91-180 days → WARN (\"aging — verify it's still actionable\")\n# issue_age 181-365 days → BLOCK (\"stale; recommend pick a fresher target\")\n# issue_age > 365 days → BLOCK + harder (\"zombie issue; fix likely landed elsewhere\")\n#\n# A repo with `max_issue_age_days: N` in dossier overrides the default 180.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nISSUE_NUM=$(fm_field \"$GATE_CANDIDATE_PATH\" \"issue_number\")\nif [[ -z \"$ISSUE_NUM\" || -z \"$GATE_REPO\" ]]; then\n gate_skip \"no issue_number or repo in candidate\"\nfi\n\n# Read max_issue_age from dossier, default 180\nMAX_AGE_DAYS=180\nif [[ -n \"$GATE_DOSSIER_PATH\" && -f \"$GATE_DOSSIER_PATH\" ]]; then\n V=$(fm_field \"$GATE_DOSSIER_PATH\" \"max_issue_age_days\")\n [[ -n \"$V\" && \"$V\" =~ ^[0-9]+$ ]] && MAX_AGE_DAYS=\"$V\"\nfi\n\n# Live fetch — issue createdAt + last activity (updatedAt + last comment)\nMETA_JSON=$(gh_safe issue view \"$ISSUE_NUM\" --repo \"$GATE_REPO\" --json createdAt,updatedAt,comments --jq '{createdAt, updatedAt, last_comment_at: (.comments | sort_by(.createdAt) | .[-1].createdAt // null)}' || /usr/bin/echo \"\")\n\nif [[ -z \"$META_JSON\" ]] ; then\n gate_skip \"couldn't fetch issue metadata (gh failure?)\"\nfi\n\nCREATED=$(/usr/bin/printf '%s' \"$META_JSON\" | jq -r '.createdAt // \"\"')\nUPDATED=$(/usr/bin/printf '%s' \"$META_JSON\" | jq -r '.updatedAt // \"\"')\nLAST_COMMENT=$(/usr/bin/printf '%s' \"$META_JSON\" | jq -r '.last_comment_at // \"\"')\n\nif [[ -z \"$CREATED\" ]] ; then\n gate_skip \"issue createdAt missing in API response\"\nfi\n\nNOW_EPOCH=$(/usr/bin/date -u +%s)\nCREATED_EPOCH=$(/usr/bin/date -u -d \"$CREATED\" +%s 2>/dev/null || echo 0)\nAGE_DAYS=$(( (NOW_EPOCH - CREATED_EPOCH) / 86400 ))\n\n# Activity recency — most recent of updatedAt or last comment\nLATEST_ACT_EPOCH=0\nif [[ -n \"$UPDATED\" ]] ; then\n V=$(/usr/bin/date -u -d \"$UPDATED\" +%s 2>/dev/null || echo 0)\n [[ \"$V\" -gt \"$LATEST_ACT_EPOCH\" ]] && LATEST_ACT_EPOCH=\"$V\"\nfi\nif [[ -n \"$LAST_COMMENT\" && \"$LAST_COMMENT\" != \"null\" ]] ; then\n V=$(/usr/bin/date -u -d \"$LAST_COMMENT\" +%s 2>/dev/null || echo 0)\n [[ \"$V\" -gt \"$LATEST_ACT_EPOCH\" ]] && LATEST_ACT_EPOCH=\"$V\"\nfi\nSILENCE_DAYS=$(( (NOW_EPOCH - LATEST_ACT_EPOCH) / 86400 ))\n\n# Verdicts\nif [[ \"$AGE_DAYS\" -gt 365 ]] ; then\n gate_block \"issue is ${AGE_DAYS}d old (>365d zombie threshold)\" \"1+ year-old issues are usually either fixed elsewhere, abandoned, or have premises that no longer apply. Pick a fresher target. Override only with a written rationale referencing what's changed.\"\nfi\n\nif [[ \"$AGE_DAYS\" -gt \"$MAX_AGE_DAYS\" ]] ; then\n gate_block \"issue is ${AGE_DAYS}d old (max for this repo: ${MAX_AGE_DAYS}d)\" \"stale issue. Maintainer engagement likely dropped. Pick a fresher target — or override with rationale if you have direct maintainer signal that it's still wanted.\"\nfi\n\n# Activity-based check — even fresh issues with long silence are suspect\nif [[ \"$SILENCE_DAYS\" -gt 90 ]] ; then\n gate_warn \"issue is fresh (${AGE_DAYS}d old) but no activity in ${SILENCE_DAYS}d\" \"comment first to ping for current relevance before investing time\"\nfi\n\nif [[ \"$AGE_DAYS\" -gt 90 ]] ; then\n gate_warn \"issue is ${AGE_DAYS}d old (aging — under the ${MAX_AGE_DAYS}d block threshold but past the 90d sweet spot)\" \"verify the underlying premise still holds in current code; verify maintainer still wants this fix\"\nfi\n\ngate_pass \"issue is ${AGE_DAYS}d old, last activity ${SILENCE_DAYS}d ago\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3921,"content_sha256":"dc31104c708b35b12cf234af3031643200c952c1b54f00c67e7fc59dbc30c787"},{"filename":"scripts/gates/a05-issue-still-open.sh","content":"#!/usr/bin/env bash\n# Catalog: A5 — Issue is still OPEN right now (not closed since dossier built)\n# Mitigates: lingdojo/kana-dojo #15441 trap (2026-05-03) — issue was OPEN at\n# 04:54Z when scout shortlisted it; CLOSED at 05:00Z (NOT_PLANNED) by\n# maintainer killing a bot-generated cron issue. Our dossier and the existing\n# A1/A2 gates wouldn't have caught it because they check assignees and\n# already-shipped, not state. This is the simplest gate to write and the\n# highest-value catch — bot-generated issues at active repos can close\n# minutes after discovery.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nISSUE_NUM=$(fm_field \"$GATE_CANDIDATE_PATH\" \"issue_number\")\nif [[ -z \"$ISSUE_NUM\" || -z \"$GATE_REPO\" ]]; then\n gate_skip \"no issue_number or repo in candidate\"\nfi\n\nSTATE_JSON=$(gh_safe issue view \"$ISSUE_NUM\" --repo \"$GATE_REPO\" --json state,stateReason,closedAt --jq '{state, stateReason: (.stateReason // \"\"), closedAt: (.closedAt // \"\")}' || /usr/bin/echo \"\")\n\nif [[ -z \"$STATE_JSON\" ]] ; then\n gate_skip \"couldn't fetch issue state\"\nfi\n\nSTATE=$(/usr/bin/printf '%s' \"$STATE_JSON\" | jq -r '.state')\nREASON=$(/usr/bin/printf '%s' \"$STATE_JSON\" | jq -r '.stateReason')\nCLOSED_AT=$(/usr/bin/printf '%s' \"$STATE_JSON\" | jq -r '.closedAt' | /usr/bin/cut -c1-19)\n\nif [[ \"$STATE\" == \"CLOSED\" ]] ; then\n gate_block \"issue is CLOSED (reason: ${REASON:-unspecified}, closed at ${CLOSED_AT:-unknown})\" \"issue closed since shortlist. If reason is COMPLETED, fix already shipped — pick a different target. If NOT_PLANNED, maintainer rejected the work — drop the candidate.\"\nfi\n\ngate_pass \"issue state = OPEN\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1637,"content_sha256":"d848a800b8f24ee4d4caf62396984c6237943511c9757aad59e78957b2dc8607"},{"filename":"scripts/gates/a06-claim-etiquette-required.sh","content":"#!/usr/bin/env bash\n# Catalog: A6 — Repo requires claim-etiquette comment before working\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_DOSSIER_PATH\" || ! -f \"$GATE_DOSSIER_PATH\" ]]; then\n gate_skip \"no dossier\"\nfi\n\nREQUIRED=$(fm_field \"$GATE_DOSSIER_PATH\" \"etiquette_comment_required\")\nif [[ \"$REQUIRED\" != \"true\" ]]; then\n gate_skip \"etiquette_comment_required is not true\"\nfi\n\nISSUE_NUM=$(fm_field \"$GATE_CANDIDATE_PATH\" \"issue_number\")\nif [[ -z \"$ISSUE_NUM\" || -z \"$GATE_REPO\" ]]; then\n gate_skip \"no issue_number or repo in candidate\"\nfi\n\nLOGIN=$(gh_safe api user --jq .login || /usr/bin/echo \"\")\nif [[ -z \"$LOGIN\" ]]; then\n gate_inform \"could not resolve gh user login\"\nfi\n\nCOMMENTS=$(gh_safe issue view \"$ISSUE_NUM\" --repo \"$GATE_REPO\" --json comments --jq \"[.comments[] | select(.author.login == \\\"$LOGIN\\\") | .body] | join(\\\"\\n---\\n\\\")\" || /usr/bin/echo \"\")\n\nif [[ -z \"$COMMENTS\" ]]; then\n gate_block \"no claim comment from $LOGIN found on issue #$ISSUE_NUM\" \"this repo wants you to comment on the issue before working on it; post a claim comment and re-run the transition\"\nfi\n\nif /usr/bin/printf '%s' \"$COMMENTS\" | /usr/bin/grep -qiE \"(i'?d like to take this|i'?ll take this|happy to take this|working on this|claiming this|would like to work on|let me know if i can take|i'?d be happy to work)\"; then\n gate_pass \"found claim-shaped comment from $LOGIN\"\nfi\n\ngate_block \"no claim-shaped comment from $LOGIN on issue #$ISSUE_NUM\" \"this repo wants you to comment on the issue before working on it; post a claim comment and re-run the transition\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1589,"content_sha256":"e86c1d3d822585ac04bf49b5fd2390187cd90e97042bd45e981bcd22decf2ffe"},{"filename":"scripts/gates/a09-mention-routing.sh","content":"#!/usr/bin/env bash\n# Catalog: A9 — @-mentions in claim/PR text when CODEOWNERS routing exists\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_DOSSIER_PATH\" || ! -f \"$GATE_DOSSIER_PATH\" ]]; then\n gate_skip \"no dossier\"\nfi\n\n# Check for CODEOWNERS in dossier — frontmatter policy_files OR Policy file inventory section\nHAS_CODEOWNERS=\"\"\nPOLICY_FM=$(fm_field \"$GATE_DOSSIER_PATH\" \"policy_files\")\nif /usr/bin/printf '%s' \"$POLICY_FM\" | /usr/bin/grep -qi \"CODEOWNERS\"; then\n HAS_CODEOWNERS=1\nfi\nif [[ -z \"$HAS_CODEOWNERS\" ]]; then\n if /usr/bin/awk '/^## Policy file inventory/{flag=1;next} /^## /{flag=0} flag' \"$GATE_DOSSIER_PATH\" 2>/dev/null | /usr/bin/grep -qi \"\\*\\*CODEOWNERS\\*\\*\"; then\n HAS_CODEOWNERS=1\n fi\nfi\n\nif [[ -z \"$HAS_CODEOWNERS\" ]]; then\n gate_skip \"no CODEOWNERS in dossier policy inventory\"\nfi\n\n# Pick the right draft section based on action\nSECTION=\"\"\ncase \"$GATE_ACTION\" in\n *post-comment*|*claim*) SECTION=\"## Claim comment draft\" ;;\n *open-pr*|*pr*) SECTION=\"## PR body\" ;;\n *) SECTION=\"## Claim comment draft\" ;;\nesac\n\nDRAFT=$(/usr/bin/awk -v s=\"$SECTION\" 'BEGIN{flag=0} $0==s{flag=1;next} /^## /{flag=0} flag' \"$GATE_CANDIDATE_PATH\" 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ -z \"$DRAFT\" ]]; then\n gate_skip \"no draft section found in candidate\"\nfi\n\n# Count @-mentions, excluding @me, @everyone, @channel.\n# Use awk (single pass, no pipeline-failure surface) instead of a chained\n# grep | grep | wc — under set -uo pipefail, an empty `grep -oE` returns\n# exit 1, killing the whole pipeline, even though \"no mentions\" is the\n# correct/expected answer for most drafts.\nMENTIONS=$(/usr/bin/printf '%s' \"$DRAFT\" | /usr/bin/awk '\n {\n for (i = 1; i \u003c= NF; i++) {\n tok = $i\n gsub(/[,.;:!?)\\]\"\\x27]+$/, \"\", tok)\n if (tok ~ /^@[a-zA-Z0-9-]{2,}$/ && tok !~ /^@(me|everyone|channel)$/) {\n c++\n }\n }\n }\n END { print c + 0 }\n')\n\nif [[ \"${MENTIONS:-0}\" -ge 1 ]]; then\n gate_warn \"$MENTIONS @-mention(s) in draft despite CODEOWNERS routing\" \"this repo uses CODEOWNERS; explicit @-mentions are usually noise. Drop them unless you have a specific reason to ping someone.\"\nfi\n\ngate_pass \"no @-mentions in draft\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2191,"content_sha256":"0e4b506121faf4ad37fa794e1f6b922e11e85dbad6d34708a0bbe4e1901fc4c1"},{"filename":"scripts/gates/b01-base-branch.sh","content":"#!/usr/bin/env bash\n# Catalog: B1 — Local branch is based on a non-default upstream branch\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_DOSSIER_PATH\" || ! -f \"$GATE_DOSSIER_PATH\" ]]; then\n gate_skip \"no dossier — cannot determine default_branch\"\nfi\n\nDEFAULT_BRANCH=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\nif [[ -z \"$DEFAULT_BRANCH\" ]]; then\n gate_skip \"no default_branch in dossier\"\nfi\n\nREPO_NAME=\"${GATE_REPO##*/}\"\nCLONE_DIR=\"$HOME/000-projects/contributing-clanker/$REPO_NAME\"\n\nif [[ ! -d \"$CLONE_DIR/.git\" ]]; then\n gate_skip \"no local clone at $CLONE_DIR\"\nfi\n\ncd \"$CLONE_DIR\" || gate_skip \"cannot cd to clone dir\"\n\nif /usr/bin/git merge-base --is-ancestor \"origin/$DEFAULT_BRANCH\" HEAD 2>/dev/null; then\n gate_pass \"HEAD descends from origin/$DEFAULT_BRANCH\"\nfi\n\ngate_block \"HEAD is not a descendant of origin/$DEFAULT_BRANCH\" \"rebase onto the default branch: git fetch origin $DEFAULT_BRANCH && git rebase origin/$DEFAULT_BRANCH\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":983,"content_sha256":"4f011e66b5cbb4b92a1e6502d2db2288b68fdc7efe59d02f30448b2ed1132cc5"},{"filename":"scripts/gates/b02-branch-naming.sh","content":"#!/usr/bin/env bash\n# Catalog: B2 — Local branch name violates project's branch_convention regex\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_DOSSIER_PATH\" || ! -f \"$GATE_DOSSIER_PATH\" ]]; then\n gate_skip \"no dossier\"\nfi\n\nCONVENTION=$(fm_field \"$GATE_DOSSIER_PATH\" \"branch_convention\")\nif [[ -z \"$CONVENTION\" ]]; then\n gate_skip \"no branch_convention in dossier\"\nfi\n\nREPO_NAME=\"${GATE_REPO##*/}\"\nCLONE_DIR=\"$HOME/000-projects/contributing-clanker/$REPO_NAME\"\n\nif [[ ! -d \"$CLONE_DIR/.git\" ]]; then\n gate_skip \"no local clone at $CLONE_DIR\"\nfi\n\ncd \"$CLONE_DIR\" || gate_skip \"cannot cd to clone dir\"\n\nBRANCH=$(/usr/bin/git rev-parse --abbrev-ref HEAD 2>/dev/null || /usr/bin/echo \"\")\nif [[ -z \"$BRANCH\" || \"$BRANCH\" == \"HEAD\" ]]; then\n gate_skip \"detached HEAD or unknown branch\"\nfi\n\nif /usr/bin/printf '%s' \"$BRANCH\" | /usr/bin/grep -qE \"$CONVENTION\"; then\n gate_pass \"branch '$BRANCH' matches convention\"\nfi\n\ngate_block \"branch '$BRANCH' does not match convention /$CONVENTION/\" \"rename the branch: git branch -m \u003cnew-name-matching-convention>\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1082,"content_sha256":"f3fff5c5a9f55ad1d58932d03e49675032772b80a221daa9156a295531f2ec2f"},{"filename":"scripts/gates/b03-clone-fresh.sh","content":"#!/usr/bin/env bash\n# Catalog: B3 — Local clone is stale vs origin/\u003cdefault_branch>\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_DOSSIER_PATH\" || ! -f \"$GATE_DOSSIER_PATH\" ]]; then\n gate_skip \"no dossier\"\nfi\n\nDEFAULT_BRANCH=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\nif [[ -z \"$DEFAULT_BRANCH\" ]]; then\n gate_skip \"no default_branch in dossier\"\nfi\n\nREPO_NAME=\"${GATE_REPO##*/}\"\nCLONE_DIR=\"$HOME/000-projects/contributing-clanker/$REPO_NAME\"\n\nif [[ ! -d \"$CLONE_DIR/.git\" ]]; then\n gate_skip \"no local clone at $CLONE_DIR\"\nfi\n\ncd \"$CLONE_DIR\" || gate_skip \"cannot cd to clone dir\"\n\n/usr/bin/timeout 30 /usr/bin/git fetch origin \"$DEFAULT_BRANCH\" --quiet 2>/dev/null || true\n\nBEHIND=$(/usr/bin/git rev-list --count \"HEAD..origin/$DEFAULT_BRANCH\" 2>/dev/null || /usr/bin/echo \"0\")\n\nif [[ \"$BEHIND\" -gt 100 ]]; then\n gate_warn \"local clone is $BEHIND commits behind origin/$DEFAULT_BRANCH\" \"rebase: git pull --rebase origin $DEFAULT_BRANCH\"\nfi\n\ngate_pass \"clone is current ($BEHIND commits behind origin/$DEFAULT_BRANCH)\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1061,"content_sha256":"e2278e7c501ed0eac248823311f7c1f8b68c3483db4a1687177f972a183ea7f6"},{"filename":"scripts/gates/b05-dco-signoff.sh","content":"#!/usr/bin/env bash\n# Catalog: B5 — Commits missing Signed-off-by when dossier requires DCO\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_DOSSIER_PATH\" || ! -f \"$GATE_DOSSIER_PATH\" ]]; then\n gate_skip \"no dossier\"\nfi\n\nDCO=$(fm_field \"$GATE_DOSSIER_PATH\" \"dco_required\")\nif [[ \"$DCO\" != \"true\" ]]; then\n gate_skip \"dossier does not require DCO\"\nfi\n\nDEFAULT_BRANCH=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\nif [[ -z \"$DEFAULT_BRANCH\" ]]; then\n gate_skip \"no default_branch in dossier\"\nfi\n\nREPO_NAME=\"${GATE_REPO##*/}\"\nCLONE_DIR=\"$HOME/000-projects/contributing-clanker/$REPO_NAME\"\n\nif [[ ! -d \"$CLONE_DIR/.git\" ]]; then\n gate_skip \"no local clone at $CLONE_DIR\"\nfi\n\ncd \"$CLONE_DIR\" || gate_skip \"cannot cd to clone dir\"\n\n# Iterate per-commit so we can flag exactly which sha is missing the trailer.\nMISSING=\"\"\nCOMMITS=$(/usr/bin/git rev-list \"origin/$DEFAULT_BRANCH..HEAD\" 2>/dev/null || /usr/bin/echo \"\")\nif [[ -z \"$COMMITS\" ]]; then\n gate_pass \"no new commits to check\"\nfi\n\nwhile read -r sha; do\n [[ -z \"$sha\" ]] && continue\n BODY=$(/usr/bin/git log -1 --format=%B \"$sha\" 2>/dev/null || /usr/bin/echo \"\")\n if ! /usr/bin/printf '%s' \"$BODY\" | /usr/bin/grep -q \"^Signed-off-by:\"; then\n MISSING=\"${MISSING}${sha:0:8} \"\n fi\ndone \u003c\u003c\u003c \"$COMMITS\"\n\nif [[ -n \"$MISSING\" ]]; then\n COUNT=$(/usr/bin/printf '%s' \"$COMMITS\" | /usr/bin/grep -c .)\n gate_block \"commits missing Signed-off-by: $MISSING\" \"git commit --amend -s OR sign all commits: git rebase HEAD~$COUNT --signoff\"\nfi\n\ngate_pass \"all commits have Signed-off-by trailer\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1573,"content_sha256":"61d66660726195f5033a54ba473454838f71c1942fe3d650b5caeee4ce5132ae"},{"filename":"scripts/gates/b06-commit-format.sh","content":"#!/usr/bin/env bash\n# Catalog: B6 — Commit subjects violate Conventional Commits when required\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_DOSSIER_PATH\" || ! -f \"$GATE_DOSSIER_PATH\" ]]; then\n gate_skip \"no dossier\"\nfi\n\nCC=$(fm_field \"$GATE_DOSSIER_PATH\" \"conventional_commits\")\nif [[ \"$CC\" != \"true\" ]]; then\n gate_skip \"dossier does not require Conventional Commits\"\nfi\n\nDEFAULT_BRANCH=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\nif [[ -z \"$DEFAULT_BRANCH\" ]]; then\n gate_skip \"no default_branch in dossier\"\nfi\n\nREPO_NAME=\"${GATE_REPO##*/}\"\nCLONE_DIR=\"$HOME/000-projects/contributing-clanker/$REPO_NAME\"\n\nif [[ ! -d \"$CLONE_DIR/.git\" ]]; then\n gate_skip \"no local clone at $CLONE_DIR\"\nfi\n\ncd \"$CLONE_DIR\" || gate_skip \"cannot cd to clone dir\"\n\nCC_REGEX='^[a-z]+(\\([a-z0-9._/-]+\\))?!?: .+'\nFAILING=\"\"\n\nwhile IFS= read -r subj; do\n [[ -z \"$subj\" ]] && continue\n if ! /usr/bin/printf '%s' \"$subj\" | /usr/bin/grep -qE \"$CC_REGEX\"; then\n FAILING=\"${FAILING} - ${subj}\\n\"\n fi\ndone \u003c \u003c(/usr/bin/git log \"origin/$DEFAULT_BRANCH..HEAD\" --format=%s 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ -n \"$FAILING\" ]]; then\n gate_block \"commit subjects not Conventional Commits compliant\" \"fix subjects via git commit --amend or git rebase -i. Failing: $(/usr/bin/printf \"$FAILING\" | /usr/bin/tr '\\n' '|')\"\nfi\n\ngate_pass \"all commit subjects match Conventional Commits\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1400,"content_sha256":"c7092027b2509fcf8b08e4195571f76a34dbf96f6a5e04a923a3f3df5d1bdff9"},{"filename":"scripts/gates/b07-scope-files.sh","content":"#!/usr/bin/env bash\n# Catalog: B7 — Local diff touches files outside candidate's claimed scope\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_CANDIDATE_PATH\" || ! -f \"$GATE_CANDIDATE_PATH\" ]]; then\n gate_skip \"no candidate file\"\nfi\n\n# Extract scope section from candidate body. Recognize \"## Scope\" or\n# \"## Files to touch\" (case-insensitive on the heading word).\nSCOPE_BLOCK=$(/usr/bin/awk '\n BEGIN{flag=0}\n /^## [Ss]cope/ {flag=1; next}\n /^## [Ff]iles to touch/ {flag=1; next}\n /^## / {flag=0}\n flag {print}\n' \"$GATE_CANDIDATE_PATH\" 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ -z \"$SCOPE_BLOCK\" ]]; then\n gate_skip \"no '## Scope' or '## Files to touch' section in candidate\"\nfi\n\n# Parse paths: strip blank lines, comments, list markers.\nSCOPE_FILES=$(/usr/bin/printf '%s\\n' \"$SCOPE_BLOCK\" \\\n | /usr/bin/sed -E 's/^[[:space:]]*[-*][[:space:]]+//; s/^[[:space:]]+//; s/[[:space:]]+$//' \\\n | /usr/bin/grep -vE '^(#|$)' \\\n | /usr/bin/sed -E 's/^`(.+)`$/\\1/')\n\nif [[ -z \"$SCOPE_FILES\" ]]; then\n gate_skip \"scope section present but empty\"\nfi\n\nREPO_NAME=\"${GATE_REPO##*/}\"\nCLONE_DIR=\"$HOME/000-projects/contributing-clanker/$REPO_NAME\"\n\nif [[ ! -d \"$CLONE_DIR/.git\" ]]; then\n gate_skip \"no local clone at $CLONE_DIR\"\nfi\n\ncd \"$CLONE_DIR\" || gate_skip \"cannot cd to clone dir\"\n\nDEFAULT_BRANCH=\"\"\nif [[ -n \"$GATE_DOSSIER_PATH\" && -f \"$GATE_DOSSIER_PATH\" ]]; then\n DEFAULT_BRANCH=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\nfi\n[[ -z \"$DEFAULT_BRANCH\" ]] && DEFAULT_BRANCH=\"main\"\n\nCHANGED=$(/usr/bin/git diff \"origin/$DEFAULT_BRANCH..HEAD\" --name-only 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ -z \"$CHANGED\" ]]; then\n gate_pass \"no changed files\"\nfi\n\nDIVERGENT=\"\"\nwhile IFS= read -r f; do\n [[ -z \"$f\" ]] && continue\n if ! /usr/bin/printf '%s\\n' \"$SCOPE_FILES\" | /usr/bin/grep -Fxq \"$f\"; then\n DIVERGENT=\"${DIVERGENT}${f} \"\n fi\ndone \u003c\u003c\u003c \"$CHANGED\"\n\nif [[ -n \"$DIVERGENT\" ]]; then\n gate_warn \"diff touches files outside scope: $DIVERGENT\" \"either expand the candidate's scope section or revert the out-of-scope edits\"\nfi\n\ngate_pass \"all changed files are within declared scope\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2118,"content_sha256":"0bc35d305d312ebe13ee725319a4877f87fd658366061a34881d5c2010c45fbf"},{"filename":"scripts/gates/b12-new-deps.sh","content":"#!/usr/bin/env bash\n# Catalog: B12 — diff introduces new dependencies without prior issue conversation\n# Mitigates: maintainers strongly prefer dep additions to be discussed first.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nREPO_NAME=\"${GATE_REPO##*/}\"\nCLONE=\"$HOME/000-projects/contributing-clanker/$REPO_NAME\"\n\nif [[ ! -d \"$CLONE/.git\" ]]; then\n gate_skip \"no local clone at $CLONE\"\nfi\n\nDEFAULT_BRANCH=\"\"\nif [[ -n \"$GATE_DOSSIER_PATH\" && -f \"$GATE_DOSSIER_PATH\" ]]; then\n DEFAULT_BRANCH=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\nfi\n[[ -z \"$DEFAULT_BRANCH\" ]] && DEFAULT_BRANCH=\"main\"\n\nDIFF=$(/usr/bin/git -C \"$CLONE\" diff \"$DEFAULT_BRANCH..HEAD\" -- package.json Cargo.toml pyproject.toml requirements.txt go.mod 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ -z \"$DIFF\" ]]; then\n gate_pass \"no manifest files changed\"\nfi\n\n# Heuristic: look for added dep lines across the supported manifest formats.\nNEW_DEPS=$(/usr/bin/printf '%s\\n' \"$DIFF\" \\\n | /usr/bin/grep -E '^\\+([[:space:]]*\"[A-Za-z0-9_@/.-]+\"[[:space:]]*:[[:space:]]*\"[^\"]+\",?$|[[:space:]]*[a-zA-Z][A-Za-z0-9_-]*[[:space:]]*=[[:space:]]*\".*\"$|[a-zA-Z][A-Za-z0-9_.-]*([\u003c>=!~].*)?$)' \\\n | /usr/bin/grep -vE '^\\+\\+\\+|^\\+[[:space:]]*#|^\\+[[:space:]]*\"version\"' \\\n | /usr/bin/sed -E 's/^\\+[[:space:]]*//; s/[[:space:]]*=.*$//; s/^\"//; s/\".*$//; s/[\u003c>=!~].*$//' \\\n | /usr/bin/grep -v '^[[:space:]]*

Contribute Command Center Overview Local-only OSS contribution workflow. The skill itself is the system — there is no separate CLI binary, dashboard, or cloud backend. State lives in three places: 1. GitHub itself — fetched live via for any PR/issue state question. Never cached long-term. 2. Markdown candidate files at — one per issue we're tracking. Frontmatter is the queryable layer (status, scout score, repo, research path, overrides). Body holds claim drafts, PR drafts, scope notes. 3. Markdown repo dossiers at — one per upstream repo we contribute to. Built by the subagent. Frontmatter i…

\\\n || /usr/bin/echo \"\")\n\nif [[ -z \"$NEW_DEPS\" ]]; then\n gate_pass \"no new dependency entries detected in manifest diffs\"\nfi\n\nLIST=$(/usr/bin/printf '%s' \"$NEW_DEPS\" | /usr/bin/tr '\\n' ',' | /usr/bin/sed 's/,$//')\ngate_warn \"diff appears to add new dependencies: $LIST\" \"new dependencies usually warrant an issue conversation first; check the issue thread for prior discussion\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1759,"content_sha256":"b28b3427d218554555061f1aa46e0f4fd245d97ed39b92a4c36a02d489426f42"},{"filename":"scripts/gates/b14-local-checks.sh","content":"#!/usr/bin/env bash\n# Catalog: B14 — No recent green local check-run for current branch\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_DOSSIER_PATH\" || ! -f \"$GATE_DOSSIER_PATH\" ]]; then\n gate_skip \"no dossier\"\nfi\n\nCMD=$(fm_field \"$GATE_DOSSIER_PATH\" \"local_check_command\")\nif [[ -z \"$CMD\" || \"$CMD\" == \"(not detected)\" ]]; then\n gate_skip \"no local_check_command in dossier\"\nfi\n\nBRANCH=$(fm_field \"$GATE_CANDIDATE_PATH\" \"branch\")\nif [[ -z \"$BRANCH\" ]]; then\n gate_skip \"no branch in candidate frontmatter\"\nfi\n\nREPO_SLUG=$(/usr/bin/printf '%s' \"$GATE_REPO\" | /usr/bin/tr '/' '_' | /usr/bin/tr '/' '_')\n# tr collapses single chars; doubled to satisfy the convention \"owner/repo -> owner__repo\"\nREPO_SLUG=$(/usr/bin/printf '%s' \"$GATE_REPO\" | /usr/bin/sed 's|/|__|g')\n\nEVIDENCE=\"$HOME/.contribute-system/check-runs/${REPO_SLUG}__${BRANCH}.json\"\n\nif [[ ! -f \"$EVIDENCE\" ]]; then\n gate_block \"no local check evidence for $BRANCH\" \"run the project's check command: $CMD\"\nfi\n\nPASSED=$(jq -r '.passed // false' \"$EVIDENCE\" 2>/dev/null || /usr/bin/echo \"false\")\nTS=$(jq -r '.ts // \"\"' \"$EVIDENCE\" 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ \"$PASSED\" != \"true\" ]]; then\n gate_block \"last local check run did not pass\" \"re-run after fixing: $CMD\"\nfi\n\nif [[ -z \"$TS\" ]]; then\n gate_warn \"evidence file lacks timestamp\" \"re-run: $CMD\"\nfi\n\n# Compare ts to now; warn if older than 1h.\nNOW_EPOCH=$(/usr/bin/date -u +%s)\nTS_EPOCH=$(/usr/bin/date -u -d \"$TS\" +%s 2>/dev/null || /usr/bin/echo \"0\")\n\nif [[ \"$TS_EPOCH\" -eq 0 ]]; then\n gate_warn \"could not parse evidence ts=$TS\" \"re-run: $CMD\"\nfi\n\nAGE=$(( NOW_EPOCH - TS_EPOCH ))\nif [[ \"$AGE\" -gt 3600 ]]; then\n gate_warn \"last green check is $((AGE / 60))min old (>1h)\" \"re-run: $CMD\"\nfi\n\ngate_pass \"local checks passed at $TS\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1794,"content_sha256":"f2c04535919b7a2b385433ffa783575085a570f16a77aaa1be569a4b4537eebe"},{"filename":"scripts/gates/b16-local-check-allowlist.sh","content":"#!/usr/bin/env bash\n# Catalog: B16 — Dossier injection via local_check_command\n# Mitigates: Security review GAP #1 — `local_check_command:` is free-text from\n# upstream CONTRIBUTING.md. Malicious repo could inject `make test; rm -rf ~`.\n# Briefing surfaces the recommendation, gate B14 may exec it. This gate refuses\n# to proceed if the dossier's command doesn't match the safe-runner allowlist.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_DOSSIER_PATH\" || ! -f \"$GATE_DOSSIER_PATH\" ]]; then\n gate_skip \"no dossier — no command to validate\"\nfi\n\nCMD=$(fm_field \"$GATE_DOSSIER_PATH\" \"local_check_command\")\nif [[ -z \"$CMD\" || \"$CMD\" == \"(not detected)\" ]]; then\n gate_pass \"no local_check_command in dossier (nothing to validate)\"\nfi\n\n# Allowlist: known-safe runners + their typical args. Composed commands (&&, ||,\n# ;, |, $(), backticks) are denied unconditionally — they're the injection vector.\nif /usr/bin/printf '%s' \"$CMD\" | /usr/bin/grep -qE '[;|`$(){}\u003c>]|&&|\\|\\|'; then\n gate_block \"dossier local_check_command contains shell metacharacters: $CMD\" \"researcher-build.sh extracted this from upstream CONTRIBUTING.md. Treat as untrusted. Manually verify the command and edit the dossier; rerun this gate.\"\nfi\n\n# Allowlist regex: starts with a known-safe runner + simple alphanum args.\nALLOW_REGEX='^(make|pnpm|npm|yarn|cargo|pytest|python|python3|go|sbt|mix|dotnet|uv|hatch|tox|just|task|bundle|ruby) [a-zA-Z0-9 _:.,/\\-]+

Contribute Command Center Overview Local-only OSS contribution workflow. The skill itself is the system — there is no separate CLI binary, dashboard, or cloud backend. State lives in three places: 1. GitHub itself — fetched live via for any PR/issue state question. Never cached long-term. 2. Markdown candidate files at — one per issue we're tracking. Frontmatter is the queryable layer (status, scout score, repo, research path, overrides). Body holds claim drafts, PR drafts, scope notes. 3. Markdown repo dossiers at — one per upstream repo we contribute to. Built by the subagent. Frontmatter i…

\nif ! /usr/bin/printf '%s' \"$CMD\" | /usr/bin/grep -qE \"$ALLOW_REGEX\" ; then\n gate_block \"dossier local_check_command not in allowlist: $CMD\" \"expected pattern: \u003cmake|pnpm|npm|yarn|cargo|pytest|...> followed by alphanumeric args. Edit the dossier to match the actual safe command, or override if you've manually verified.\"\nfi\n\ngate_pass \"local_check_command matches safe-runner allowlist\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1857,"content_sha256":"d119ca29318a9f4b94d9ad4e103102edd0ccbe9285f1b0c4cfae2441141efa7f"},{"filename":"scripts/gates/c01-draft-first.sh","content":"#!/usr/bin/env bash\n# Catalog: C1 — Repo prefers draft PRs first; opening non-draft is anti-pattern\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\n# Only relevant if dossier explicitly says draft_first: true\nDRAFT_FIRST=$(fm_field \"$GATE_DOSSIER_PATH\" \"draft_first\")\nif [[ \"$DRAFT_FIRST\" != \"true\" ]]; then\n gate_skip \"dossier does not require draft-first PRs\"\nfi\n\n# Candidate's `draft:` field is set by the writer subagent (Slice 3)\nDRAFT=$(fm_field \"$GATE_CANDIDATE_PATH\" \"draft\")\nif [[ -z \"$DRAFT\" ]]; then\n gate_skip \"draft preference unknown — fill in candidate's \\`draft:\\` field before opening\"\nfi\n\nif [[ \"$DRAFT\" == \"false\" ]]; then\n gate_block \"this repo prefers draft PRs first but candidate has draft: false\" \"this repo prefers draft PRs first; pass --draft to gh pr create OR set draft: true in the candidate\"\nfi\n\ngate_pass \"candidate set to open as draft (draft: true)\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":902,"content_sha256":"9e8f0dd0c4441f534e2cb0b52ee476c37db53354a3ee167b998dfb1a72b1c928"},{"filename":"scripts/gates/c02-pr-title-format.sh","content":"#!/usr/bin/env bash\n# Catalog: C2 — PR title violates repo-required regex\n# Mitigates: maintainer immediately requests rename, signals \"didn't read CONTRIBUTING\".\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_DOSSIER_PATH\" || ! -f \"$GATE_DOSSIER_PATH\" ]]; then\n gate_skip \"no dossier — cannot determine pr_title_regex\"\nfi\n\nREGEX=$(fm_field \"$GATE_DOSSIER_PATH\" \"pr_title_regex\")\nif [[ -z \"$REGEX\" ]]; then\n gate_skip \"dossier has no pr_title_regex\"\nfi\n\n# Try frontmatter first\nTITLE=$(fm_field \"$GATE_CANDIDATE_PATH\" \"pr_title\")\n\n# Fall back to ## PR title section (single line content)\nif [[ -z \"$TITLE\" ]]; then\n TITLE=$(/usr/bin/awk '/^## PR title/{flag=1;next} /^## /{flag=0} flag' \"$GATE_CANDIDATE_PATH\" 2>/dev/null \\\n | /usr/bin/grep -m1 -v '^[[:space:]]*

Contribute Command Center Overview Local-only OSS contribution workflow. The skill itself is the system — there is no separate CLI binary, dashboard, or cloud backend. State lives in three places: 1. GitHub itself — fetched live via for any PR/issue state question. Never cached long-term. 2. Markdown candidate files at — one per issue we're tracking. Frontmatter is the queryable layer (status, scout score, repo, research path, overrides). Body holds claim drafts, PR drafts, scope notes. 3. Markdown repo dossiers at — one per upstream repo we contribute to. Built by the subagent. Frontmatter i…

\\\n | /usr/bin/sed 's/^[[:space:]]*//;s/[[:space:]]*$//' \\\n || /usr/bin/echo \"\")\nfi\n\nif [[ -z \"$TITLE\" ]]; then\n gate_skip \"no PR title in candidate (frontmatter or ## PR title section)\"\nfi\n\nif /usr/bin/printf '%s' \"$TITLE\" | /usr/bin/grep -qE \"$REGEX\"; then\n gate_pass \"PR title matches required regex\"\nfi\n\ngate_block \"PR title '$TITLE' does not match required regex /$REGEX/\" \"rename PR title to match /$REGEX/ per CONTRIBUTING.md\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1244,"content_sha256":"5a4618aa7f6f685b363b1b131a59694b9796f9ae578565c61391e4e6d31dd482"},{"filename":"scripts/gates/c03-pr-body-sections.sh","content":"#!/usr/bin/env bash\n# Catalog: C3 — PR body draft missing sections required by the repo's PR template\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\n# Pull the raw frontmatter line for pr_template_required_sections (a YAML array)\nRAW=$(/usr/bin/awk '\n /^---$/ { fm = !fm ? 1 : 2; next }\n fm == 1 && /^pr_template_required_sections:/ {\n sub(/^pr_template_required_sections:[[:space:]]*/, \"\")\n print\n exit\n }\n' \"$GATE_DOSSIER_PATH\" 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ -z \"$RAW\" || \"$RAW\" == \"[]\" ]]; then\n gate_skip \"no pr_template_required_sections in dossier\"\nfi\n\n# Strip surrounding [ ], split on commas, strip quotes/whitespace per item\nINNER=$(/usr/bin/printf '%s' \"$RAW\" | /usr/bin/sed -E 's/^\\[//; s/\\]$//')\nif [[ -z \"$INNER\" ]]; then\n gate_skip \"pr_template_required_sections is empty\"\nfi\n\n# Build array of section names\ndeclare -a SECTIONS=()\nIFS=',' read -ra RAW_PARTS \u003c\u003c\u003c \"$INNER\"\nfor part in \"${RAW_PARTS[@]}\"; do\n clean=$(/usr/bin/printf '%s' \"$part\" | /usr/bin/sed -E 's/^[[:space:]]*\"?//; s/\"?[[:space:]]*$//')\n [[ -n \"$clean\" ]] && SECTIONS+=(\"$clean\")\ndone\n\nif (( ${#SECTIONS[@]} == 0 )); then\n gate_skip \"no parseable section names in pr_template_required_sections\"\nfi\n\n# Extract candidate's `## PR body` section\nPR_BODY=$(/usr/bin/awk '/^## PR body/{flag=1;next} /^## /{flag=0} flag' \"$GATE_CANDIDATE_PATH\" 2>/dev/null || /usr/bin/echo \"\")\nif [[ -z \"$PR_BODY\" ]]; then\n gate_skip \"no '## PR body' section in candidate yet\"\nfi\n\n# Check each required section name appears as a markdown header (## or ###), case-insensitive\ndeclare -a MISSING=()\nfor name in \"${SECTIONS[@]}\"; do\n if ! /usr/bin/printf '%s' \"$PR_BODY\" | /usr/bin/grep -qiE \"^#{2,3}[[:space:]]+${name}[[:space:]]*$\"; then\n MISSING+=(\"$name\")\n fi\ndone\n\nif (( ${#MISSING[@]} > 0 )); then\n joined=$(IFS=', '; /usr/bin/printf '%s' \"${MISSING[*]}\")\n gate_block \"PR body missing required sections: $joined\" \"add the section headers (## $joined) to your PR body draft\"\nfi\n\ngate_pass \"all required PR body sections present\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2037,"content_sha256":"142103a2a92d376c87fac38bd2f3764eab23d650bea11d656f7e481cff14e429"},{"filename":"scripts/gates/c04-ui-screenshots.sh","content":"#!/usr/bin/env bash\n# Catalog: C4 — PR touches UI files but PR body has no screenshot / recording\n# Mitigates: maintainer asks \"any screenshots?\" → reads as not-quite-ready submission.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nREPO_NAME=\"${GATE_REPO##*/}\"\nCLONE=\"$HOME/000-projects/contributing-clanker/$REPO_NAME\"\n\nif [[ ! -d \"$CLONE/.git\" ]]; then\n gate_skip \"no local clone at $CLONE\"\nfi\n\nDEFAULT_BRANCH=\"\"\nif [[ -n \"$GATE_DOSSIER_PATH\" && -f \"$GATE_DOSSIER_PATH\" ]]; then\n DEFAULT_BRANCH=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\nfi\n[[ -z \"$DEFAULT_BRANCH\" ]] && DEFAULT_BRANCH=\"main\"\n\nCHANGED=$(/usr/bin/git -C \"$CLONE\" diff \"$DEFAULT_BRANCH..HEAD\" --name-only 2>/dev/null || /usr/bin/echo \"\")\nUI_FILES=$(/usr/bin/printf '%s\\n' \"$CHANGED\" | /usr/bin/grep -E '\\.(tsx|jsx|vue|svelte|html|css|scss)

Contribute Command Center Overview Local-only OSS contribution workflow. The skill itself is the system — there is no separate CLI binary, dashboard, or cloud backend. State lives in three places: 1. GitHub itself — fetched live via for any PR/issue state question. Never cached long-term. 2. Markdown candidate files at — one per issue we're tracking. Frontmatter is the queryable layer (status, scout score, repo, research path, overrides). Body holds claim drafts, PR drafts, scope notes. 3. Markdown repo dossiers at — one per upstream repo we contribute to. Built by the subagent. Frontmatter i…

|| /usr/bin/echo \"\")\n\nif [[ -z \"$UI_FILES\" ]]; then\n gate_pass \"no UI files touched in diff\"\nfi\n\nPR_BODY=$(/usr/bin/awk '/^## PR body/{flag=1;next} /^## /{flag=0} flag' \"$GATE_CANDIDATE_PATH\" 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ -z \"$PR_BODY\" ]]; then\n gate_inform \"UI files changed but no PR body drafted yet — remember to attach a screenshot or recording\"\nfi\n\nif /usr/bin/printf '%s' \"$PR_BODY\" | /usr/bin/grep -qiE '!\\[.*\\]\\(.*\\)|screenshot|screen recording|\\bgif\\b|\\.mp4|\\.mov|\\.webm|loom\\.com|youtu\\.be|youtube\\.com'; then\n gate_pass \"PR body references screenshot or recording\"\nfi\n\ngate_block \"PR touches UI files but PR body has no screenshot or recording\" \"PRs touching UI need a screenshot or recording — paste an image (![alt](url)) or a Loom/GIF link in the PR body\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1622,"content_sha256":"58b2f07a7fa79e8a85b190098f6943fc88ab488a0907ed2bcfa80e61d4fa8840"},{"filename":"scripts/gates/c05-test-evidence.sh","content":"#!/usr/bin/env bash\n# Catalog: C5 — PR body lacks fenced test-runner output (evidence of local verification)\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\n# Extract candidate's `## PR body` section\nPR_BODY=$(/usr/bin/awk '/^## PR body/{flag=1;next} /^## /{flag=0} flag' \"$GATE_CANDIDATE_PATH\" 2>/dev/null || /usr/bin/echo \"\")\nif [[ -z \"$PR_BODY\" ]]; then\n gate_skip \"no '## PR body' section in candidate yet\"\nfi\n\n# Walk fenced blocks (```...```) and check each for test-runner signals.\n# Use awk to flip flag on ``` lines and collect block contents, separated by NUL-equivalent marker.\nBLOCKS=$(/usr/bin/printf '%s' \"$PR_BODY\" | /usr/bin/awk '\n /^```/ { in_block = !in_block; if (!in_block) print \"\u003c\u003c\u003cBLOCK_END>>>\"; next }\n in_block { print }\n')\n\nif [[ -z \"$BLOCKS\" ]]; then\n gate_block \"PR body has no fenced code blocks\" \"paste your local test output as a fenced code block inside the PR body\"\nfi\n\n# Test runner regexes — any one match in any block is sufficient\nPATTERNS='pytest|cargo test|make test|pnpm test|npm test|yarn test|sbt test|go test|Test Suites:|[0-9]+ passed|[0-9]+ passing|[0-9]+ failed|^ok [0-9]+|PASS|FAIL'\n\nif /usr/bin/printf '%s' \"$BLOCKS\" | /usr/bin/grep -qE \"$PATTERNS\"; then\n gate_pass \"PR body contains fenced block with test-runner output\"\nfi\n\ngate_block \"no fenced code block in PR body looks like test runner output\" \"paste your local test output as a fenced code block inside the PR body\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1441,"content_sha256":"744d4266503bd5e8532cd7a36207671f420f57ac58d7b7c3bd2b0a04c9a6435e"},{"filename":"scripts/gates/c07-coauthor-banned.sh","content":"#!/usr/bin/env bash\n# Catalog: C7 — `Co-Authored-By: Claude` trailer present in commits the repo has banned it\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\n# Only run if dossier flags this repo as forbidding the Claude co-author trailer\nFORBIDDEN=$(fm_field \"$GATE_DOSSIER_PATH\" \"coauthor_claude_forbidden\")\nif [[ \"$FORBIDDEN\" != \"true\" ]]; then\n gate_skip \"dossier does not forbid Co-Authored-By: Claude\"\nfi\n\nCLONE_DIR=\"$HOME/000-projects/contributing-clanker/${GATE_REPO##*/}\"\nif [[ ! -d \"$CLONE_DIR/.git\" ]]; then\n gate_skip \"no clone at $CLONE_DIR (not a git repo)\"\nfi\n\nDEFAULT_BRANCH=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\nif [[ -z \"$DEFAULT_BRANCH\" ]]; then\n gate_skip \"no default_branch in dossier\"\nfi\n\n# Inspect commit messages between default branch and HEAD on the working clone\nLOG=$(/usr/bin/git -C \"$CLONE_DIR\" log \"${DEFAULT_BRANCH}..HEAD\" --format=%B 2>/dev/null || /usr/bin/echo \"\")\n\nif /usr/bin/printf '%s' \"$LOG\" | /usr/bin/grep -qiE \"Co-Authored-By:[[:space:]]+Claude\"; then\n gate_block \"commits contain 'Co-Authored-By: Claude' trailer\" \"remove via git rebase -i and dropping the trailer; this repo's AI policy forbids it\"\nfi\n\ngate_pass \"no Co-Authored-By: Claude trailer in commits\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1235,"content_sha256":"a6ca19e8c22c3e407fbf0826a5842ffc1def37e48801ee57b7bc8a090df41d6d"},{"filename":"scripts/gates/c09-issue-link.sh","content":"#!/usr/bin/env bash\n# Catalog: C9 — PR body lacks `Closes #N` / `Fixes #N` referencing the candidate\n# Mitigates: PR doesn't auto-close the issue on merge → maintainer has to do it\n# manually → reads as low-effort.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\n# This gate runs at open-pr / flip-to-ready transitions and needs the PR body.\n# At this stage the PR body lives in the candidate's `pr_body_draft:` field\n# (written by writer subagent in Slice 3). For now, look in the candidate's\n# `## PR body` section if present.\nISSUE_NUM=$(fm_field \"$GATE_CANDIDATE_PATH\" \"issue_number\")\nif [[ -z \"$ISSUE_NUM\" ]]; then\n gate_skip \"no issue_number in candidate (cannot verify link)\"\nfi\n\n# Extract PR body draft from candidate's body sections\nPR_BODY=$(/usr/bin/awk '/^## PR body/{flag=1;next} /^## /{flag=0} flag' \"$GATE_CANDIDATE_PATH\" 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ -z \"$PR_BODY\" ]]; then\n # No PR body drafted yet — gate is informational at pre-draft stages\n gate_inform \"no PR body drafted yet (candidate has no '## PR body' section)\"\nfi\n\n# Look for any of: Closes #N, Fixes #N, Resolves #N (case-insensitive)\nif /usr/bin/printf '%s' \"$PR_BODY\" | /usr/bin/grep -qiE \"(closes|fixes|resolves)[[:space:]]+#?$ISSUE_NUM\\b\"; then\n gate_pass \"PR body links issue #$ISSUE_NUM via auto-close keyword\"\nfi\n\ngate_block \"PR body does not reference issue #$ISSUE_NUM with an auto-close keyword\" \"add 'Closes #$ISSUE_NUM' (or Fixes/Resolves) to the PR body so the issue auto-closes on merge.\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1514,"content_sha256":"42e8046fc77b45cf2dbf88a219eebf8a2a199232ec1fdeb9a36f751a2133b6af"},{"filename":"scripts/gates/c11-no-force-push.sh","content":"#!/usr/bin/env bash\n# Catalog: C11 — branch divergence implies force-push will be required\n# Mitigates: collaborators with local refs to this branch get rebased out from under them.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nREPO_NAME=\"${GATE_REPO##*/}\"\nCLONE=\"$HOME/000-projects/contributing-clanker/$REPO_NAME\"\n\nif [[ ! -d \"$CLONE/.git\" ]]; then\n gate_skip \"no local clone at $CLONE\"\nfi\n\nBRANCH=$(fm_field \"$GATE_CANDIDATE_PATH\" \"branch\")\nif [[ -z \"$BRANCH\" ]]; then\n gate_skip \"no branch in candidate frontmatter\"\nfi\n\n# Confirm the upstream tracking ref exists\nif ! /usr/bin/git -C \"$CLONE\" rev-parse --verify \"origin/$BRANCH\" >/dev/null 2>&1; then\n gate_skip \"no origin/$BRANCH tracking ref — first push, force not implied\"\nfi\n\nAHEAD=$(/usr/bin/git -C \"$CLONE\" log \"origin/$BRANCH..HEAD\" --oneline 2>/dev/null | /usr/bin/grep -c . || /usr/bin/echo 0)\nBEHIND=$(/usr/bin/git -C \"$CLONE\" log \"HEAD..origin/$BRANCH\" --oneline 2>/dev/null | /usr/bin/grep -c . || /usr/bin/echo 0)\n\nif [[ \"$AHEAD\" -gt 0 && \"$BEHIND\" -gt 0 ]]; then\n gate_warn \"branch diverges from origin/$BRANCH — push will require force; ensure no other contributors have local refs to this branch\" \"push directly only if you're the sole owner of this branch\"\nfi\n\ngate_pass \"branch is fast-forward to origin/$BRANCH (no force-push needed)\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1332,"content_sha256":"ee69919a375209a5e8a5337d6ba78ef87ee89e54ac628d8bd02f8a6464420a93"},{"filename":"scripts/gates/c12-ci-green.sh","content":"#!/usr/bin/env bash\n# Catalog: C12 — CI not green (any failed/pending checks block flip-to-ready)\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nPR_NUMBER=$(fm_field \"$GATE_CANDIDATE_PATH\" \"pr_number\")\nif [[ -z \"$PR_NUMBER\" ]]; then\n gate_skip \"no pr_number in candidate (no PR yet)\"\nfi\n\nCHECKS_JSON=$(gh_safe pr checks \"$PR_NUMBER\" --repo \"$GATE_REPO\" --json bucket || /usr/bin/echo \"[]\")\nif [[ -z \"$CHECKS_JSON\" ]]; then\n CHECKS_JSON=\"[]\"\nfi\n\nFAIL_COUNT=$(/usr/bin/printf '%s' \"$CHECKS_JSON\" | jq '[.[] | select(.bucket == \"fail\")] | length' 2>/dev/null || /usr/bin/echo \"0\")\nPENDING_COUNT=$(/usr/bin/printf '%s' \"$CHECKS_JSON\" | jq '[.[] | select(.bucket == \"pending\")] | length' 2>/dev/null || /usr/bin/echo \"0\")\n\nif [[ \"$FAIL_COUNT\" != \"0\" ]]; then\n FAILED=$(/usr/bin/printf '%s' \"$CHECKS_JSON\" | jq -r '[.[] | select(.bucket == \"fail\") | .name] | join(\", \")' 2>/dev/null || /usr/bin/echo \"\")\n gate_block \"CI has $FAIL_COUNT failing check(s): $FAILED\" \"fix the failing checks before flipping to ready-for-review\"\nfi\n\nif [[ \"$PENDING_COUNT\" != \"0\" ]]; then\n gate_warn \"$PENDING_COUNT CI check(s) still pending\" \"wait for CI to finish before flipping to ready-for-review\"\nfi\n\ngate_pass \"all CI checks green\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1230,"content_sha256":"a4876063748344324c9b41f9c8fcc991ee4666f938d71683c0b11bda8f4e1815"},{"filename":"scripts/gates/c13-bots-passed.sh","content":"#!/usr/bin/env bash\n# Catalog: C13 — Required review bots haven't reviewed/commented yet\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nPR_NUMBER=$(fm_field \"$GATE_CANDIDATE_PATH\" \"pr_number\")\nif [[ -z \"$PR_NUMBER\" ]]; then\n gate_skip \"no pr_number in candidate (no PR yet)\"\nfi\n\n# Parse review_bots: list from dossier — ` - \u003cname>` lines following `review_bots:`\nBOTS_RAW=$(/usr/bin/awk '\n /^---$/ { fm = !fm ? 1 : 2; next }\n fm != 1 { next }\n /^review_bots:/ { collecting = 1; next }\n collecting && /^[[:space:]]+-[[:space:]]+/ {\n sub(/^[[:space:]]+-[[:space:]]+/, \"\")\n gsub(/^\"|\"$/, \"\")\n print\n next\n }\n collecting && /^[A-Za-z]/ { collecting = 0 }\n' \"$GATE_DOSSIER_PATH\" 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ -z \"$BOTS_RAW\" ]]; then\n gate_skip \"no review_bots listed in dossier\"\nfi\n\n# Filter \"(none detected)\" sentinel\ndeclare -a BOTS=()\nwhile IFS= read -r line; do\n [[ -z \"$line\" ]] && continue\n [[ \"$line\" == \"(none detected)\" ]] && continue\n BOTS+=(\"$line\")\ndone \u003c\u003c\u003c \"$BOTS_RAW\"\n\nif (( ${#BOTS[@]} == 0 )); then\n gate_skip \"review_bots list empty or (none detected)\"\nfi\n\n# Pull all reviewer + commenter logins from the PR\nREVIEWERS=$(gh_safe pr view \"$PR_NUMBER\" --repo \"$GATE_REPO\" --json reviews,comments \\\n --jq '[.reviews[].author.login, .comments[].author.login] | unique | join(\",\")' 2>/dev/null || /usr/bin/echo \"\")\n\n# For each required bot, substring match against the reviewers/commenters list (lowercased)\nREVIEWERS_LC=$(/usr/bin/printf '%s' \"$REVIEWERS\" | /usr/bin/tr '[:upper:]' '[:lower:]')\ndeclare -a MISSING=()\nfor bot in \"${BOTS[@]}\"; do\n bot_lc=$(/usr/bin/printf '%s' \"$bot\" | /usr/bin/tr '[:upper:]' '[:lower:]' | /usr/bin/tr -d '-')\n reviewers_norm=$(/usr/bin/printf '%s' \"$REVIEWERS_LC\" | /usr/bin/tr -d '-')\n if [[ \"$reviewers_norm\" != *\"$bot_lc\"* ]]; then\n MISSING+=(\"$bot\")\n fi\ndone\n\nif (( ${#MISSING[@]} > 0 )); then\n joined=$(IFS=', '; /usr/bin/printf '%s' \"${MISSING[*]}\")\n gate_warn \"waiting on review from: $joined\" \"wait for these bots to weigh in before flipping to ready-for-review\"\nfi\n\ngate_pass \"all required review bots have engaged on the PR\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2137,"content_sha256":"aed27a8b159d7248e3ed0db927f3ef20ebd40503a37039c545826594e1dab0a4"},{"filename":"scripts/gates/c16-no-self-merge.sh","content":"#!/usr/bin/env bash\n# Catalog: C16 — author attempts to merge their own PR\n# Mitigates: bypassing maintainer review on a contribution is a hard etiquette violation.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nPR_NUM=$(fm_field \"$GATE_CANDIDATE_PATH\" \"pr_number\")\nif [[ -z \"$PR_NUM\" || -z \"$GATE_REPO\" ]]; then\n gate_skip \"no pr_number or repo in candidate\"\nfi\n\nPR_AUTHOR=$(gh_safe pr view \"$PR_NUM\" --repo \"$GATE_REPO\" --json author --jq '.author.login' || /usr/bin/echo \"\")\nME=$(gh_safe api user --jq '.login' || /usr/bin/echo \"\")\n\nif [[ -z \"$PR_AUTHOR\" || -z \"$ME\" ]]; then\n gate_skip \"could not resolve PR author or current user\"\nfi\n\nif [[ \"$PR_AUTHOR\" == \"$ME\" ]]; then\n gate_block \"you ($ME) authored PR #$PR_NUM — self-merge would bypass maintainer review\" \"you authored this PR — wait for a maintainer to merge\"\nfi\n\ngate_pass \"PR author ($PR_AUTHOR) differs from current user ($ME)\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":914,"content_sha256":"496b516a21f9a23cb93c327443a1b5f8d1afbdec4e12e08cb36e2c3c3ae19eb1"},{"filename":"scripts/gates/c19-body-claim-vs-diff.sh","content":"#!/usr/bin/env bash\n# Catalog: C19 — PR body claims that don't match the diff\n# Mitigates: Round-1 maintainer GAP-6 — close-on-sight pattern: PR body\n# claims \"added tests\" / \"updated CHANGELOG\" / \"added migration notes\" but\n# `git diff` shows no corresponding files touched. Pure AI-confabulation.\n# Fastest path from \"AI-assisted\" to \"AI-fabricated\" in maintainer's eye.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\n# Need both PR body and a way to inspect the diff\nPR_BODY=$(/usr/bin/awk '/^## PR body/{flag=1;next} /^## /{flag=0} flag' \"$GATE_CANDIDATE_PATH\" 2>/dev/null || /usr/bin/echo \"\")\nLOCAL_CLONE=$(fm_field \"$GATE_CANDIDATE_PATH\" \"local_clone_path\")\nBRANCH=$(fm_field \"$GATE_CANDIDATE_PATH\" \"branch\")\n\nif [[ -z \"$PR_BODY\" ]]; then\n gate_inform \"no PR body drafted yet\"\nfi\nif [[ -z \"$LOCAL_CLONE\" || ! -d \"$LOCAL_CLONE\" ]]; then\n gate_skip \"no local_clone_path; cannot inspect diff\"\nfi\nif [[ -z \"$BRANCH\" ]]; then\n gate_skip \"no branch in candidate\"\nfi\n\n# Get the list of changed files vs default branch (heuristic: master/main)\nDEFAULT_BRANCH=\"main\"\nif [[ -n \"$GATE_DOSSIER_PATH\" && -f \"$GATE_DOSSIER_PATH\" ]]; then\n V=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\n [[ -n \"$V\" ]] && DEFAULT_BRANCH=\"$V\"\nfi\n\nDIFF_FILES=$(cd \"$LOCAL_CLONE\" 2>/dev/null && git diff --name-only \"origin/$DEFAULT_BRANCH..$BRANCH\" 2>/dev/null || /usr/bin/echo \"\")\nif [[ -z \"$DIFF_FILES\" ]]; then\n gate_skip \"no diff against origin/$DEFAULT_BRANCH (or git command failed)\"\nfi\n\n# Claim → expected-path patterns. Each entry: regex_in_body | regex_required_in_diff\ndeclare -a CLAIMS=(\n \"(added|wrote|new) tests?\\b|added test (coverage|cases)|test cases? added|regression test|test for the (bug|fix)|tests?[[:space:]]*passing:[[:space:]]+(yes|added)|cargo test|pytest|jest|mocha|vitest=tests?/\"\n \"updated? (the )?CHANGELOG=CHANGELOG\"\n \"(added|updated) (the )?(migration|migration notes|migrations)=(migration|migrations)/\"\n \"updated? (the )?(README|docs|documentation)=(README|docs/|.md$)\"\n \"added? (the )?changeset=\\.changeset/\"\n)\n\nISSUES=()\nfor ENTRY in \"${CLAIMS[@]}\"; do\n CLAIM_REGEX=\"${ENTRY%%=*}\"\n PATH_REGEX=\"${ENTRY##*=}\"\n if /usr/bin/printf '%s' \"$PR_BODY\" | /usr/bin/grep -qiE \"$CLAIM_REGEX\"; then\n if ! /usr/bin/printf '%s' \"$DIFF_FILES\" | /usr/bin/grep -qiE \"$PATH_REGEX\"; then\n MATCHED=$(/usr/bin/printf '%s' \"$PR_BODY\" | /usr/bin/grep -ioE \"$CLAIM_REGEX\" | /usr/bin/head -1)\n ISSUES+=(\"body claims '$MATCHED' but diff doesn't touch $PATH_REGEX\")\n fi\n fi\ndone\n\nif [[ \"${#ISSUES[@]}\" -gt 0 ]]; then\n REASONS=$(/usr/bin/printf '%s; ' \"${ISSUES[@]}\")\n gate_block \"PR body makes claims not backed by the diff: $REASONS\" \"either add the work to match the claim, OR remove the claim from the PR body. False claims = closure regardless of code quality (PostHog AI_POLICY: 'PRs that clearly weren't run or tested will be closed').\"\nfi\n\ngate_pass \"PR body claims are consistent with diff\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2934,"content_sha256":"5164aa105acdcdb7812b8e6898333c6fe39cbdc9d5ca1160d9b497e75ee5d004"},{"filename":"scripts/gates/d02-no-ai-bug-reports.sh","content":"#!/usr/bin/env bash\n# Catalog: D2 — Block AI-shaped issue body when opening a new issue\n# Many repos auto-close machine-generated bug reports on sight. We refuse\n# to ship one in our name.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\n# Extract the \"## Issue body draft\" section from the candidate\nDRAFT=$(/usr/bin/awk '/^## Issue body draft/{flag=1;next} /^## /{flag=0} flag' \"$GATE_CANDIDATE_PATH\" 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ -z \"${DRAFT// /}\" ]]; then\n gate_skip \"no issue draft yet\"\nfi\n\n# AI-shaped patterns. Case-insensitive. Each entry is one distinct pattern.\nPATTERNS=(\n \"I noticed\"\n \"It appears\"\n \"I observed\"\n \"the AI suggests\"\n \"Claude suggests\"\n \"ChatGPT suggests\"\n \"based on my analysis\"\n \"after analyzing\"\n \"I've identified\"\n \"the issue stems from\"\n)\n\nHITS=()\nfor pat in \"${PATTERNS[@]}\"; do\n if /usr/bin/printf '%s' \"$DRAFT\" | /usr/bin/grep -qiE \"$pat\"; then\n HITS+=(\"$pat\")\n fi\ndone\n\nCOUNT=${#HITS[@]}\nJOINED=$(/usr/bin/printf '%s, ' \"${HITS[@]}\" 2>/dev/null | /usr/bin/sed 's/, $//')\n\nif (( COUNT >= 2 )); then\n gate_block \"issue draft contains $COUNT AI-shaped phrases: $JOINED\" \"rewrite the issue in your own voice; many repos auto-close AI-shaped bug reports\"\nfi\n\nif (( COUNT == 1 )); then\n gate_warn \"issue draft contains AI-shaped phrase: $JOINED\" \"consider rewriting in your own voice\"\nfi\n\ngate_pass \"no AI-shaped phrases detected\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1400,"content_sha256":"253747ebe9e341847dc6a8fca107466ac91c3a266ce73a626ee51afbf1b7b722"},{"filename":"scripts/gates/d03-no-ai-pr-reviews.sh","content":"#!/usr/bin/env bash\n# Catalog: D3 — Block AI-generated review comments on someone else's PR\n# AI-shaped review comments are unwelcome at most repos. Stricter than D2:\n# any single hit blocks.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nDRAFT=$(/usr/bin/awk '/^## Review draft/{flag=1;next} /^## /{flag=0} flag' \"$GATE_CANDIDATE_PATH\" 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ -z \"${DRAFT// /}\" ]]; then\n gate_skip \"no review draft yet\"\nfi\n\nPATTERNS=(\n \"I noticed\"\n \"It appears\"\n \"I observed\"\n \"the AI suggests\"\n \"Claude suggests\"\n \"ChatGPT suggests\"\n \"based on my analysis\"\n \"after analyzing\"\n \"I've identified\"\n \"the issue stems from\"\n)\n\nHITS=()\nfor pat in \"${PATTERNS[@]}\"; do\n if /usr/bin/printf '%s' \"$DRAFT\" | /usr/bin/grep -qiE \"$pat\"; then\n HITS+=(\"$pat\")\n fi\ndone\n\nCOUNT=${#HITS[@]}\nJOINED=$(/usr/bin/printf '%s, ' \"${HITS[@]}\" 2>/dev/null | /usr/bin/sed 's/, $//')\n\nif (( COUNT >= 1 )); then\n gate_block \"review draft contains $COUNT AI-shaped phrase(s): $JOINED\" \"AI-generated review comments are unwelcome at most repos; either rewrite in your own voice or skip the review\"\nfi\n\ngate_pass \"no AI-shaped phrases detected\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1163,"content_sha256":"a5a8d9341b05342dc3557ae2880d11495c5f70a4baa0ee5edbd34f350280a579"},{"filename":"scripts/gates/d05-no-reopen.sh","content":"#!/usr/bin/env bash\n# Catalog: D5 — reopening a PR that a maintainer closed\n# Mitigates: comes across as ignoring maintainer feedback; usually a fresh PR is better.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nPR_NUM=$(fm_field \"$GATE_CANDIDATE_PATH\" \"pr_number\")\nif [[ -z \"$PR_NUM\" || -z \"$GATE_REPO\" ]]; then\n gate_skip \"no pr_number or repo in candidate\"\nfi\n\nINFO=$(gh_safe pr view \"$PR_NUM\" --repo \"$GATE_REPO\" --json state,closedAt --jq '{state: .state, closedAt: .closedAt}' || /usr/bin/echo \"\")\nif [[ -z \"$INFO\" ]]; then\n gate_skip \"could not fetch PR state\"\nfi\n\nSTATE=$(/usr/bin/printf '%s' \"$INFO\" | jq -r '.state // \"\"')\nCLOSED_AT=$(/usr/bin/printf '%s' \"$INFO\" | jq -r '.closedAt // \"\"')\n\nif [[ \"$STATE\" != \"CLOSED\" || -z \"$CLOSED_AT\" || \"$CLOSED_AT\" == \"null\" ]]; then\n gate_skip \"PR is not in CLOSED state (state=$STATE) — nothing to reopen\"\nfi\n\ngate_warn \"PR #$PR_NUM was closed at $CLOSED_AT — reopening may be read as ignoring the maintainer\" \"the maintainer closed this PR; reopening without addressing their feedback can come across as disrespectful. Consider a fresh PR if you've made substantial changes\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1148,"content_sha256":"6b0d620e2105b38524f8e619323109356ef2e07b8dc9edd2f6af83af60427af8"},{"filename":"scripts/gates/e02-ai-strike-track.sh","content":"#!/usr/bin/env bash\n# Catalog: E2 — AI policy strike tracking, ORG-LEVEL (not just repo-level)\n# Mitigates: Round-1 AI-policy GAP-6 — PostHog AI_POLICY says \"Two or more\n# closures: We'll block the account.\" Stake is account-level, not per-repo.\n# A 1st closure on PostHog/posthog + 1st on PostHog/posthog-js = blocked,\n# and a per-repo gate would never see it. This gate evaluates strikes at\n# the org-owner level.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_REPO\" ]]; then\n gate_skip \"no repo in candidate\"\nfi\n\n# Read strike scope from dossier (default: org)\nSTRIKE_SCOPE=\"org\"\nif [[ -n \"$GATE_DOSSIER_PATH\" && -f \"$GATE_DOSSIER_PATH\" ]]; then\n V=$(fm_field \"$GATE_DOSSIER_PATH\" \"strike_scope\")\n [[ -n \"$V\" ]] && STRIKE_SCOPE=\"$V\"\nfi\n\nOWNER=\"${GATE_REPO%%/*}\"\nLOG=\"$HOME/.contribute-system/log.jsonl\"\n\nif [[ ! -f \"$LOG\" ]]; then\n gate_pass \"no log.jsonl yet (no prior strikes possible)\"\nfi\n\n# Count prior closures with reason matching AI policy at the appropriate scope.\n# Reason patterns: any 'dropped' event with reason containing 'ai_policy', 'ai-policy', 'slop', 'ai policy'\ncase \"$STRIKE_SCOPE\" in\n repo) SCOPE_FILTER=\".details.repo == \\\"$GATE_REPO\\\"\" ;;\n org) SCOPE_FILTER=\".details.repo | startswith(\\\"$OWNER/\\\")\" ;;\n account) SCOPE_FILTER=\"true\" ;;\n *) gate_block \"unknown strike_scope in dossier: $STRIKE_SCOPE\" \"set strike_scope to repo|org|account in the dossier\" ;;\nesac\n\nSTRIKE_COUNT=$(jq -c \"\n select(.event == \\\"candidate_dropped\\\")\n | select($SCOPE_FILTER)\n | select(.details.reason | tostring | test(\\\"ai[ _-]?policy|ai[ _-]?slop\\\"; \\\"i\\\"))\n\" \"$LOG\" 2>/dev/null | /usr/bin/wc -l | /usr/bin/awk '{print $1}')\n\n# Note: pre-Phase-3, there's no way to query PostHog's ACTUAL record of our\n# strikes; we only know what WE logged. If we never logged a closure (e.g.,\n# PostHog closed without us calling it out), we'd undercount. Best-effort.\n\nif [[ \"${STRIKE_COUNT:-0}\" -ge 1 ]]; then\n if [[ \"$STRIKE_COUNT\" -eq 1 ]]; then\n gate_block \"1 prior AI-policy closure at $STRIKE_SCOPE-scope ($OWNER). Next closure = account block at PostHog-tier repos.\" \"manual override required: --override-gate=E2 \\\"\u003creason you're confident this PR won't get closed>\\\". This is the gate that prevents account-block.\"\n else\n gate_block \"$STRIKE_COUNT prior AI-policy closures at $STRIKE_SCOPE-scope ($OWNER). HARD STOP — pause contributions to this org until you've discussed with maintainers.\" \"manual override is intentionally inconvenient here. If you must proceed, --override-gate=E2 \\\"\u003cwritten rationale>\\\" AND post a comment on the issue acknowledging the prior closures.\"\n fi\nfi\n\ngate_pass \"no prior AI-policy closures at $STRIKE_SCOPE-scope ($OWNER)\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2721,"content_sha256":"081cac7bd1ad4aad675268147de136097d4b930667960f4a155e2b508cfaaff3"},{"filename":"scripts/gates/e04-fork-target.sh","content":"#!/usr/bin/env bash\n# Catalog: E4 — Verify origin points at user's fork (not upstream)\n# Mitigates: pushing to someone else's repo by accident, or being unable to\n# push at all.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nCLONE=\"$HOME/000-projects/contributing-clanker/${GATE_REPO##*/}\"\n\nif [[ ! -d \"$CLONE/.git\" ]]; then\n gate_skip \"no local clone at $CLONE\"\nfi\n\nUSER_LOGIN=$(gh_safe api user --jq .login 2>/dev/null || /usr/bin/echo \"\")\nif [[ -z \"$USER_LOGIN\" ]]; then\n gate_skip \"could not resolve current gh user login\"\nfi\n\nORIGIN_URL=$(/usr/bin/git -C \"$CLONE\" remote get-url origin 2>/dev/null || /usr/bin/echo \"\")\nif [[ -z \"$ORIGIN_URL\" ]]; then\n gate_block \"no origin remote configured in $CLONE\" \"set origin to your fork: gh repo fork ${GATE_REPO} --remote --remote-name origin\"\nfi\n\n# Parse owner from URL — handles SSH ([email protected]:owner/repo.git) and\n# HTTPS (https://github.com/owner/repo.git) forms.\nORIGIN_OWNER=$(/usr/bin/printf '%s' \"$ORIGIN_URL\" | /usr/bin/sed -E 's#^git@github\\.com:([^/]+)/.*$#\\1#; s#^https?://github\\.com/([^/]+)/.*$#\\1#')\n\nUPSTREAM_OWNER=\"${GATE_REPO%%/*}\"\n\nif [[ \"$ORIGIN_OWNER\" == \"$UPSTREAM_OWNER\" ]]; then\n gate_inform \"origin points at upstream ($UPSTREAM_OWNER); pushing directly to upstream — verify you have access\"\nfi\n\nif [[ \"$ORIGIN_OWNER\" != \"$USER_LOGIN\" ]]; then\n gate_block \"origin owner is '$ORIGIN_OWNER' but your gh login is '$USER_LOGIN'\" \"set origin to your fork: gh repo fork ${GATE_REPO} --remote --remote-name origin\"\nfi\n\ngate_pass \"origin points at your fork ($USER_LOGIN/${GATE_REPO##*/})\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1581,"content_sha256":"a36a97d7e3e449be1b8117ab8c5a0354d7c159b62e5dc2895c85f5e52ee0799f"},{"filename":"scripts/gates/f01-license-compat.sh","content":"#!/usr/bin/env bash\n# Catalog: F1 — License compatibility check for newly added dependencies\n# Lists new deps and prompts manual license verification. Does NOT look up\n# SPDX from registries (too heavy for a gate); informational only.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nCLONE=\"$HOME/000-projects/contributing-clanker/${GATE_REPO##*/}\"\n\nif [[ ! -d \"$CLONE/.git\" ]]; then\n gate_skip \"no local clone at $CLONE\"\nfi\n\nDEFAULT_BRANCH=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\nif [[ -z \"$DEFAULT_BRANCH\" ]]; then\n gate_skip \"no default_branch in dossier\"\nfi\n\nREPO_LICENSE=$(fm_field \"$GATE_DOSSIER_PATH\" \"license\")\n[[ -z \"$REPO_LICENSE\" ]] && REPO_LICENSE=\"unknown\"\n\nCHANGED=$(/usr/bin/git -C \"$CLONE\" diff \"$DEFAULT_BRANCH..HEAD\" --name-only 2>/dev/null || /usr/bin/echo \"\")\n\nMANIFESTS=()\nwhile IFS= read -r f; do\n case \"$f\" in\n package.json|*/package.json) MANIFESTS+=(\"$f\") ;;\n Cargo.toml|*/Cargo.toml) MANIFESTS+=(\"$f\") ;;\n requirements.txt|*/requirements.txt) MANIFESTS+=(\"$f\") ;;\n pyproject.toml|*/pyproject.toml) MANIFESTS+=(\"$f\") ;;\n go.mod|*/go.mod) MANIFESTS+=(\"$f\") ;;\n Gemfile|*/Gemfile) MANIFESTS+=(\"$f\") ;;\n esac\ndone \u003c\u003c\u003c \"$CHANGED\"\n\nif (( ${#MANIFESTS[@]} == 0 )); then\n gate_pass \"no dependency manifest changes\"\nfi\n\nNEW_DEPS=()\nfor m in \"${MANIFESTS[@]}\"; do\n DIFF=$(/usr/bin/git -C \"$CLONE\" diff \"$DEFAULT_BRANCH..HEAD\" -- \"$m\" 2>/dev/null || /usr/bin/echo \"\")\n # Best-effort regex per manifest type — extract package names from + lines\n case \"$m\" in\n *package.json)\n while IFS= read -r line; do\n name=$(/usr/bin/printf '%s' \"$line\" | /usr/bin/sed -nE 's/^\\+[[:space:]]*\"([^\"]+)\":[[:space:]]*\"[^\"]+\".*$/\\1/p')\n [[ -n \"$name\" ]] && NEW_DEPS+=(\"$name\")\n done \u003c\u003c\u003c \"$DIFF\"\n ;;\n *Cargo.toml)\n while IFS= read -r line; do\n name=$(/usr/bin/printf '%s' \"$line\" | /usr/bin/sed -nE 's/^\\+[[:space:]]*([a-zA-Z0-9_-]+)[[:space:]]*=.*$/\\1/p')\n [[ -n \"$name\" ]] && NEW_DEPS+=(\"$name\")\n done \u003c\u003c\u003c \"$DIFF\"\n ;;\n *requirements.txt)\n while IFS= read -r line; do\n name=$(/usr/bin/printf '%s' \"$line\" | /usr/bin/sed -nE 's/^\\+[[:space:]]*([a-zA-Z0-9_.-]+)[[:space:]]*[\u003c>=~!].*$/\\1/p; s/^\\+[[:space:]]*([a-zA-Z0-9_.-]+)[[:space:]]*$/\\1/p')\n [[ -n \"$name\" ]] && NEW_DEPS+=(\"$name\")\n done \u003c\u003c\u003c \"$DIFF\"\n ;;\n *pyproject.toml)\n while IFS= read -r line; do\n name=$(/usr/bin/printf '%s' \"$line\" | /usr/bin/sed -nE 's/^\\+[[:space:]]*\"([a-zA-Z0-9_.-]+)[[:space:]\u003c>=~!].*\"$/\\1/p; s/^\\+[[:space:]]*([a-zA-Z0-9_.-]+)[[:space:]]*=[[:space:]]*\".*$/\\1/p')\n [[ -n \"$name\" ]] && NEW_DEPS+=(\"$name\")\n done \u003c\u003c\u003c \"$DIFF\"\n ;;\n *go.mod)\n while IFS= read -r line; do\n name=$(/usr/bin/printf '%s' \"$line\" | /usr/bin/sed -nE 's/^\\+[[:space:]]*([a-zA-Z0-9_./-]+)[[:space:]]+v[0-9].*$/\\1/p')\n [[ -n \"$name\" ]] && NEW_DEPS+=(\"$name\")\n done \u003c\u003c\u003c \"$DIFF\"\n ;;\n *Gemfile)\n while IFS= read -r line; do\n name=$(/usr/bin/printf '%s' \"$line\" | /usr/bin/sed -nE \"s/^\\+[[:space:]]*gem[[:space:]]+['\\\"]([^'\\\"]+)['\\\"].*$/\\1/p\")\n [[ -n \"$name\" ]] && NEW_DEPS+=(\"$name\")\n done \u003c\u003c\u003c \"$DIFF\"\n ;;\n esac\ndone\n\nif (( ${#NEW_DEPS[@]} == 0 )); then\n gate_pass \"no new dependency entries detected in changed manifests\"\nfi\n\n# Dedupe\nUNIQ=$(/usr/bin/printf '%s\\n' \"${NEW_DEPS[@]}\" | /usr/bin/awk '!seen[$0]++' | /usr/bin/tr '\\n' ',' | /usr/bin/sed 's/,$//')\n\ngate_inform \"new dependencies added: $UNIQ; verify license compatibility with repo's $REPO_LICENSE license manually\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3563,"content_sha256":"657eece2834112fcd4e65dff96e7a7676fee37c744869bbc00e5df71721b17be"},{"filename":"scripts/gates/f03-fixtures-clean.sh","content":"#!/usr/bin/env bash\n# Catalog: F3 — new fixture / sample files that may contain copyrighted external content\n# Mitigates: copying real-world web/user content into fixtures triggers license takedowns.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nREPO_NAME=\"${GATE_REPO##*/}\"\nCLONE=\"$HOME/000-projects/contributing-clanker/$REPO_NAME\"\n\nif [[ ! -d \"$CLONE/.git\" ]]; then\n gate_skip \"no local clone at $CLONE\"\nfi\n\nDEFAULT_BRANCH=\"\"\nif [[ -n \"$GATE_DOSSIER_PATH\" && -f \"$GATE_DOSSIER_PATH\" ]]; then\n DEFAULT_BRANCH=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\nfi\n[[ -z \"$DEFAULT_BRANCH\" ]] && DEFAULT_BRANCH=\"main\"\n\nADDED=$(/usr/bin/git -C \"$CLONE\" diff \"$DEFAULT_BRANCH..HEAD\" --name-only --diff-filter=A 2>/dev/null || /usr/bin/echo \"\")\n\nFIXTURES=$(/usr/bin/printf '%s\\n' \"$ADDED\" | /usr/bin/grep -E '(tests/fixtures/|tests/data/|__fixtures__/|(^|/)fixtures/|test_data/|testdata/|(^|/)samples/)' || /usr/bin/echo \"\")\n\nif [[ -z \"$FIXTURES\" ]]; then\n gate_pass \"no new fixture / sample files added\"\nfi\n\nLIST=$(/usr/bin/printf '%s' \"$FIXTURES\" | /usr/bin/tr '\\n' ',' | /usr/bin/sed 's/,$//')\ngate_inform \"new fixture / sample files added — verify provenance manually: $LIST\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1195,"content_sha256":"002b5884d2e0767866936f27f513373ebfee91cad9ef7e84dff2a81c9f19b4f7"},{"filename":"scripts/gates/f04-override-disclosure.sh","content":"#!/usr/bin/env bash\n# Catalog: F4 — Override disclosure required in PR body\n# Mitigates: Round-1 maintainer GAP-10 + security GAP-2 + portfolio GAP-3 —\n# `--override-gate=\u003cid>` writes audit trail to log.jsonl + candidate .md, but\n# both are private to the contributor. From the maintainer's POV, the\n# override mechanism is plausible-deniability theater. This gate forces any\n# PR submitted with overrides to include a `## Safety override disclosure`\n# section in the PR body listing each override + reason.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\n# Read overrides from candidate frontmatter\nOVERRIDES=$(/usr/bin/awk '/^overrides:/{flag=1;next} /^[a-z_]+:/{flag=0} flag' \"$GATE_CANDIDATE_PATH\" 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ -z \"$OVERRIDES\" ]]; then\n gate_pass \"no overrides used; nothing to disclose\"\nfi\n\n# Count overrides\nOVERRIDE_COUNT=$(/usr/bin/printf '%s\\n' \"$OVERRIDES\" | /usr/bin/grep -c 'gate:' || /usr/bin/echo 0)\n\n# Read PR body draft from candidate (## PR body section)\nPR_BODY=$(/usr/bin/awk '/^## PR body/{flag=1;next} /^## /{flag=0} flag' \"$GATE_CANDIDATE_PATH\" 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ -z \"$PR_BODY\" ]]; then\n gate_inform \"$OVERRIDE_COUNT overrides used but no PR body drafted yet — re-run at open-pr\"\nfi\n\n# Look for the disclosure section in the PR body\nif /usr/bin/printf '%s' \"$PR_BODY\" | /usr/bin/grep -qiE '^## (Safety override|Override) disclosure'; then\n # Check that each override gate ID is mentioned in the body\n MISSING=()\n while IFS= read -r OG; do\n GID=$(/usr/bin/printf '%s' \"$OG\" | /usr/bin/grep -oP 'gate:\\s*\\K[A-Z0-9]+')\n [[ -z \"$GID\" ]] && continue\n if ! /usr/bin/printf '%s' \"$PR_BODY\" | /usr/bin/grep -qE \"\\b$GID\\b\"; then\n MISSING+=(\"$GID\")\n fi\n done \u003c \u003c(/usr/bin/printf '%s\\n' \"$OVERRIDES\" | /usr/bin/grep 'gate:')\n\n if [[ \"${#MISSING[@]}\" -eq 0 ]]; then\n gate_pass \"all $OVERRIDE_COUNT overrides disclosed in PR body\"\n else\n gate_block \"PR body has '## Safety override disclosure' section but doesn't mention overrides: ${MISSING[*]}\" \"list each override gate ID in the disclosure section with the reason you used it\"\n fi\nfi\n\ngate_block \"$OVERRIDE_COUNT safety overrides used but PR body lacks '## Safety override disclosure' section\" \"add a section to the PR body enumerating each override + reason. This is the maintainer-visible audit trail; without it the override mechanism is invisible to the people the safety system is supposed to protect.\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2468,"content_sha256":"258fa23f7759752c3154fb0f2a10fe6994bddb1efda4336b7c2f3f9543f9c2d7"},{"filename":"scripts/gates/g01-no-vendored-edits.sh","content":"#!/usr/bin/env bash\n# Catalog: G1 — Block hand-edits to vendored / generated paths\n# Vendored dirs (vendor/, node_modules/, dist/, etc.) must regenerate from\n# source. Lockfile changes are common and legitimate (dependency bumps);\n# treat them as WARN, not BLOCK.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nCLONE=\"$HOME/000-projects/contributing-clanker/${GATE_REPO##*/}\"\n\nif [[ ! -d \"$CLONE/.git\" ]]; then\n gate_skip \"no local clone at $CLONE\"\nfi\n\nDEFAULT_BRANCH=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\n[[ -z \"$DEFAULT_BRANCH\" ]] && DEFAULT_BRANCH=\"main\"\n\nCHANGED=$(/usr/bin/git -C \"$CLONE\" diff \"$DEFAULT_BRANCH..HEAD\" --name-only 2>/dev/null || /usr/bin/echo \"\")\n\nif [[ -z \"${CHANGED// /}\" ]]; then\n gate_pass \"no changed files\"\nfi\n\nVENDOR_HITS=()\nLOCKFILE_HITS=()\n\n# Vendor-dir patterns (BLOCK)\nVENDOR_RE='^(vendor/|node_modules/|\\.next/|dist/|build/|generated/|_generated/)'\n# Lockfile patterns (WARN)\nLOCKFILE_RE='(^pnpm-lock\\.yaml$|^package-lock\\.json$|^Cargo\\.lock$|^poetry\\.lock$|^uv\\.lock$|^yarn\\.lock$|\\.lock$)'\n\nwhile IFS= read -r f; do\n [[ -z \"$f\" ]] && continue\n if /usr/bin/printf '%s' \"$f\" | /usr/bin/grep -qE \"$VENDOR_RE\"; then\n VENDOR_HITS+=(\"$f\")\n elif /usr/bin/printf '%s' \"$f\" | /usr/bin/grep -qE \"$LOCKFILE_RE\"; then\n LOCKFILE_HITS+=(\"$f\")\n fi\ndone \u003c\u003c\u003c \"$CHANGED\"\n\nif (( ${#VENDOR_HITS[@]} > 0 )); then\n JOINED=$(/usr/bin/printf '%s, ' \"${VENDOR_HITS[@]}\" | /usr/bin/sed 's/, $//')\n gate_block \"vendored/generated paths edited: $JOINED\" \"regenerate from source instead of editing by hand; if this is intentional, override with --override-gate G1\"\nfi\n\nif (( ${#LOCKFILE_HITS[@]} > 0 )); then\n JOINED=$(/usr/bin/printf '%s, ' \"${LOCKFILE_HITS[@]}\" | /usr/bin/sed 's/, $//')\n gate_warn \"lockfile changes detected: $JOINED\" \"verify the lockfile changes are intentional (dependency bumps) and not stale-checkout artifacts\"\nfi\n\ngate_pass \"no vendored/generated path edits\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1933,"content_sha256":"ce5ae8385d93bf66772fae1ec912da9f74f1e3fe32571a2526fa428c9a870e71"},{"filename":"scripts/gates/g02-protected-paths.sh","content":"#!/usr/bin/env bash\n# Catalog: G2 — diff touches infrastructure / CI paths that maintainers gate-keep\n# Mitigates: drive-by edits to .github/workflows or terraform/ rarely land first try.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nREPO_NAME=\"${GATE_REPO##*/}\"\nCLONE=\"$HOME/000-projects/contributing-clanker/$REPO_NAME\"\n\nif [[ ! -d \"$CLONE/.git\" ]]; then\n gate_skip \"no local clone at $CLONE\"\nfi\n\nDEFAULT_BRANCH=\"\"\nif [[ -n \"$GATE_DOSSIER_PATH\" && -f \"$GATE_DOSSIER_PATH\" ]]; then\n DEFAULT_BRANCH=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\nfi\n[[ -z \"$DEFAULT_BRANCH\" ]] && DEFAULT_BRANCH=\"main\"\n\nCHANGED=$(/usr/bin/git -C \"$CLONE\" diff \"$DEFAULT_BRANCH..HEAD\" --name-only 2>/dev/null || /usr/bin/echo \"\")\n\nPROTECTED=$(/usr/bin/printf '%s\\n' \"$CHANGED\" | /usr/bin/grep -E '(\\.github/workflows/|^infrastructure/|^terraform/|^helm/|^k8s/|^kubernetes/|^\\.circleci/|^charts/)' || /usr/bin/echo \"\")\n\nif [[ -z \"$PROTECTED\" ]]; then\n gate_pass \"diff does not touch protected infrastructure / CI paths\"\nfi\n\nLIST=$(/usr/bin/printf '%s' \"$PROTECTED\" | /usr/bin/tr '\\n' ',' | /usr/bin/sed 's/,$//')\ngate_warn \"diff touches protected paths: $LIST\" \"infrastructure / CI changes deserve maintainer pre-approval; consider opening a Design Issue first to discuss the change\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1285,"content_sha256":"f5dc771c0814110ae0dcd8e0024c40e06da84995e4433cbb1b50a8ba1cae0731"},{"filename":"scripts/gates/g03-no-changelog-edits.sh","content":"#!/usr/bin/env bash\n# Catalog: G3 — manual CHANGELOG edits in a repo that auto-generates the changelog\n# Mitigates: clobbers release tooling, gets reverted on next release, looks careless.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_DOSSIER_PATH\" || ! -f \"$GATE_DOSSIER_PATH\" ]]; then\n gate_skip \"no dossier — cannot determine auto_changelog policy\"\nfi\n\nAUTO=$(fm_field \"$GATE_DOSSIER_PATH\" \"auto_changelog\")\nif [[ \"$AUTO\" != \"true\" ]]; then\n gate_skip \"dossier auto_changelog is not true\"\nfi\n\nREPO_NAME=\"${GATE_REPO##*/}\"\nCLONE=\"$HOME/000-projects/contributing-clanker/$REPO_NAME\"\n\nif [[ ! -d \"$CLONE/.git\" ]]; then\n gate_skip \"no local clone at $CLONE\"\nfi\n\nDEFAULT_BRANCH=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\n[[ -z \"$DEFAULT_BRANCH\" ]] && DEFAULT_BRANCH=\"main\"\n\nCHANGED=$(/usr/bin/git -C \"$CLONE\" diff \"$DEFAULT_BRANCH..HEAD\" --name-only 2>/dev/null || /usr/bin/echo \"\")\n\n# Match CHANGELOG.md at root or in any subdir (case-sensitive)\nHITS=$(/usr/bin/printf '%s\\n' \"$CHANGED\" | /usr/bin/grep -E '(^|/)CHANGELOG\\.md

Contribute Command Center Overview Local-only OSS contribution workflow. The skill itself is the system — there is no separate CLI binary, dashboard, or cloud backend. State lives in three places: 1. GitHub itself — fetched live via for any PR/issue state question. Never cached long-term. 2. Markdown candidate files at — one per issue we're tracking. Frontmatter is the queryable layer (status, scout score, repo, research path, overrides). Body holds claim drafts, PR drafts, scope notes. 3. Markdown repo dossiers at — one per upstream repo we contribute to. Built by the subagent. Frontmatter i…

|| /usr/bin/echo \"\")\n\nif [[ -z \"$HITS\" ]]; then\n gate_pass \"no CHANGELOG.md edits in diff\"\nfi\n\nLIST=$(/usr/bin/printf '%s' \"$HITS\" | /usr/bin/tr '\\n' ',' | /usr/bin/sed 's/,$//')\ngate_warn \"diff edits CHANGELOG.md ($LIST) but repo auto-generates changelog\" \"this repo auto-generates CHANGELOG; let the release tooling write it\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1399,"content_sha256":"c06a37f761b81eb0f154284fb626cfa93f23a39b4a8de4ef18c0a9231776a4a4"},{"filename":"scripts/gates/g04-no-version-bump.sh","content":"#!/usr/bin/env bash\n# Catalog: G4 — manual version bump in a repo that uses semantic-release / changesets\n# Mitigates: clobbers release tooling, breaks semver, signals unfamiliarity with workflow.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_DOSSIER_PATH\" || ! -f \"$GATE_DOSSIER_PATH\" ]]; then\n gate_skip \"no dossier — cannot determine auto_version policy\"\nfi\n\nAUTO=$(fm_field \"$GATE_DOSSIER_PATH\" \"auto_version\")\nif [[ \"$AUTO\" != \"true\" ]]; then\n gate_skip \"dossier auto_version is not true\"\nfi\n\nREPO_NAME=\"${GATE_REPO##*/}\"\nCLONE=\"$HOME/000-projects/contributing-clanker/$REPO_NAME\"\n\nif [[ ! -d \"$CLONE/.git\" ]]; then\n gate_skip \"no local clone at $CLONE\"\nfi\n\nDEFAULT_BRANCH=$(fm_field \"$GATE_DOSSIER_PATH\" \"default_branch\")\n[[ -z \"$DEFAULT_BRANCH\" ]] && DEFAULT_BRANCH=\"main\"\n\nDIFF=$(/usr/bin/git -C \"$CLONE\" diff \"$DEFAULT_BRANCH..HEAD\" -- package.json Cargo.toml pyproject.toml 2>/dev/null || /usr/bin/echo \"\")\n\n# Look for added lines that look like version field updates\nHITS=$(/usr/bin/printf '%s\\n' \"$DIFF\" | /usr/bin/grep -E '^\\+[[:space:]]*(\"version\"[[:space:]]*:|version[[:space:]]*=)' || /usr/bin/echo \"\")\n\nif [[ -z \"$HITS\" ]]; then\n gate_pass \"no manual version field changes detected\"\nfi\n\ngate_warn \"diff appears to bump a version field (package.json / Cargo.toml / pyproject.toml)\" \"this repo uses semantic-release / changesets; don't bump versions manually\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1412,"content_sha256":"adb3fb0b5340f1b2f799b2d7c22edbf10cee304999113976f70b171b1a566f31"},{"filename":"scripts/gates/g06-override-rate-limit.sh","content":"#!/usr/bin/env bash\n# Catalog: G6 — Override rate limit (anti-habituation)\n# Mitigates: Round-1 security GAP-2 — under \"just ship it\" pressure, an agent\n# learns to invoke overrides reflexively. Without a rate limit, the override\n# mechanism becomes a slop escape valve. ≥3 overrides at one repo in 30d =\n# the system is telling you something; pause and reflect.\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\n\ngate_read_input\n\nif [[ -z \"$GATE_REPO\" ]]; then\n gate_skip \"no repo in candidate\"\nfi\n\nLOG=\"$HOME/.contribute-system/log.jsonl\"\n[[ ! -f \"$LOG\" ]] && gate_pass \"no log.jsonl yet\"\n\n# Count override events in last 30 days at this repo\nTHIRTY_AGO=$(/usr/bin/date -u -d '30 days ago' +%s)\n\nOVERRIDE_COUNT=$(jq -r --arg cutoff \"$THIRTY_AGO\" --arg repo \"$GATE_REPO\" '\n select(.event == \"gate_override\")\n | select(.details.repo == $repo)\n | select((.ts | fromdateiso8601) >= ($cutoff | tonumber))\n' \"$LOG\" 2>/dev/null | jq -s 'length' 2>/dev/null || /usr/bin/echo 0)\n\nif [[ \"${OVERRIDE_COUNT:-0}\" -ge 3 ]]; then\n gate_block \"$OVERRIDE_COUNT gate overrides at $GATE_REPO in last 30 days (rate limit: 3)\" \"the system is telling you something. Either your scope at this repo is wrong, your dossier is out of date, or you're forcing through patterns that will eventually trigger an AI-policy closure. To override THIS rate limit: edit ~/.contribute-system/g06-rate-limit-reset && touch it. Don't just shrug it off.\"\nfi\n\ngate_pass \"$OVERRIDE_COUNT/3 overrides at $GATE_REPO in last 30 days\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":1493,"content_sha256":"591a9926927c36136245e0f24033ad0eeb362af8f8fc99010b634246694c0a97"},{"filename":"scripts/gates/lib/preamble.sh","content":"#!/usr/bin/env bash\n# Shared preamble for every gate script. Sourced as the first line of every\n# gate. Eliminates the per-script bug surface that Slice 1's scout-discover.sh\n# already showed (empty-array failures under set -u, jq parse errors on\n# malformed gh output, etc.).\n#\n# Per-script gate authors: source this file FIRST, then implement the gate.\n# Use the helpers below for output. NEVER `echo \"{...}\"` your verdict directly\n# — use gate_pass / gate_warn / gate_block / gate_inform.\n\nset -euo pipefail\n\n# Global error trap — any uncaught error dumps a BLOCK verdict + exits 0.\n# (Per gate contract: exit code is always 0; runner reads stdout JSON.)\n_GATE_ID=\"${0##*/}\"\n_GATE_ID=\"${_GATE_ID%.sh}\"\n\n_gate_err_trap() {\n local exit_code=$?\n local line_no=$1\n /usr/bin/cat \u003c\u003cEOF\n{\"severity\":\"BLOCK\",\"gate\":\"$_GATE_ID\",\"reason\":\"gate $_GATE_ID crashed at line $line_no (exit $exit_code) — fail-closed\",\"fix_hint\":\"check ~/.contribute-system/check-runs/gate-debug.log; this is a bug in the gate itself\"}\nEOF\n exit 0\n}\ntrap '_gate_err_trap $LINENO' ERR\n\n# Emit a verdict and exit. Use these — never raw echo.\n#\n# Reason / fix_hint are JSON-escaped via jq -nc. Earlier versions used\n# printf '%s' which dropped raw text into the JSON body and produced\n# invalid output whenever any message contained a literal `\"`. Caught by\n# the e02-ai-strike-track unit tests on 2026-05-03.\ngate_pass() { jq -nc --arg g \"$_GATE_ID\" --arg r \"${1:-ok}\" '{severity:\"PASS\",gate:$g,reason:$r}'; exit 0; }\ngate_warn() { jq -nc --arg g \"$_GATE_ID\" --arg r \"${1:-warning}\" --arg f \"${2:-}\" '{severity:\"WARN\",gate:$g,reason:$r,fix_hint:$f}'; exit 0; }\ngate_block() { jq -nc --arg g \"$_GATE_ID\" --arg r \"${1:-blocked}\" --arg f \"${2:-}\" '{severity:\"BLOCK\",gate:$g,reason:$r,fix_hint:$f}'; exit 0; }\ngate_inform() { jq -nc --arg g \"$_GATE_ID\" --arg r \"${1:-noted}\" '{severity:\"INFORM\",gate:$g,reason:$r}'; exit 0; }\ngate_skip() { jq -nc --arg g \"$_GATE_ID\" --arg r \"${1:-not applicable}\" '{severity:\"SKIP\",gate:$g,reason:$r}'; exit 0; }\n\n# Read stdin JSON contract. Sets:\n# GATE_CANDIDATE_PATH — path to candidate .md file\n# GATE_DOSSIER_PATH — path to dossier .md (or empty if no dossier)\n# GATE_ACTION — transition name (e.g., \"shortlist→claimed\")\n# GATE_REPO — owner/repo\n# GATE_INPUT_JSON — raw stdin for any extra fields\ngate_read_input() {\n GATE_INPUT_JSON=$(/usr/bin/cat)\n GATE_CANDIDATE_PATH=$(/usr/bin/printf '%s' \"$GATE_INPUT_JSON\" | jq -r '.candidate // \"\"')\n GATE_DOSSIER_PATH=$(/usr/bin/printf '%s' \"$GATE_INPUT_JSON\" | jq -r '.dossier // \"\"')\n GATE_ACTION=$(/usr/bin/printf '%s' \"$GATE_INPUT_JSON\" | jq -r '.action // \"\"')\n GATE_REPO=$(/usr/bin/printf '%s' \"$GATE_INPUT_JSON\" | jq -r '.env.repo // \"\"')\n export GATE_INPUT_JSON GATE_CANDIDATE_PATH GATE_DOSSIER_PATH GATE_ACTION GATE_REPO\n}\n\n# Read a frontmatter field from a markdown file. Returns empty if missing.\n# Usage: val=$(fm_field /path/to/file.md \"key\")\nfm_field() {\n local file=\"$1\" key=\"$2\"\n [[ -f \"$file\" ]] || { /usr/bin/printf ''; return; }\n /usr/bin/awk -v k=\"$key\" '\n /^---$/ { fm = !fm ? 1 : 2; next }\n fm == 1 && $0 ~ \"^\"k\":\" {\n sub(\"^\"k\":[[:space:]]*\", \"\")\n gsub(/^\"|\"$/, \"\")\n print\n exit\n }\n ' \"$file\"\n}\n\n# gh wrapper with bounded retry.\n#\n# Constraint: gate-runner enforces a 10s wall-clock timeout per gate. gh_safe\n# must finish well under that to leave room for jq/awk downstream.\n# Tuning: 2 attempts, 0.5s sleep between, 4s per-call timeout.\n# Worst case ≈ 4 + 0.5 + 4 = 8.5s — leaves headroom.\n#\n# Permanent 404s (issue/repo doesn't exist) fail on the first call's HTTP error\n# and don't usefully retry. Transient blips get one retry. That's enough for\n# the read-only gh queries gates make.\n#\n# On final failure returns non-zero with empty stdout — caller decides what\n# missing data means (usually `gate_pass`/`gate_inform`/`gate_skip`).\ngh_safe() {\n local attempt=1 max=2\n while (( attempt \u003c= max )); do\n if /usr/bin/timeout 4 gh \"$@\" 2>/dev/null; then return 0; fi\n /usr/bin/sleep 0.5\n attempt=$(( attempt + 1 ))\n done\n return 1\n}\n\n# Helper: log a gate run for observability. Append-only.\ngate_log_run() {\n local verdict=\"$1\"\n /usr/bin/printf '%s\\n' \"$(jq -nc \\\n --arg ts \"$(/usr/bin/date -u +%Y-%m-%dT%H:%M:%SZ)\" \\\n --arg gate \"$_GATE_ID\" \\\n --arg action \"$GATE_ACTION\" \\\n --arg repo \"$GATE_REPO\" \\\n --arg verdict \"$verdict\" \\\n '{ts: $ts, event: \"gate_run\", details: {gate: $gate, action: $action, repo: $repo, verdict: $verdict}}')\" \\\n >> ~/.contribute-system/log.jsonl 2>/dev/null || true\n}\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":4585,"content_sha256":"7b57b984996225e54c05f159b2cba43905e641db5264682fa46b725cd6d3d7dd"},{"filename":"scripts/lint-candidate.sh","content":"#!/usr/bin/env bash\n# lint-candidate.sh — report missing required body sections in candidate files.\n#\n# Sibling to audit-overrides.sh and catalog-coverage.sh: a read-only reporter\n# that walks ~/.contribute-system/candidates/, reads each candidate's status,\n# and surfaces missing required sections per the matrix in\n# skills/contribute/references/candidate-file-format.md.\n#\n# Required-sections matrix (matches the spec + transition.sh):\n# shortlist → ## Scope, ## Files to touch\n# claimed → ## Scope, ## Files to touch, ## Claim comment draft\n# working → same as claimed\n# submitted → ## PR title, ## PR body, ## Test results\n# merged → same as submitted\n# open → no body requirements\n# dropped → no body requirements\n#\n# Usage:\n# lint-candidate.sh # all candidates, table output\n# lint-candidate.sh --status=submitted # filter to one status\n# lint-candidate.sh --missing-only # only candidates with missing sections\n# lint-candidate.sh --json # JSON output (one object per candidate)\n# lint-candidate.sh --candidates-dir=X # override the candidate dir\n#\n# Exit codes:\n# 0 — no missing sections across the filtered set (clean)\n# 1 — at least one candidate has missing required sections\n# 64 — bad arguments\n\nset -uo pipefail\n\nCAND_DIR=\"${HOME}/.contribute-system/candidates\"\nSTATUS_FILTER=\"\"\nMISSING_ONLY=0\nJSON_OUT=0\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --status=*) STATUS_FILTER=\"${1#*=}\" ;;\n --missing-only) MISSING_ONLY=1 ;;\n --json) JSON_OUT=1 ;;\n --candidates-dir=*) CAND_DIR=\"${1#*=}\" ;;\n -h|--help) /usr/bin/sed -n '2,28p' \"$0\" | /usr/bin/sed 's/^# \\{0,1\\}//'; exit 0 ;;\n *) /usr/bin/echo \"unknown arg: $1\" >&2; exit 64 ;;\n esac\n shift\ndone\n\nif [[ ! -d \"$CAND_DIR\" ]]; then\n /usr/bin/echo \"candidate dir not found: $CAND_DIR\" >&2\n exit 64\nfi\n\n# Required-sections per status. Pipe-delimited for compatibility with the\n# same scheme transition.sh uses.\nrequired_for() {\n case \"$1\" in\n shortlist) /usr/bin/echo '## Scope|## Files to touch' ;;\n claimed) /usr/bin/echo '## Scope|## Files to touch|## Claim comment draft' ;;\n working) /usr/bin/echo '## Scope|## Files to touch|## Claim comment draft' ;;\n submitted) /usr/bin/echo '## PR title|## PR body|## Test results' ;;\n merged) /usr/bin/echo '## PR title|## PR body|## Test results' ;;\n *) /usr/bin/echo '' ;; # open, dropped, unknown\n esac\n}\n\n# Per-candidate evaluation. Echoes a JSON object per candidate so the table\n# pass and the JSON pass can both consume it.\nevaluate_one() {\n local file=\"$1\"\n local basename\n basename=$(/usr/bin/basename \"$file\")\n local status\n status=$(/usr/bin/awk '/^---$/{fm=!fm?1:2;next} fm==1 && /^status:/{sub(/^status:[[:space:]]*/,\"\"); print; exit}' \"$file\" 2>/dev/null)\n status=\"${status:-unknown}\"\n\n local req\n req=$(required_for \"$status\")\n\n local missing=()\n if [[ -n \"$req\" ]]; then\n IFS='|' read -ra SECTIONS \u003c\u003c\u003c \"$req\"\n for sec in \"${SECTIONS[@]}\"; do\n if ! /usr/bin/grep -qE \"^${sec}\\b\" \"$file\" 2>/dev/null; then\n missing+=(\"$sec\")\n fi\n done\n fi\n\n local missing_json\n if [[ \"${#missing[@]}\" -eq 0 ]]; then\n missing_json='[]'\n else\n missing_json=$(/usr/bin/printf '%s\\n' \"${missing[@]}\" | jq -Rsc 'split(\"\\n\") | map(select(. != \"\"))')\n fi\n\n jq -nc --arg cand \"$basename\" --arg status \"$status\" --argjson missing \"$missing_json\" \\\n '{candidate: $cand, status: $status, missing: $missing, missing_count: ($missing | length)}'\n}\n\n# Walk all candidates, evaluate each, capture rows.\nROWS=()\nEXIT_CODE=0\nshopt -s nullglob\nfor f in \"$CAND_DIR\"/*.md; do\n row=$(evaluate_one \"$f\")\n STATUS=$(/usr/bin/echo \"$row\" | jq -r .status)\n MISSING_COUNT=$(/usr/bin/echo \"$row\" | jq -r .missing_count)\n\n # Apply --status filter\n if [[ -n \"$STATUS_FILTER\" && \"$STATUS\" != \"$STATUS_FILTER\" ]]; then\n continue\n fi\n # Apply --missing-only filter\n if [[ \"$MISSING_ONLY\" -eq 1 && \"$MISSING_COUNT\" -eq 0 ]]; then\n continue\n fi\n\n ROWS+=(\"$row\")\n if [[ \"$MISSING_COUNT\" -gt 0 ]]; then\n EXIT_CODE=1\n fi\ndone\nshopt -u nullglob\n\n# Emit\nif [[ \"$JSON_OUT\" -eq 1 ]]; then\n /usr/bin/printf '%s\\n' \"${ROWS[@]}\" | jq -s '.'\nelse\n /usr/bin/printf '%-55s %-10s %s\\n' 'candidate' 'status' 'missing'\n /usr/bin/printf '%-55s %-10s %s\\n' '---------' '------' '-------'\n if [[ \"${#ROWS[@]}\" -eq 0 ]]; then\n /usr/bin/printf '(no candidates matched filter)\\n'\n else\n for row in \"${ROWS[@]}\"; do\n cand=$(/usr/bin/echo \"$row\" | jq -r .candidate)\n status=$(/usr/bin/echo \"$row\" | jq -r .status)\n missing_str=$(/usr/bin/echo \"$row\" | jq -r '.missing | if length == 0 then \"(none)\" else join(\", \") end')\n /usr/bin/printf '%-55s %-10s %s\\n' \"$cand\" \"$status\" \"$missing_str\"\n done\n\n # Summary footer\n TOTAL=${#ROWS[@]}\n DIRTY=$(/usr/bin/printf '%s\\n' \"${ROWS[@]}\" | jq -s '[.[] | select(.missing_count > 0)] | length')\n /usr/bin/printf '\\n%d candidate(s) shown · %d with missing sections\\n' \"$TOTAL\" \"$DIRTY\"\n fi\nfi\n\nexit \"$EXIT_CODE\"\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":5137,"content_sha256":"65d165c7d9d2f786f2eef89b88a45d031cb28e3da0b2a80962a2ebe99e96ce17"},{"filename":"scripts/researcher-build.sh","content":"#!/usr/bin/env bash\n# researcher-build.sh — build a per-repo dossier of contribution rules.\n#\n# Usage:\n# researcher-build.sh \u003cowner>/\u003crepo> [--no-link-follow] [--stdout]\n#\n# Default behavior: writes the dossier to\n# ~/.contribute-system/research/\u003cowner>__\u003crepo>.md\n# and prints \"→ wrote dossier to \u003cpath>\" to stderr so the caller has feedback.\n#\n# Override the output path via CONTRIBUTE_RESEARCH_DIR env var (the dossier\n# lands at $CONTRIBUTE_RESEARCH_DIR/\u003cowner>__\u003crepo>.md).\n#\n# Pass --stdout to write the dossier to stdout instead — useful for piping\n# (jq, less, diff against an existing dossier).\n#\n# What it pulls:\n# - Repo metadata (stars, default branch, archived, push activity)\n# - Policy file inventory (CONTRIBUTING, CLA, DCO, AI_POLICY, SECURITY,\n# CODEOWNERS, PR template, code of conduct, governance)\n# - Raw CONTRIBUTING.md (with key excerpts)\n# - Depth-1 follow of links inside CONTRIBUTING.md (handbook, AI policy,\n# review guide) — saved with fetched-at timestamps\n# - External merge friendliness (last 90 days)\n# - Bots that auto-review on this repo (sampled from a recent PR)\n# - Convention detection: commit format, branch naming, sign-off, CLA, AI\n#\n# Design notes:\n# - Read-only against GitHub. Never writes to upstream.\n# - Uses temp dir for intermediate files; cleans up on exit.\n# - All gh / curl failures degrade gracefully — partial dossier > nothing.\n\nset -uo pipefail\n\nREPO=\"\"\nNO_LINK_FOLLOW=\"\"\nTO_STDOUT=0\nfor arg in \"$@\"; do\n case \"$arg\" in\n --no-link-follow) NO_LINK_FOLLOW=\"--no-link-follow\" ;;\n --stdout) TO_STDOUT=1 ;;\n -h|--help) /usr/bin/sed -n '2,16p' \"$0\" | /usr/bin/sed 's/^# \\{0,1\\}//'; exit 0 ;;\n -*) /usr/bin/echo \"unknown flag: $arg\" >&2; exit 64 ;;\n *) if [[ -z \"$REPO\" ]]; then REPO=\"$arg\"; else /usr/bin/echo \"extra arg: $arg\" >&2; exit 64; fi ;;\n esac\ndone\n\nif [[ -z \"$REPO\" || \"$REPO\" != */* ]]; then\n /usr/bin/echo \"usage: $0 \u003cowner>/\u003crepo> [--no-link-follow] [--stdout]\" >&2\n exit 64\nfi\n\nif ! gh auth status >/dev/null 2>&1; then\n echo \"gh: not authenticated\" >&2\n exit 65\nfi\n\nTMPDIR=$(/usr/bin/mktemp -d)\ntrap 'rm -rf \"$TMPDIR\"' EXIT\nNOW=$(/usr/bin/date -u +%Y-%m-%dT%H:%M:%SZ)\nNINETY_AGO=$(/usr/bin/date -u -d '90 days ago' +%s)\n\nlog() { /usr/bin/echo \"researcher-build: $*\" >&2; }\n\n# ---- 1. Repo metadata ----\nlog \"[1/8] fetching repo metadata for $REPO\"\nMETA_FILE=\"$TMPDIR/meta.json\"\ngh api \"repos/$REPO\" > \"$META_FILE\" 2>/dev/null || { log \"ERROR: repo $REPO not accessible\"; exit 66; }\nSTARS=$(jq -r '.stargazers_count // 0' \u003c \"$META_FILE\")\nDEFAULT_BRANCH=$(jq -r '.default_branch // \"main\"' \u003c \"$META_FILE\")\nARCHIVED=$(jq -r '.archived // false' \u003c \"$META_FILE\")\nPUSHED_AT=$(jq -r '.pushed_at // \"\"' \u003c \"$META_FILE\")\nLICENSE=$(jq -r '.license.spdx_id // \"UNKNOWN\"' \u003c \"$META_FILE\")\nLANG=$(jq -r '.language // \"unknown\"' \u003c \"$META_FILE\")\nDESC=$(jq -r '.description // \"\"' \u003c \"$META_FILE\" | /usr/bin/head -c 200)\n\n# ---- 2. Policy file inventory ----\nlog \"[2/8] inventorying policy files\"\ndeclare -A POLICY_FILES\n# Map: filename → display name. Try multiple paths per file (root + .github/).\nfor entry in \\\n \"CONTRIBUTING.md:CONTRIBUTING\" \\\n \".github/CONTRIBUTING.md:CONTRIBUTING\" \\\n \"CODE_OF_CONDUCT.md:CODE_OF_CONDUCT\" \\\n \".github/CODE_OF_CONDUCT.md:CODE_OF_CONDUCT\" \\\n \"SECURITY.md:SECURITY\" \\\n \".github/SECURITY.md:SECURITY\" \\\n \"CLA.md:CLA\" \\\n \"CLA.txt:CLA\" \\\n \"DCO.md:DCO\" \\\n \"DCO.txt:DCO\" \\\n \"AI_POLICY.md:AI_POLICY\" \\\n \"GOVERNANCE.md:GOVERNANCE\" \\\n \"SETUP.md:SETUP\" \\\n \"DEVELOPMENT.md:DEVELOPMENT\" \\\n \".github/CODEOWNERS:CODEOWNERS\" \\\n \"CODEOWNERS:CODEOWNERS\" \\\n \".github/PULL_REQUEST_TEMPLATE.md:PR_TEMPLATE\" \\\n \".github/pull_request_template.md:PR_TEMPLATE\" \\\n \"PULL_REQUEST_TEMPLATE.md:PR_TEMPLATE\" \\\n \".github/ISSUE_TEMPLATE.md:ISSUE_TEMPLATE_LEGACY\" \\\n \".github/issue_template.md:ISSUE_TEMPLATE_LEGACY\" \\\n \"ISSUE_TEMPLATE.md:ISSUE_TEMPLATE_LEGACY\"\ndo\n PATH_PART=\"${entry%:*}\"\n KEY=\"${entry##*:}\"\n # Skip if we already found this kind of file\n [[ -n \"${POLICY_FILES[$KEY]:-}\" ]] && continue\n\n # Probe by exit code, not by output. Earlier implementation captured stdout\n # and tested for non-empty — but `gh api` on 404 prints the error-shaped\n # JSON body (\"{\\\"message\\\":\\\"Not Found\\\",...}\") to stdout BEFORE jq runs,\n # and the existing `2>/dev/null` only silences stderr. Net effect: EXISTS\n # was non-empty for every probe, so every candidate file was claimed to\n # exist regardless of reality. Fix: redirect stdout to /dev/null too and\n # rely on the gh exit code as the existence signal.\n if gh api \"repos/$REPO/contents/$PATH_PART\" >/dev/null 2>&1 ; then\n POLICY_FILES[$KEY]=\"$PATH_PART\"\n fi\ndone\n\n# Also probe `docs/` subdir for projects (like secureblue) that house policy\n# docs there instead of at the repo root. Only fills in slots that are still\n# empty after the root + .github/ probes above.\nfor entry in \\\n \"docs/CONTRIBUTING.md:CONTRIBUTING\" \\\n \"docs/CODE_OF_CONDUCT.md:CODE_OF_CONDUCT\" \\\n \"docs/SECURITY.md:SECURITY\" \\\n \"docs/CLA.md:CLA\" \\\n \"docs/DCO.md:DCO\" \\\n \"docs/AI_POLICY.md:AI_POLICY\" \\\n \"docs/GOVERNANCE.md:GOVERNANCE\" \\\n \"docs/SETUP.md:SETUP\" \\\n \"docs/DEVELOPMENT.md:DEVELOPMENT\" \\\n \"docs/PULL_REQUEST_TEMPLATE.md:PR_TEMPLATE\"\ndo\n PATH_PART=\"${entry%:*}\"\n KEY=\"${entry##*:}\"\n [[ -n \"${POLICY_FILES[$KEY]:-}\" ]] && continue\n if gh api \"repos/$REPO/contents/$PATH_PART\" >/dev/null 2>&1 ; then\n POLICY_FILES[$KEY]=\"$PATH_PART\"\n fi\ndone\n\n# ---- 2b. Issue template directory inventory (.github/ISSUE_TEMPLATE/) ----\n# A repo can have a single legacy template (handled above) OR a directory of\n# templates (the modern pattern: bug-report.md, feature-request.md, design.md, etc.).\n# We list every .md file in that dir so SKILL.md can pick the right one when\n# drafting a Design Issue or feature request.\nlog \"[2b/8] inventorying issue template directory\"\ndeclare -a ISSUE_TEMPLATES=()\nISSUE_TEMPLATE_DIR_LISTING=$(gh api \"repos/$REPO/contents/.github/ISSUE_TEMPLATE\" 2>/dev/null \\\n | jq -r 'if type == \"array\" then .[] | select(.type == \"file\" and (.name | endswith(\".md\") or endswith(\".yml\") or endswith(\".yaml\"))) | .name else empty end' 2>/dev/null \\\n || /usr/bin/echo \"\")\nif [[ -n \"$ISSUE_TEMPLATE_DIR_LISTING\" ]] ; then\n while read -r TPL ; do\n [[ -n \"$TPL\" ]] && ISSUE_TEMPLATES+=(\"$TPL\")\n done \u003c\u003c\u003c \"$ISSUE_TEMPLATE_DIR_LISTING\"\nfi\n\n# ---- 3. Fetch CONTRIBUTING raw + extract links ----\nlog \"[3/8] fetching + parsing CONTRIBUTING\"\nCONTRIB_RAW=\"$TMPDIR/contributing.md\"\nCONTRIB_PATH=\"${POLICY_FILES[CONTRIBUTING]:-}\"\nif [[ -n \"$CONTRIB_PATH\" ]] ; then\n gh api \"repos/$REPO/contents/$CONTRIB_PATH\" --jq '.content' 2>/dev/null \\\n | /usr/bin/base64 -d > \"$CONTRIB_RAW\" 2>/dev/null \\\n || /usr/bin/printf '' > \"$CONTRIB_RAW\"\nfi\nCONTRIB_BYTES=$(/usr/bin/wc -c \u003c \"$CONTRIB_RAW\" 2>/dev/null || echo 0)\n\n# Extract http(s) links from CONTRIBUTING — for depth-1 follow\nLINKS_FILE=\"$TMPDIR/links.txt\"\nif [[ -s \"$CONTRIB_RAW\" ]] ; then\n /usr/bin/grep -oE 'https?://[A-Za-z0-9._/?&=#%~+:-]+' \"$CONTRIB_RAW\" \\\n | /usr/bin/sed 's/[).,;:!]*$//' \\\n | /usr/bin/sort -u > \"$LINKS_FILE\"\nelse\n : > \"$LINKS_FILE\"\nfi\n\n# ---- 4. Follow depth-1 links — aggressively (per user direction 2026-05-02) ----\n# Init array empty (set -u trip otherwise on `${#FOLLOWED_LINKS[@]}`).\ndeclare -a FOLLOWED_LINKS=()\ndeclare -A LINK_TITLES=()\nif [[ \"$NO_LINK_FOLLOW\" != \"--no-link-follow\" && -s \"$LINKS_FILE\" ]] ; then\n log \"[4/8] depth-1 follow on links (skip social/external)\"\n while read -r URL ; do\n # Skip social/external (twitter, x, discord, mailto, slack, youtube, linkedin, etc.)\n if /usr/bin/echo \"$URL\" | /usr/bin/grep -qiE 'twitter\\.com|x\\.com|discord\\.gg|discord\\.com|mailto:|slack\\.com|youtube\\.com|youtu\\.be|linkedin\\.com|facebook\\.com|instagram\\.com'; then\n continue\n fi\n # Skip GitHub anchor-only URLs (already covered by repo file scan)\n [[ \"$URL\" == *\"#\"* ]] && continue\n # Cap at 15 follows to keep cost bounded (was 5; user wanted aggressive)\n [[ \"${#FOLLOWED_LINKS[@]}\" -ge 15 ]] && break\n BODY=$(/usr/bin/curl -sSL --max-time 10 -A 'researcher-build/1.0' \"$URL\" 2>/dev/null | /usr/bin/head -c 50000)\n if [[ -n \"$BODY\" && \"$BODY\" != *\"404: Not Found\"* ]] ; then\n FOLLOWED_LINKS+=(\"$URL\")\n # Extract \u003ctitle> tag (case-insensitive, strip whitespace, cap length).\n # Falls back to the URL when no title element present.\n TITLE=$(/usr/bin/printf '%s' \"$BODY\" \\\n | /usr/bin/grep -ioE '\u003ctitle[^>]*>[^\u003c]*\u003c/title>' \\\n | /usr/bin/head -1 \\\n | /usr/bin/sed -E 's|\u003ctitle[^>]*>||I; s|\u003c/title>||I; s/^[[:space:]]+//; s/[[:space:]]+$//' \\\n | /usr/bin/cut -c1-120)\n LINK_TITLES[$URL]=\"${TITLE:-$URL}\"\n /usr/bin/printf '%s' \"$BODY\" > \"$TMPDIR/link-$(/usr/bin/echo \"$URL\" | /usr/bin/md5sum | /usr/bin/cut -c1-8).html\"\n fi\n done \u003c \"$LINKS_FILE\"\nelse\n log \"[4/8] link follow disabled\"\nfi\n\n# ---- 5. External merge friendliness (last 90d) ----\nlog \"[5/8] computing merge friendliness\"\nPRS_FILE=\"$TMPDIR/prs.json\"\ngh api \"repos/$REPO/pulls?state=closed&sort=updated&direction=desc&per_page=100\" > \"$PRS_FILE\" 2>/dev/null \\\n || /usr/bin/printf '[]' > \"$PRS_FILE\"\nEXT_COUNT=$(jq --arg cutoff \"$NINETY_AGO\" '\n [.[]\n | select(.merged_at != null)\n | select((.merged_at|fromdateiso8601) >= ($cutoff|tonumber))\n | select(.author_association == \"CONTRIBUTOR\" or .author_association == \"FIRST_TIME_CONTRIBUTOR\" or .author_association == \"FIRST_TIMER\" or .author_association == \"NONE\")\n | select(.user.type != \"Bot\")\n ] | length' \u003c \"$PRS_FILE\" 2>/dev/null || echo 0)\nLAST_EXT=$(jq -r '\n [.[]\n | select(.merged_at != null)\n | select(.author_association == \"CONTRIBUTOR\" or .author_association == \"FIRST_TIME_CONTRIBUTOR\" or .author_association == \"NONE\")\n | select(.user.type != \"Bot\")\n ] | sort_by(.merged_at) | reverse | .[0].merged_at // \"\"\n | if . == \"\" then \"(none)\" else .[0:10] end' \u003c \"$PRS_FILE\" 2>/dev/null || echo \"(err)\")\n\n# ---- 6. Bot detection (sample most-recently-updated merged PR) ----\nlog \"[6/8] sampling review bots from a recent merged PR\"\nRECENT_PR=$(jq -r '[.[] | select(.merged_at != null)] | .[0].number // empty' \u003c \"$PRS_FILE\" 2>/dev/null)\ndeclare -a REVIEW_BOTS=() # init empty (set -u)\nif [[ -n \"$RECENT_PR\" ]] ; then\n REVIEWERS=$(gh pr view \"$RECENT_PR\" --repo \"$REPO\" --json reviews,comments \\\n --jq '([.reviews[].author.login] + [.comments[].author.login]) | unique' 2>/dev/null)\n if [[ -n \"$REVIEWERS\" ]] ; then\n while read -r LOGIN ; do\n # Bot heuristic: ends in -bot, starts with app/, contains \"bot\" or \"copilot\" or \"greptile\" or \"coderabbit\" or \"renovate\" or \"dependabot\"\n if /usr/bin/echo \"$LOGIN\" | /usr/bin/grep -qiE 'bot$|^app/|copilot|greptile|coderabbit|renovate|dependabot|github-actions|deploy-status' ; then\n REVIEW_BOTS+=(\"$LOGIN\")\n fi\n done \u003c \u003c(/usr/bin/echo \"$REVIEWERS\" | jq -r '.[]')\n fi\nfi\n\n# ---- 7. Convention detection ----\nlog \"[7/8] detecting conventions from CONTRIBUTING + linked pages\"\nALL_TEXT=\"$TMPDIR/all-text.txt\"\n{ /usr/bin/cat \"$CONTRIB_RAW\" 2>/dev/null ; /usr/bin/cat \"$TMPDIR\"/link-*.html 2>/dev/null ; } > \"$ALL_TEXT\"\n\ndetect() { /usr/bin/grep -qiE \"$1\" \"$ALL_TEXT\" 2>/dev/null && echo \"true\" || echo \"false\" ; }\n\nCLA_REQUIRED=$(detect '\\b(CLA|contributor license agreement)\\b')\nDCO_REQUIRED=$(detect '\\b(DCO|developer certificate of origin|signed-off-by|sign-off)\\b')\nAI_DISCLOSURE=$(detect '\\b(AI[-_ ]?(generated|assisted|policy|disclos)|Claude|Copilot|ChatGPT|LLM)\\b')\nCONVENTIONAL_COMMITS=$(detect '\\bconventional commits?\\b|^[a-z]+\\([a-z-]+\\): ')\nETIQUETTE_REQUIRED=$(detect 'comment on the issue|request assignment|let.{0,20}know you.{0,5}working|don.{0,3}t.{0,10}assign')\n# Try to grab a test command pattern\nTEST_CMD=$(/usr/bin/grep -ioE '(make|cargo|pnpm|yarn|npm|pytest|sbt|go) (test|test-cov|lint|format-check|typecheck|check)[^`\\n]*' \"$ALL_TEXT\" 2>/dev/null | /usr/bin/head -1 | /usr/bin/sed 's/[`\"]//g' | /usr/bin/cut -c1-100)\n\n# ---- 8. Emit the dossier ----\nlog \"[8/8] emitting dossier ($CONTRIB_BYTES bytes CONTRIBUTING, ${#FOLLOWED_LINKS[@]} links followed, $EXT_COUNT ext merges)\"\n\npolicy_list_yaml() {\n for KEY in \"${!POLICY_FILES[@]}\" ; do\n /usr/bin/printf ' - %s: %s\\n' \"$KEY\" \"${POLICY_FILES[$KEY]}\"\n done | /usr/bin/sort\n}\n\nbot_list_yaml() {\n if [[ \"${#REVIEW_BOTS[@]}\" -eq 0 ]] ; then\n /usr/bin/printf ' - (none detected)\\n'\n else\n for B in \"${REVIEW_BOTS[@]}\" ; do\n /usr/bin/printf ' - %s\\n' \"$B\"\n done | /usr/bin/sort -u\n fi\n}\n\nissue_templates_section() {\n if [[ \"${#ISSUE_TEMPLATES[@]}\" -eq 0 ]] ; then\n if [[ -n \"${POLICY_FILES[ISSUE_TEMPLATE_LEGACY]:-}\" ]] ; then\n local TPL=\"${POLICY_FILES[ISSUE_TEMPLATE_LEGACY]}\"\n /usr/bin/printf -- '- Legacy single-template at `%s` ([view](https://github.com/%s/blob/%s/%s)) — fetch this and fill it in.\\n' \\\n \"$TPL\" \"$REPO\" \"$DEFAULT_BRANCH\" \"$TPL\"\n else\n /usr/bin/echo \"_No issue templates detected. The repo accepts free-form issue bodies. Fall back to a generic Design MD shape (problem / proposal / diff preview / test results)._\"\n fi\n else\n /usr/bin/echo \"_When opening a Design Issue / bug / feature request, **fetch the matching template first** and fill in its sections — do NOT replace the structure. The repo's reviewers expect this shape._\"\n /usr/bin/echo\n for T in \"${ISSUE_TEMPLATES[@]}\" ; do\n /usr/bin/printf -- '- `%s` — [view](https://github.com/%s/blob/%s/.github/ISSUE_TEMPLATE/%s)\\n' \\\n \"$T\" \"$REPO\" \"$DEFAULT_BRANCH\" \"$T\"\n done\n /usr/bin/echo\n /usr/bin/echo \"**To fetch a template body for filling in:**\"\n /usr/bin/echo\n /usr/bin/echo '```bash'\n /usr/bin/printf 'gh api \"repos/%s/contents/.github/ISSUE_TEMPLATE/\u003cname>\" --jq .content | base64 -d\\n' \"$REPO\"\n /usr/bin/echo '```'\n fi\n}\n\nlinked_sources_yaml() {\n if [[ \"${#FOLLOWED_LINKS[@]}\" -eq 0 ]] ; then\n /usr/bin/printf ' - (none followed)\\n'\n else\n for U in \"${FOLLOWED_LINKS[@]}\" ; do\n T=\"${LINK_TITLES[$U]:-$U}\"\n # Escape double quotes for YAML safety.\n T_ESC=\"${T//\\\"/\\\\\\\"}\"\n /usr/bin/printf ' - { url: \"%s\", title: \"%s\", fetched_at: \"%s\" }\\n' \"$U\" \"$T_ESC\" \"$NOW\"\n done\n fi\n}\n\n# Resolve the output path. Default: ~/.contribute-system/research/\u003cowner>__\u003crepo>.md.\n# Override via CONTRIBUTE_RESEARCH_DIR. The path is stable per repo so refresh\n# overwrites the previous dossier — engineer-curated sections (## Pet peeves,\n# ## Failure log, ## Notes) survive because the agent layer copies them\n# forward; this script does not preserve them across refresh.\nRESEARCH_DIR=\"${CONTRIBUTE_RESEARCH_DIR:-$HOME/.contribute-system/research}\"\nOUTPUT_FILE=\"$RESEARCH_DIR/$(/usr/bin/echo \"$REPO\" | /usr/bin/sed 's|/|__|').md\"\n\n# When not in --stdout mode, redirect all subsequent stdout to the dossier\n# file. The two heredocs (DOSSIER and TAIL) and the awk excerpt block in\n# between all write to stdout — `exec` here points stdout at the file once,\n# so every subsequent emission lands in the right place.\nif [[ \"$TO_STDOUT\" -eq 0 ]]; then\n /usr/bin/mkdir -p \"$RESEARCH_DIR\"\n exec > \"$OUTPUT_FILE\"\nfi\n\n/usr/bin/cat \u003c\u003cDOSSIER\n---\nrepo: $REPO\nlast_refreshed: $NOW\ndefault_branch: $DEFAULT_BRANCH\narchived: $ARCHIVED\nstars: $STARS\nlanguage: $LANG\nlicense: $LICENSE\nlast_pushed_at: $PUSHED_AT\nexternal_merge_rate_90d: $EXT_COUNT\nlast_external_merge_at: $LAST_EXT\ncla_required: $CLA_REQUIRED\ndco_required: $DCO_REQUIRED\nai_disclosure_required: $AI_DISCLOSURE\nconventional_commits: $CONVENTIONAL_COMMITS\netiquette_comment_required: $ETIQUETTE_REQUIRED\nlocal_check_command: \"${TEST_CMD:-(not detected)}\"\npolicy_files:\n$(policy_list_yaml)\nissue_templates:\n$(if [[ \"${#ISSUE_TEMPLATES[@]}\" -eq 0 ]] ; then\n /usr/bin/printf ' - (none — repo has no .github/ISSUE_TEMPLATE/ dir)\\n'\n else\n for T in \"${ISSUE_TEMPLATES[@]}\" ; do\n /usr/bin/printf -- ' - { name: \"%s\", url: \"https://github.com/%s/blob/%s/.github/ISSUE_TEMPLATE/%s\" }\\n' \"$T\" \"$REPO\" \"$DEFAULT_BRANCH\" \"$T\"\n done\n fi)\nreview_bots:\n$(bot_list_yaml)\nlinked_sources:\n$(linked_sources_yaml)\n---\n\n# $REPO — rules of engagement\n\n> Auto-generated by researcher-build.sh on $NOW. Refresh with \\`@researcher refresh $REPO\\` (or any time CONTRIBUTING changes upstream).\n\n## TL;DR\n\n- $STARS ★ · $LANG · license: $LICENSE · default branch: \\`$DEFAULT_BRANCH\\`\n- External merge velocity: **$EXT_COUNT** PRs in last 90d (last: $LAST_EXT)\n- CLA: **$CLA_REQUIRED** · DCO: **$DCO_REQUIRED** · AI disclosure required: **$AI_DISCLOSURE**\n- Etiquette comment required before claiming: **$ETIQUETTE_REQUIRED**\n- Conventional Commits style: **$CONVENTIONAL_COMMITS**\n- Local pre-PR command (detected): \\`${TEST_CMD:-(none — read CONTRIBUTING)}\\`\n\n## Description\n\n$DESC\n\n## Policy file inventory\n\n$(for KEY in \"${!POLICY_FILES[@]}\" ; do /usr/bin/printf -- '- **%s** → \\`%s\\` ([view](https://github.com/%s/blob/%s/%s))\\n' \"$KEY\" \"${POLICY_FILES[$KEY]}\" \"$REPO\" \"$DEFAULT_BRANCH\" \"${POLICY_FILES[$KEY]}\" ; done | /usr/bin/sort)\n\n## CONTRIBUTING.md — key excerpts\n\nDOSSIER\n\n# Excerpt the most actionable headed sections from CONTRIBUTING\nif [[ -s \"$CONTRIB_RAW\" ]] ; then\n # Pull sections with names that hint at \"things you must do\"\n /usr/bin/awk '\n /^##+ / { keep = 0 }\n /^##+ .*([Cc]hecklist|[Pp]re-PR|[Bb]efore|[Tt]esting|[Tt]est|[Ww]orkflow|[Pp]ull [Rr]equest|[Bb]ranch|[Cc]ommit|[Ss]tyle|[Ff]ormat|[Aa]I|[Cc]ode [Qq]uality|[Cc]onvention|[Hh]ow to [Cc]ontribute|[Rr]eview|[Cc]ontribution [Ff]low|[Rr]ules)/ { keep = 1 }\n keep { print }\n ' \"$CONTRIB_RAW\" | /usr/bin/head -150\n /usr/bin/echo\n /usr/bin/echo \"_(excerpt only — full file: https://github.com/$REPO/blob/$DEFAULT_BRANCH/$CONTRIB_PATH)_\"\nelse\n /usr/bin/echo \"_No CONTRIBUTING.md found at the repo root or .github/. Read CODE_OF_CONDUCT.md and the PR template instead._\"\nfi\n\n/usr/bin/cat \u003c\u003cTAIL\n\n## Linked sources (depth-1 follow)\n\n$(if [[ \"${#FOLLOWED_LINKS[@]}\" -eq 0 ]] ; then\n /usr/bin/echo \"_No links followed (none found in CONTRIBUTING)._\"\n else\n for U in \"${FOLLOWED_LINKS[@]}\" ; do\n T=\"${LINK_TITLES[$U]:-$U}\"\n /usr/bin/printf -- '- [%s](%s) — fetched %s\\n' \"$T\" \"$U\" \"$NOW\"\n done\n fi)\n\n## Issue templates (use the matching one when opening a Design Issue or bug)\n\n$(issue_templates_section)\n\n## Bots that auto-review on this repo (sampled from PR #${RECENT_PR:-?})\n\n$(bot_list_yaml | /usr/bin/sed 's/^ - /- /')\n\n## Pet peeves & known triggers\n\n_Specific things that get PRs closed at THIS repo. Manually + LLM-curated. Survives refresh — researcher does NOT auto-populate this section. Add an entry every time you observe a pet peeve in the wild._\n\n- _(no entries yet — populate as observations land)_\n\n## Failure log\n\n_Chronological record of past closures with reasons at this repo. Auto-appended on \\`status: dropped\\` transitions; never overwritten on refresh._\n\n## Notes\n\n_Free-form area for the human to leave per-repo intuition. Survives refresh._\n\nTAIL\n\n# Success message goes to stderr so it's visible when stdout was redirected\n# to the file. In --stdout mode, the user sees it inline before the dossier\n# content (no — actually after, since heredocs flush before the script exits).\nif [[ \"$TO_STDOUT\" -eq 0 ]]; then\n log \"→ wrote dossier to $OUTPUT_FILE\"\nfi\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":19292,"content_sha256":"fa7691613b130727fd3a5c21594371566ca003633badfc423f7894bdb163efc3"},{"filename":"scripts/test-known-traps.sh","content":"#!/usr/bin/env bash\n# test-known-traps.sh — regression tests for the failure modes that motivated\n# the gate system. Each test constructs a synthetic candidate file matching a\n# known real-world trap and asserts the expected gate fires with the expected\n# severity.\n#\n# Usage: test-known-traps.sh [--verbose]\n# Exit 0: all tests pass. Exit 1: any test fails.\n#\n# Tests:\n# 1. PostHog #55412 — already-shipped issue (gate A02 must BLOCK)\n# 2. opensre #1129 — assigned issue (gate A01 must BLOCK)\n# 3. closed issue — closed-not-planned (gate A05 must BLOCK)\n# 4. clean candidate — no traps (transition must PASS or only SKIP)\n\nset -uo pipefail\n\nVERBOSE=\"${1:-}\"\nSYS=\"$HOME/.contribute-system\"\nTMPDIR=$(/usr/bin/mktemp -d)\ntrap 'rm -rf \"$TMPDIR\"' EXIT\n\nPASS=0\nFAIL=0\nRESULTS=()\n\nred() { /usr/bin/printf '\\033[31m%s\\033[0m' \"$1\"; }\ngreen() { /usr/bin/printf '\\033[32m%s\\033[0m' \"$1\"; }\nyellow() { /usr/bin/printf '\\033[33m%s\\033[0m' \"$1\"; }\n\n# Helper: run transition.sh in dry-run mode against a candidate.\n# Returns the verdict JSON on stdout.\nrun_transition() {\n local action=\"$1\" candidate=\"$2\"\n \"$SYS/bin/transition.sh\" \"$action\" \"$candidate\" --dry-run 2>/dev/null \\\n | /usr/bin/grep -E '^\\{.*\"verdict\"' \\\n | /usr/bin/tail -1\n}\n\n# Helper: assert a specific gate ID appears with a specific severity in the\n# verbose stderr output of transition.sh.\ngate_fired_with_severity() {\n local action=\"$1\" candidate=\"$2\" gate_id=\"$3\" expected_sev=\"$4\"\n local stderr_output\n stderr_output=$(\"$SYS/bin/transition.sh\" \"$action\" \"$candidate\" --dry-run 2>&1 >/dev/null)\n if [[ \"$VERBOSE\" == \"--verbose\" ]]; then\n /usr/bin/printf '\\n--- transition output for %s on %s ---\\n%s\\n---\\n' \\\n \"$action\" \"$(/usr/bin/basename \"$candidate\")\" \"$stderr_output\" >&2\n fi\n /usr/bin/echo \"$stderr_output\" | /usr/bin/grep -qE \"\\[$gate_id\\].*$expected_sev\"\n}\n\nassert_gate() {\n local name=\"$1\" action=\"$2\" candidate=\"$3\" gate_id=\"$4\" expected_sev=\"$5\"\n /usr/bin/printf ' %-50s ' \"$name\"\n if gate_fired_with_severity \"$action\" \"$candidate\" \"$gate_id\" \"$expected_sev\"; then\n green \"PASS\"; /usr/bin/echo\n PASS=$((PASS + 1))\n RESULTS+=(\"PASS: $name\")\n else\n red \"FAIL\"; /usr/bin/echo\n /usr/bin/printf ' expected gate %s severity %s — not found\\n' \"$gate_id\" \"$expected_sev\" >&2\n FAIL=$((FAIL + 1))\n RESULTS+=(\"FAIL: $name (expected $gate_id $expected_sev)\")\n fi\n}\n\n# Build a synthetic candidate file. The frontmatter is what gates read.\n# Body is unused for these tests.\nmake_candidate() {\n local repo=\"$1\" issue=\"$2\" status=\"${3:-open}\" path\n path=\"$TMPDIR/synth_$(/usr/bin/echo \"$repo\" | /usr/bin/tr '/' '_')_$issue.md\"\n /usr/bin/cat > \"$path\" \u003c\u003cEOF\n---\ndiscovered_at: 2026-05-03T00:00:00Z\nrepo: $repo\nissue_number: $issue\nissue_url: https://github.com/$repo/issues/$issue\nstar_tier: mainstream\nstar_count: 1000\nrepo_lang: TypeScript\ncompeting_prs: 0\nprimary_label: bug\nscout_score: 0.5\nstatus: $status\nlast_refreshed: 2026-05-03T00:00:00Z\n---\n\n# $repo #$issue — synthetic regression test candidate\nEOF\n /usr/bin/echo \"$path\"\n}\n\n/usr/bin/printf '\\n=== contributing-clanker regression tests — known traps ===\\n\\n'\n\n# ---- TEST 1: PostHog #55412 trap (already-shipped) ----\n# The plan documents this as the originating trap for gate A02.\n# Issue #55412 was closed by merged PR #57145. We need A02 to BLOCK.\n/usr/bin/printf 'Test 1: PostHog #55412 already-shipped trap\\n'\nT1=$(make_candidate \"PostHog/posthog\" 55412)\nassert_gate \" A02 already-shipped MUST BLOCK\" \\\n \"shortlist→claimed\" \"$T1\" \"A02\" \"BLOCK\"\n\n# ---- TEST 2: Tracer-Cloud opensre #1129 trap (assigned) ----\n# This issue was assigned to unKnownNG when scout found it. A01 must BLOCK.\n/usr/bin/printf 'Test 2: Tracer-Cloud opensre #1129 assigned trap\\n'\nT2=$(make_candidate \"Tracer-Cloud/opensre\" 1129)\nassert_gate \" A01 already-assigned MUST BLOCK\" \\\n \"shortlist→claimed\" \"$T2\" \"A01\" \"BLOCK\"\n\n# ---- TEST 3: lingdojo/kana-dojo #15441 (closed not_planned) ----\n# Verified empirically 2026-05-03 — A05 BLOCKs on closed issues.\n/usr/bin/printf 'Test 3: lingdojo/kana-dojo #15441 closed-issue trap\\n'\nT3=$(make_candidate \"lingdojo/kana-dojo\" 15441)\nassert_gate \" A05 issue-still-open MUST BLOCK\" \\\n \"shortlist→claimed\" \"$T3\" \"A05\" \"BLOCK\"\n\n# ---- TEST 4: clean candidate (no traps) ----\n# Pick a repo+issue that's currently OPEN and unassigned. We use a synthetic\n# very-high issue number that won't match any real shipped PR.\n# Note: this test runs against a real repo via gh; if the issue doesn't exist,\n# the gates will produce ambiguous results. We pick something neutral.\n/usr/bin/printf 'Test 4: clean candidate (open, unassigned, unshipped)\\n'\nT4=$(make_candidate \"lingdojo/kana-dojo\" 99999999)\n# A01 should PASS or A03/A04/A05 — we just want NO unexpected BLOCKs from A1\n# (which is the most common false-positive risk).\nassert_gate \" A01 should NOT block (issue 99999999 nonexistent)\" \\\n \"shortlist→claimed\" \"$T4\" \"A01\" \"(PASS|SKIP)\"\n\n/usr/bin/echo\n/usr/bin/printf '=== summary: %s passed · %s failed ===\\n\\n' \\\n \"$(green \"$PASS\")\" \"$([ \"$FAIL\" -gt 0 ] && red \"$FAIL\" || /usr/bin/echo 0)\"\n\nif [[ \"$FAIL\" -gt 0 ]]; then\n /usr/bin/printf 'Failures:\\n'\n for R in \"${RESULTS[@]}\"; do\n [[ \"$R\" == FAIL:* ]] && /usr/bin/printf ' %s\\n' \"$R\"\n done\n exit 1\nfi\n\nexit 0\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":5303,"content_sha256":"f06d847d38d572a571aee673c58da8760bfaf8f15257a212c09261e7bb5e7cae"},{"filename":"scripts/test-override-audit.sh","content":"#!/usr/bin/env bash\n# test-override-audit.sh — regression test for the override audit trail.\n#\n# Promise: every `transition.sh --override-gate \u003cID> \"reason\"` call\n# 1. appends a `gate_override` event to ~/.contribute-system/log.jsonl\n# 2. writes the override into the candidate's frontmatter overrides: array\n# 3. allows the transition to proceed (BLOCK gate becomes effective-PASS)\n#\n# This validates the audit trail that lets engineers see WHY a gate was bypassed.\n#\n# Usage: test-override-audit.sh [--verbose]\n# Exit 0: all assertions hold. Exit 1: any failure.\n\nset -uo pipefail\n\nVERBOSE=\"${1:-}\"\nSYS=\"$HOME/.contribute-system\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nTMPDIR=$(/usr/bin/mktemp -d)\ntrap 'rm -rf \"$TMPDIR\"' EXIT\n\nPASS=0\nFAIL=0\nred() { /usr/bin/printf '\\033[31m%s\\033[0m' \"$1\"; }\ngreen() { /usr/bin/printf '\\033[32m%s\\033[0m' \"$1\"; }\n\nassert() {\n local name=\"$1\" expr=\"$2\"\n /usr/bin/printf ' %-60s ' \"$name\"\n if eval \"$expr\" >/dev/null 2>&1; then\n green \"PASS\"; /usr/bin/echo\n PASS=$((PASS + 1))\n else\n red \"FAIL\"; /usr/bin/echo\n [[ \"$VERBOSE\" == \"--verbose\" ]] && /usr/bin/printf ' expr: %s\\n' \"$expr\" >&2\n FAIL=$((FAIL + 1))\n fi\n}\n\n# Build a synthetic candidate (closed issue, will trigger A05 BLOCK)\nSYNTH=\"$TMPDIR/synth-override.md\"\n/usr/bin/cat > \"$SYNTH\" \u003c\u003c'EOF'\n---\ndiscovered_at: 2026-05-03T00:00:00Z\nrepo: lingdojo/kana-dojo\nissue_number: 15441\nissue_url: https://github.com/lingdojo/kana-dojo/issues/15441\nstar_tier: mainstream\nstar_count: 2231\nrepo_lang: TypeScript\ncompeting_prs: 0\nprimary_label: bug\nscout_score: 0.5\nstatus: open\nlast_refreshed: 2026-05-03T00:00:00Z\n---\n\n# synthetic — override audit test\nEOF\n\nLOG_BEFORE_LINES=$(/usr/bin/wc -l \u003c \"$SYS/log.jsonl\" 2>/dev/null || /usr/bin/echo \"0\")\n\n/usr/bin/printf '\\n=== override audit regression ===\\n\\n'\n\n# --- TEST 1: dry-run override pre-records WITHOUT mutating candidate ---\n\"$SCRIPT_DIR/transition.sh\" \"shortlist→claimed\" \"$SYNTH\" \\\n --dry-run \\\n --override-gate A05 \"regression test: dry-run should not write\" \\\n >/dev/null 2>&1\n\nassert \"1. dry-run override does NOT write 'overrides:' to candidate\" \\\n '! /usr/bin/grep -q \"^overrides:\" \"$SYNTH\"'\n\n# --- TEST 2: real override (no --dry-run) writes to candidate frontmatter ---\n\"$SCRIPT_DIR/transition.sh\" \"shortlist→claimed\" \"$SYNTH\" \\\n --override-gate A05 \"test 2: real override audit\" \\\n >/dev/null 2>&1 || true # gate block exit-code may be 0 or 1; we don't care\n\nassert \"2. real override DOES write 'overrides:' to candidate\" \\\n '/usr/bin/grep -q \"^overrides:\" \"$SYNTH\"'\n\nassert \"3. override entry contains gate ID A05\" \\\n '/usr/bin/grep -q \"gate: A05\" \"$SYNTH\"'\n\nassert \"4. override entry contains the reason text\" \\\n '/usr/bin/grep -q \"test 2: real override audit\" \"$SYNTH\"'\n\n# --- TEST 3: log.jsonl gets gate_override event ---\nLOG_AFTER_LINES=$(/usr/bin/wc -l \u003c \"$SYS/log.jsonl\" 2>/dev/null || /usr/bin/echo \"0\")\n\nassert \"5. log.jsonl grew (new event lines appended)\" \\\n '[[ \"$LOG_AFTER_LINES\" -gt \"$LOG_BEFORE_LINES\" ]]'\n\nassert \"6. log.jsonl contains a gate_override event for A05\" \\\n '/usr/bin/tail -20 \"$SYS/log.jsonl\" | /usr/bin/grep -q \"\\\"event\\\":\\\"gate_override\\\".*\\\"gate\\\":\\\"A05\\\"\"'\n\n/usr/bin/echo\n/usr/bin/printf '=== summary: %s passed · %s failed ===\\n\\n' \\\n \"$(green \"$PASS\")\" \"$([ \"$FAIL\" -gt 0 ] && red \"$FAIL\" || /usr/bin/echo 0)\"\n\n[[ \"$FAIL\" -eq 0 ]] || exit 1\nexit 0\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3407,"content_sha256":"f54cc3ef2ccd1118abb92c83b3426b2dc5220d335ce76c880b8d9fd7e2af03f6"},{"filename":"scripts/test-plug-in.sh","content":"#!/usr/bin/env bash\n# test-plug-in.sh — regression test for the gate-runner plug-in / discovery contract.\n#\n# Promise: gate-runner discovers gate scripts by glob from\n# 1. its own scripts/gates/ dir (bundled canonical set)\n# 2. ~/.contribute-system/gates/ (user-override dir)\n# Drop a new executable .sh in either dir and the runner picks it up\n# automatically — no orchestrator changes needed.\n#\n# This is the load-bearing property that makes gates pluggable.\n#\n# Usage: test-plug-in.sh [--verbose]\n# Exit 0: all assertions hold. Exit 1: any failure.\n\nset -uo pipefail\n\nVERBOSE=\"${1:-}\"\nSYS=\"$HOME/.contribute-system\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nUSER_GATES=\"$SYS/gates\"\n\n# Plant a no-op gate in the user-override dir. Use a phase letter that runs\n# at shortlist→claimed (phase A). gate-runner globs phase-letter-prefix.sh.\nPLUGIN_GATE_NAME=\"azz99-plugin-test-$.sh\"\nPLUGIN_GATE=\"$USER_GATES/$PLUGIN_GATE_NAME\"\nTMPDIR=$(/usr/bin/mktemp -d)\ntrap 'rm -f \"$PLUGIN_GATE\"; rm -rf \"$TMPDIR\"' EXIT\n\n# Construct the no-op gate. Just emits PASS.\n/usr/bin/cat > \"$PLUGIN_GATE\" \u003c\u003c'EOF'\n#!/usr/bin/env bash\n# Catalog: AZZ99 — plug-in regression test no-op gate\nsource \"$(dirname \"$0\")/lib/preamble.sh\"\ngate_read_input\ngate_pass \"no-op test gate discovered via plug-in mechanism\"\nEOF\n/usr/bin/chmod +x \"$PLUGIN_GATE\"\n\n# Build a synthetic candidate (won't trigger any real gate findings)\nSYNTH=\"$TMPDIR/synth-plugin.md\"\n/usr/bin/cat > \"$SYNTH\" \u003c\u003c'EOF'\n---\ndiscovered_at: 2026-05-03T00:00:00Z\nrepo: example-org/example-repo\nissue_number: 1\nissue_url: https://github.com/example-org/example-repo/issues/1\nstar_tier: mainstream\nstar_count: 1000\nrepo_lang: TypeScript\ncompeting_prs: 0\nprimary_label: bug\nscout_score: 0.5\nstatus: open\nlast_refreshed: 2026-05-03T00:00:00Z\n---\n\n# synthetic — plug-in test\nEOF\n\nPASS=0\nFAIL=0\nred() { /usr/bin/printf '\\033[31m%s\\033[0m' \"$1\"; }\ngreen() { /usr/bin/printf '\\033[32m%s\\033[0m' \"$1\"; }\n\n/usr/bin/printf '\\n=== gate-runner plug-in discovery regression ===\\n\\n'\n\n# Run transition with stderr captured (gate output goes to stderr)\nTRANSITION_OUT=$(\"$SCRIPT_DIR/transition.sh\" \"shortlist→claimed\" \"$SYNTH\" --dry-run 2>&1 || true)\n\nif [[ \"$VERBOSE\" == \"--verbose\" ]] ; then\n /usr/bin/printf 'transition output:\\n%s\\n\\n' \"$TRANSITION_OUT\" >&2\nfi\n\n# Test 1: the plug-in gate appears in the output\n/usr/bin/printf ' %-60s ' \"1. user-override gate AZZ99 was discovered\"\nif /usr/bin/echo \"$TRANSITION_OUT\" | /usr/bin/grep -q \"AZZ99\"; then\n green \"PASS\"; /usr/bin/echo; PASS=$((PASS+1))\nelse\n red \"FAIL\"; /usr/bin/echo; FAIL=$((FAIL+1))\nfi\n\n# Test 2: the plug-in gate emitted PASS (proves the gate ran, not just listed)\n/usr/bin/printf ' %-60s ' \"2. plug-in gate executed and emitted PASS\"\nif /usr/bin/echo \"$TRANSITION_OUT\" | /usr/bin/grep -qE \"AZZ99.*PASS\"; then\n green \"PASS\"; /usr/bin/echo; PASS=$((PASS+1))\nelse\n red \"FAIL\"; /usr/bin/echo; FAIL=$((FAIL+1))\nfi\n\n# Test 3: bundled canonical gates still ran (a01 should appear)\n/usr/bin/printf ' %-60s ' \"3. bundled canonical gate A01 still ran (dual-dir)\"\nif /usr/bin/echo \"$TRANSITION_OUT\" | /usr/bin/grep -q \"A01\"; then\n green \"PASS\"; /usr/bin/echo; PASS=$((PASS+1))\nelse\n red \"FAIL\"; /usr/bin/echo; FAIL=$((FAIL+1))\nfi\n\n# Test 4: removing the plug-in gate makes it disappear\n/usr/bin/rm -f \"$PLUGIN_GATE\"\nTRANSITION_OUT2=$(\"$SCRIPT_DIR/transition.sh\" \"shortlist→claimed\" \"$SYNTH\" --dry-run 2>&1 || true)\n/usr/bin/printf ' %-60s ' \"4. removing the plug-in gate stops it from running\"\nif ! /usr/bin/echo \"$TRANSITION_OUT2\" | /usr/bin/grep -q \"AZZ99\"; then\n green \"PASS\"; /usr/bin/echo; PASS=$((PASS+1))\nelse\n red \"FAIL\"; /usr/bin/echo; FAIL=$((FAIL+1))\nfi\n\n/usr/bin/echo\n/usr/bin/printf '=== summary: %s passed · %s failed ===\\n\\n' \\\n \"$(green \"$PASS\")\" \"$([ \"$FAIL\" -gt 0 ] && red \"$FAIL\" || /usr/bin/echo 0)\"\n\n[[ \"$FAIL\" -eq 0 ]] || exit 1\nexit 0\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3882,"content_sha256":"5b2cf1e66cbfbdd1f7ebcd6d1819246a71c56dfcffe1f253c341520c665fe466"},{"filename":"scripts/test-scout-refresh.sh","content":"#!/usr/bin/env bash\n# test-scout-refresh.sh — regression test for @scout refresh idempotency.\n#\n# Closes contributing-clanker-bzq.3.\n#\n# Promise of @scout refresh mode:\n# 1. Re-running scout on an existing candidate file UPDATES frontmatter\n# (scout_score, last_seen, momentum_signal) without rewriting the body.\n# 2. The body of the candidate (pet peeves observed, manual notes, draft\n# claim text) is preserved across refreshes.\n# 3. status: never moves backward — a `claimed` candidate doesn't get\n# reverted to `open` by a refresh.\n#\n# Why this matters: if refresh clobbers manual body content, every dossier\n# enrichment Jeremy does manually gets lost on the next scout run. Hard\n# constraint per the @scout spec.\n#\n# Test approach: synthesize a candidate file with manual body content + a\n# \"claimed\" status, run the equivalent of refresh against it, assert\n# preservation.\n#\n# Usage: test-scout-refresh.sh [--verbose]\n# Exit 0: all assertions hold. Exit 1: any failure.\n\nset -uo pipefail\n\nVERBOSE=\"${1:-}\"\nTMPDIR=$(/usr/bin/mktemp -d)\ntrap 'rm -rf \"$TMPDIR\"' EXIT\n\nPASS=0\nFAIL=0\nred() { /usr/bin/printf '\\033[31m%s\\033[0m' \"$1\"; }\ngreen() { /usr/bin/printf '\\033[32m%s\\033[0m' \"$1\"; }\n\nassert() {\n local name=\"$1\" expr=\"$2\"\n /usr/bin/printf ' %-60s ' \"$name\"\n if eval \"$expr\" >/dev/null 2>&1; then\n /usr/bin/printf '%s\\n' \"$(green PASS)\"\n PASS=$(( PASS + 1 ))\n else\n /usr/bin/printf '%s\\n' \"$(red FAIL)\"\n FAIL=$(( FAIL + 1 ))\n if [[ \"$VERBOSE\" == \"--verbose\" ]]; then\n eval \"$expr\"\n fi\n fi\n}\n\n# Synthesize an existing candidate with manual body content\nCANDIDATE=\"$TMPDIR/example__repo__issue42.md\"\n/usr/bin/cat > \"$CANDIDATE\" \u003c\u003c'EOF'\n---\nstatus: claimed\nrepo: example/repo\nissue_number: 42\nscout_score: 0.65\nlast_seen: 2026-04-15T00:00:00Z\nresearch_path: ~/.contribute-system/research/example__repo.md\noverrides: []\n---\n\n# Issue #42 — Add bulk export feature\n\n## Manual notes (engineer-curated, must survive refresh)\n- Maintainer @alice prefers small PRs (\u003c200 LOC)\n- Has CLA via dev.intentsolutions.io/cla\n- Follow-up planned: add CSV export after JSON export ships\n\n## Draft claim comment\nI'd like to take this. I've reviewed CONTRIBUTING.md and will follow the\nsmall-PR convention noted by @alice. Plan: JSON export in PR1, CSV in PR2.\n\n## Pet peeves observed for this repo\n- Don't @-mention @alice on weekends\n- Run `make precommit` before pushing — repo CI is slow\nEOF\n\n# Snapshot original body (everything after the second `---`)\nORIG_BODY=$(/usr/bin/awk '/^---$/{c++; next} c>=2' \"$CANDIDATE\")\n\n# Simulate a refresh: update only frontmatter fields scout would write\n# (scout_score, last_seen, momentum_signal). This is what the real\n# scout-refresh logic should do — never touch body, never regress status.\n/usr/bin/awk '\n BEGIN { in_fm = 0; fm_count = 0 }\n /^---$/ {\n fm_count++\n in_fm = (fm_count == 1)\n print\n if (fm_count == 2 && !momentum_added) {\n # closing frontmatter — too late, never mind\n }\n next\n }\n in_fm && /^scout_score:/ { print \"scout_score: 0.78\"; next }\n in_fm && /^last_seen:/ { print \"last_seen: 2026-05-03T18:00:00Z\"; next }\n in_fm && /^status:/ {\n # Status never goes backward. Verify the synthesized status is preserved.\n print\n next\n }\n { print }\n' \"$CANDIDATE\" > \"$CANDIDATE.refreshed\"\n\n# Add a new frontmatter field (momentum_signal) — simulates a real scout\n# enhancement that should land at the end of frontmatter, before the second ---\n/usr/bin/awk '\n BEGIN { fm_count = 0 }\n /^---$/ {\n fm_count++\n if (fm_count == 2) {\n print \"momentum_signal: rising\"\n }\n print\n next\n }\n { print }\n' \"$CANDIDATE.refreshed\" > \"$CANDIDATE\"\n\n# Assertions\nassert \"candidate file still exists after refresh\" \"[[ -f \\\"$CANDIDATE\\\" ]]\"\n\nassert \"scout_score updated to 0.78\" \\\n \"/usr/bin/grep -q '^scout_score: 0.78' \\\"$CANDIDATE\\\"\"\n\nassert \"last_seen updated to 2026-05-03T18:00:00Z\" \\\n \"/usr/bin/grep -q '^last_seen: 2026-05-03T18:00:00Z' \\\"$CANDIDATE\\\"\"\n\nassert \"momentum_signal field added\" \\\n \"/usr/bin/grep -q '^momentum_signal: rising' \\\"$CANDIDATE\\\"\"\n\nassert \"status: claimed preserved (never regresses to open)\" \\\n \"/usr/bin/grep -q '^status: claimed' \\\"$CANDIDATE\\\"\"\n\nassert \"research_path preserved\" \\\n \"/usr/bin/grep -q '^research_path:' \\\"$CANDIDATE\\\"\"\n\nNEW_BODY=$(/usr/bin/awk '/^---$/{c++; next} c>=2' \"$CANDIDATE\")\nassert \"manual body content preserved verbatim\" \\\n \"[[ \\\"\\$NEW_BODY\\\" == \\\"\\$ORIG_BODY\\\" ]]\"\n\nassert \"manual notes section preserved\" \\\n \"/usr/bin/grep -q 'Maintainer @alice prefers small PRs' \\\"$CANDIDATE\\\"\"\n\nassert \"draft claim comment preserved\" \\\n \"/usr/bin/grep -q 'JSON export in PR1, CSV in PR2' \\\"$CANDIDATE\\\"\"\n\nassert \"pet peeves section preserved\" \\\n \"/usr/bin/grep -q \\\"Don't @-mention @alice on weekends\\\" \\\"$CANDIDATE\\\"\"\n\n# Summary\n/usr/bin/printf '\\n scout-refresh: %s passed, %s failed\\n\\n' \\\n \"$(green \"$PASS\")\" \"$([[ $FAIL -eq 0 ]] && green 0 || red \"$FAIL\")\"\n\n[[ $FAIL -eq 0 ]] && exit 0 || exit 1\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":5012,"content_sha256":"6d5297ba6233f77290145a7ae2d2003dae84dfb7767c4d0ec5ce962e87968ad7"},{"filename":"scripts/test-stale-dossier-refresh.sh","content":"#!/usr/bin/env bash\n# test-stale-dossier-refresh.sh — regression test for dossier freshness signals.\n#\n# Promise: researcher-build.sh emits a `last_refreshed:` ISO-8601 timestamp\n# matching \"now\" (within 60 seconds). The 14-day staleness check is a\n# downstream consumer concern (SKILL.md Step 0.5 + dossier_age helpers);\n# this test validates the BUILDER side of the contract.\n#\n# Note: the actual \"auto-refresh at 14 days\" trigger logic lives in\n# /contribute SKILL.md as agent instructions (not executable code). What\n# we CAN test deterministically:\n# 1. researcher-build.sh writes last_refreshed: to current time\n# 2. an old timestamp is detectable as stale by simple shell math\n# 3. the dossier path slug (owner__repo) matches what gates expect\n#\n# Usage: test-stale-dossier-refresh.sh [--verbose]\n# Exit 0: all assertions hold. Exit 1: any failure.\n\nset -uo pipefail\n\nVERBOSE=\"${1:-}\"\nSYS=\"$HOME/.contribute-system\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nTMPDIR=$(/usr/bin/mktemp -d)\ntrap 'rm -rf \"$TMPDIR\"' EXIT\n\nPASS=0\nFAIL=0\nred() { /usr/bin/printf '\\033[31m%s\\033[0m' \"$1\"; }\ngreen() { /usr/bin/printf '\\033[32m%s\\033[0m' \"$1\"; }\n\nassert() {\n local name=\"$1\" expr=\"$2\"\n /usr/bin/printf ' %-60s ' \"$name\"\n if eval \"$expr\" >/dev/null 2>&1; then\n green \"PASS\"; /usr/bin/echo\n PASS=$((PASS + 1))\n else\n red \"FAIL\"; /usr/bin/echo\n [[ \"$VERBOSE\" == \"--verbose\" ]] && /usr/bin/printf ' expr: %s\\n' \"$expr\" >&2\n FAIL=$((FAIL + 1))\n fi\n}\n\n/usr/bin/printf '\\n=== dossier freshness regression ===\\n\\n'\n\n# --- TEST 1: researcher-build emits last_refreshed: matching now ---\nDOSSIER=\"$TMPDIR/lingdojo__kana-dojo.md\"\n\"$SCRIPT_DIR/researcher-build.sh\" lingdojo/kana-dojo --no-link-follow > \"$DOSSIER\" 2>/dev/null\n\nLAST_REFRESHED=$(/usr/bin/awk '/^last_refreshed:/{print $2; exit}' \"$DOSSIER\" 2>/dev/null)\nNOW_EPOCH=$(/usr/bin/date +%s)\nTHEN_EPOCH=$(/usr/bin/date -d \"$LAST_REFRESHED\" +%s 2>/dev/null || /usr/bin/echo 0)\nDELTA=$((NOW_EPOCH - THEN_EPOCH))\n\nassert \"1. dossier has last_refreshed frontmatter field\" \\\n '[[ -n \"$LAST_REFRESHED\" ]]'\n\nassert \"2. last_refreshed is within 60 seconds of now\" \\\n '[[ \"$DELTA\" -ge 0 && \"$DELTA\" -le 60 ]]'\n\n# --- TEST 2: stale detection math works (synthetic 30-day-old timestamp) ---\nTHIRTY_DAYS_AGO=$(/usr/bin/date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ)\nTHIRTY_EPOCH=$(/usr/bin/date -d \"$THIRTY_DAYS_AGO\" +%s)\nAGE_DAYS=$(( (NOW_EPOCH - THIRTY_EPOCH) / 86400 ))\n\nassert \"3. shell-math correctly identifies 30d-old as stale (>14d)\" \\\n '[[ \"$AGE_DAYS\" -gt 14 ]]'\n\n# --- TEST 3: dossier filename slug uses double underscore (matches gate expectations) ---\nEXPECTED_SLUG=\"lingdojo__kana-dojo\"\nACTUAL_SLUG=$(/usr/bin/basename \"$DOSSIER\" .md)\n\nassert \"4. dossier filename slug matches owner__repo convention\" \\\n '[[ \"$ACTUAL_SLUG\" == \"$EXPECTED_SLUG\" ]]'\n\n# --- TEST 4: the dossier has the manual sections that survive refresh ---\nassert \"5. dossier has Pet peeves section (manual, append-only)\" \\\n '/usr/bin/grep -q \"^## Pet peeves\" \"$DOSSIER\"'\n\nassert \"6. dossier has Failure log section (manual, append-only)\" \\\n '/usr/bin/grep -q \"^## Failure log\" \"$DOSSIER\"'\n\nassert \"7. dossier has Notes section (manual, append-only)\" \\\n '/usr/bin/grep -q \"^## Notes\" \"$DOSSIER\"'\n\n# --- TEST 5: dossier has issue_templates field (added 2026-05-03) ---\nassert \"8. dossier has issue_templates frontmatter (per skill-creator)\" \\\n '/usr/bin/grep -q \"^issue_templates:\" \"$DOSSIER\"'\n\n/usr/bin/echo\n/usr/bin/printf '=== summary: %s passed · %s failed ===\\n\\n' \\\n \"$(green \"$PASS\")\" \"$([ \"$FAIL\" -gt 0 ] && red \"$FAIL\" || /usr/bin/echo 0)\"\n\n[[ \"$FAIL\" -eq 0 ]] || exit 1\nexit 0\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":3658,"content_sha256":"141f227c9596f2bb2a10ab0900843847cbd6b5b41d18132d05ca85356629ed70"},{"filename":"scripts/transition.sh","content":"#!/usr/bin/env bash\n# transition.sh — invoked by /contribute SKILL.md on every lifecycle transition.\n# This is the single chokepoint between \"user wants to take action\" and\n# \"external action happens.\" Wraps gate-runner + override resolution +\n# atomic candidate update.\n#\n# Usage:\n# transition.sh \u003caction> \u003ccandidate-path> [options]\n# action: \"shortlist→claimed\", \"claimed→working\", etc.\n# options:\n# --dossier \u003cpath> Override dossier path (default: derive from candidate)\n# --override-gate \u003cid> \u003creason> Pre-record an override before running gates (repeatable)\n# --dry-run Run gates, print verdict, do NOT mutate candidate\n# --max-gate-age \u003cseconds> Reject if last gate run for this candidate is older\n# (TOCTOU mitigation; default 60)\n#\n# Exit code: 0 if transition allowed, 1 if BLOCKed (effective after overrides).\n\nset -euo pipefail\n\nACTION=\"${1:-}\"\nCANDIDATE=\"${2:-}\"\nshift 2 2>/dev/null || true\n\nif [[ -z \"$ACTION\" || -z \"$CANDIDATE\" ]]; then\n echo \"usage: $0 \u003caction> \u003ccandidate-path> [--dossier PATH] [--override-gate ID REASON ...] [--dry-run] [--max-gate-age SEC]\" >&2\n exit 64\nfi\n\nif [[ ! -f \"$CANDIDATE\" ]]; then\n echo \"candidate not found: $CANDIDATE\" >&2\n exit 65\nfi\n\nDOSSIER=\"\"\nDRY_RUN=0\nMAX_GATE_AGE=60\ndeclare -a OVERRIDES_NEW=() # pairs: gate id, reason\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n --dossier)\n DOSSIER=\"$2\"; shift 2 ;;\n --override-gate)\n OVERRIDES_NEW+=(\"$2\" \"$3\"); shift 3 ;;\n --dry-run)\n DRY_RUN=1; shift ;;\n --max-gate-age)\n # shellcheck disable=SC2034 # reserved for TOCTOU mitigation\n MAX_GATE_AGE=\"$2\"; shift 2 ;;\n *)\n echo \"unknown option: $1\" >&2; exit 64 ;;\n esac\ndone\n\nLOG=\"$HOME/.contribute-system/log.jsonl\"\nNOW=$(/usr/bin/date -u +%Y-%m-%dT%H:%M:%SZ)\n\n# Derive dossier path from candidate's repo if not supplied\nif [[ -z \"$DOSSIER\" ]]; then\n REPO=$(/usr/bin/awk '/^---$/{fm=!fm?1:2;next} fm==1 && /^repo:/{sub(/^repo:[[:space:]]*/,\"\"); print; exit}' \"$CANDIDATE\")\n if [[ -n \"$REPO\" ]]; then\n SLUG=$(/usr/bin/echo \"$REPO\" | /usr/bin/tr '/' '_')_; SLUG=\"${SLUG%_}\" # placeholder; researcher uses double-underscore\n SLUG=$(/usr/bin/echo \"$REPO\" | /usr/bin/sed 's,/,__,')\n CAND_DOSSIER=\"$HOME/.contribute-system/research/${SLUG}.md\"\n [[ -f \"$CAND_DOSSIER\" ]] && DOSSIER=\"$CAND_DOSSIER\"\n fi\nfi\n\n# Pre-record any overrides into the candidate file (atomic temp+rename).\n#\n# Earlier implementation used `awk -v RS='---'` to insert `overrides: []`\n# before the closing frontmatter delimiter, then `sed -i \"/^overrides:/a\"`\n# to append each entry. That path corrupted YAML: the awk RS=--- handling\n# mangled the opening delimiter to \"------\" (6 dashes) and the sed\n# append landed entries OUTSIDE the array as sibling list items rather\n# than children of `overrides`.\n#\n# Replace with a Python yaml round-trip — parse frontmatter, append to\n# the overrides list as proper YAML mapping entries, re-serialize. The\n# body (everything after the second `---`) passes through unchanged.\nif [[ \"${#OVERRIDES_NEW[@]}\" -gt 0 ]]; then\n if [[ \"$DRY_RUN\" -eq 1 ]]; then\n echo \"(dry-run) would record ${#OVERRIDES_NEW[@]} overrides; skipping write\" >&2\n else\n TMP=\"${CANDIDATE}.tmp.$\"\n # Build a flat list of \"gate=reason\" pairs for the Python helper.\n # Use NUL as field separator to handle reasons containing any char.\n PAIRS_FILE=\"${CANDIDATE}.pairs.$\"\n : > \"$PAIRS_FILE\"\n i=0\n while [[ $i -lt ${#OVERRIDES_NEW[@]} ]]; do\n /usr/bin/printf '%s\\0%s\\0' \"${OVERRIDES_NEW[$i]}\" \"${OVERRIDES_NEW[$((i+1))]}\" >> \"$PAIRS_FILE\"\n i=$((i+2))\n done\n\n /usr/bin/python3 - \"$CANDIDATE\" \"$NOW\" \"$PAIRS_FILE\" \"$TMP\" \u003c\u003c'PYEOF'\nimport sys, yaml\n\ncand_path, now_iso, pairs_path, out_path = sys.argv[1:5]\n\nwith open(cand_path) as f:\n text = f.read()\n\n# Split frontmatter from body (markdown convention: --- on its own line)\nlines = text.split('\\n')\nif not lines or lines[0].strip() != '---':\n sys.stderr.write(\"transition: candidate has no opening frontmatter delimiter\\n\")\n sys.exit(2)\ntry:\n end = lines.index('---', 1)\nexcept ValueError:\n sys.stderr.write(\"transition: candidate has no closing frontmatter delimiter\\n\")\n sys.exit(2)\n\nfm_text = '\\n'.join(lines[1:end])\nbody_text = '\\n'.join(lines[end+1:])\n\nfm = yaml.safe_load(fm_text) or {}\nif not isinstance(fm, dict):\n sys.stderr.write(\"transition: frontmatter is not a mapping\\n\")\n sys.exit(2)\n\nif 'overrides' not in fm or fm['overrides'] is None:\n fm['overrides'] = []\nif not isinstance(fm['overrides'], list):\n sys.stderr.write(\"transition: existing overrides field is not a list\\n\")\n sys.exit(2)\n\n# Read NUL-separated pairs from pairs_path: gate, reason, gate, reason, ...\nwith open(pairs_path, 'rb') as f:\n raw = f.read()\nparts = raw.split(b'\\x00')\n# Trailing NUL produces an empty final element — drop it.\nif parts and parts[-1] == b'':\n parts.pop()\nif len(parts) % 2 != 0:\n sys.stderr.write(\"transition: malformed override pairs (odd count)\\n\")\n sys.exit(2)\n\nfor i in range(0, len(parts), 2):\n gate = parts[i].decode('utf-8')\n reason = parts[i+1].decode('utf-8')\n fm['overrides'].append({'gate': gate, 'reason': reason, 'at': now_iso})\n\n# Re-serialize. default_flow_style=False keeps blocks readable.\nnew_fm = yaml.safe_dump(fm, default_flow_style=False, sort_keys=False, allow_unicode=True).rstrip('\\n')\nnew_text = '---\\n' + new_fm + '\\n---\\n' + body_text\n\nwith open(out_path, 'w') as f:\n f.write(new_text)\nPYEOF\n\n PY_EXIT=$?\n /usr/bin/rm -f \"$PAIRS_FILE\"\n if [[ \"$PY_EXIT\" -ne 0 ]]; then\n /usr/bin/rm -f \"$TMP\"\n echo \"transition: yaml round-trip failed (exit $PY_EXIT) — candidate not modified\" >&2\n exit 65\n fi\n\n /usr/bin/mv \"$TMP\" \"$CANDIDATE\" # atomic rename\n\n # Log each override\n i=0\n while [[ $i -lt ${#OVERRIDES_NEW[@]} ]]; do\n OG=\"${OVERRIDES_NEW[$i]}\"\n OR=\"${OVERRIDES_NEW[$((i+1))]}\"\n i=$((i+2))\n jq -nc --arg ts \"$NOW\" --arg gate \"$OG\" --arg reason \"$OR\" --arg cand \"$CANDIDATE\" \\\n '{ts: $ts, event: \"gate_override\", details: {gate: $gate, reason: $reason, candidate: $cand}}' >> \"$LOG\"\n done\n fi\nfi\n\n# Advisory: warn on missing required body sections per target status.\n# Per skills/contribute/references/candidate-file-format.md § \"Required\n# sections by lifecycle stage\". WARN (not BLOCK) — backfilled candidates\n# legitimately came in mid-lifecycle without early-stage sections.\nTARGET_STATUS=\"${ACTION##*→}\"\nREQUIRED_SECTIONS=\"\"\ncase \"$TARGET_STATUS\" in\n shortlist) REQUIRED_SECTIONS=\"## Scope|## Files to touch\" ;;\n claimed) REQUIRED_SECTIONS=\"## Scope|## Files to touch|## Claim comment draft\" ;;\n working) REQUIRED_SECTIONS=\"## Scope|## Files to touch|## Claim comment draft\" ;;\n submitted) REQUIRED_SECTIONS=\"## PR title|## PR body|## Test results\" ;;\n merged) REQUIRED_SECTIONS=\"## PR title|## PR body|## Test results\" ;;\n *) REQUIRED_SECTIONS=\"\" ;; # open, dropped: no body requirements\nesac\n\nMISSING_SECTIONS=()\nif [[ -n \"$REQUIRED_SECTIONS\" ]]; then\n IFS='|' read -ra SECTIONS \u003c\u003c\u003c \"$REQUIRED_SECTIONS\"\n for sec in \"${SECTIONS[@]}\"; do\n # Match the section header anchored at line start (avoid false positives\n # inside code blocks or quoted text).\n if ! /usr/bin/grep -qE \"^${sec}\\b\" \"$CANDIDATE\"; then\n MISSING_SECTIONS+=(\"$sec\")\n fi\n done\nfi\n\nif [[ \"${#MISSING_SECTIONS[@]}\" -gt 0 ]]; then\n /usr/bin/printf '[transition] WARN: candidate is missing %d required section(s) for status=%s:\\n' \\\n \"${#MISSING_SECTIONS[@]}\" \"$TARGET_STATUS\" >&2\n for sec in \"${MISSING_SECTIONS[@]}\"; do\n /usr/bin/printf '[transition] - %s\\n' \"$sec\" >&2\n done\n /usr/bin/printf '[transition] (advisory only — see references/candidate-file-format.md;\\n' >&2\n /usr/bin/printf '[transition] backfilled candidates legitimately skip early-stage sections)\\n' >&2\n # Log it so audits can see the pattern frequency\n MISSING_JSON=$(/usr/bin/printf '%s\\n' \"${MISSING_SECTIONS[@]}\" | jq -Rsc 'split(\"\\n\") | map(select(. != \"\"))')\n jq -nc --arg ts \"$NOW\" --arg cand \"$CANDIDATE\" --arg target \"$TARGET_STATUS\" \\\n --argjson missing \"$MISSING_JSON\" \\\n '{ts: $ts, event: \"transition_section_warn\",\n details: {candidate: $cand, target_status: $target, missing_sections: $missing}}' \\\n >> \"$LOG\" 2>/dev/null || true\nfi\n\n# Run gate-runner\n/usr/bin/printf '\\n[transition] %s on %s\\n' \"$ACTION\" \"$(/usr/bin/basename \"$CANDIDATE\")\" >&2\n[[ -n \"$DOSSIER\" ]] && /usr/bin/printf '[transition] dossier: %s\\n' \"$DOSSIER\" >&2 || /usr/bin/printf '[transition] dossier: (none — gates that need it will SKIP)\\n' >&2\n\nset +e\n# Find gate-runner co-located with this script (works whether invoked from\n# ~/.contribute-system/bin/ or from the skill's scripts/ dir).\n_TRANSITION_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nGATE_VERDICT=$(\"${_TRANSITION_DIR}/gate-runner.sh\" \"$ACTION\" \"$CANDIDATE\" \"$DOSSIER\")\nGATE_EXIT=$?\nset -e\n\n# Surface verdict\necho \"$GATE_VERDICT\"\n\n# Log the transition attempt\njq -nc --arg ts \"$NOW\" --arg action \"$ACTION\" --arg cand \"$CANDIDATE\" --arg exit \"$GATE_EXIT\" --arg verdict \"$GATE_VERDICT\" \\\n '{ts: $ts, event: \"transition_attempt\", details: {action: $action, candidate: $cand, gate_exit: $exit | tonumber, gate_verdict: ($verdict | fromjson? // {raw: $verdict})}}' >> \"$LOG\" 2>/dev/null || true\n\nif [[ \"$GATE_EXIT\" -ne 0 ]]; then\n /usr/bin/printf '\\n[transition] BLOCKED. Resolve the BLOCKers above or use --override-gate.\\n\\n' >&2\n exit 1\nfi\n\n# Update candidate state if not dry-run\nif [[ \"$DRY_RUN\" -eq 0 ]]; then\n # Parse target state from action (\"foo→bar\" → \"bar\")\n NEW_STATE=\"${ACTION##*→}\"\n if [[ \"$NEW_STATE\" != \"$ACTION\" && -n \"$NEW_STATE\" ]]; then\n TMP=\"${CANDIDATE}.tmp.$\"\n /usr/bin/sed \"s/^status: .*/status: $NEW_STATE/\" \"$CANDIDATE\" > \"$TMP\"\n /usr/bin/mv \"$TMP\" \"$CANDIDATE\" # atomic\n /usr/bin/printf '[transition] candidate status → %s\\n\\n' \"$NEW_STATE\" >&2\n\n # Log success\n jq -nc --arg ts \"$NOW\" --arg action \"$ACTION\" --arg cand \"$CANDIDATE\" --arg new_state \"$NEW_STATE\" \\\n '{ts: $ts, event: \"transition_committed\", details: {action: $action, candidate: $cand, new_state: $new_state}}' >> \"$LOG\" 2>/dev/null || true\n fi\nfi\n\nexit 0\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":10299,"content_sha256":"c6d39867264540c45f3ed622abcd91a753d11af9e209b990835a5b0f996164e3"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Contribute Command Center","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Overview","type":"text"}]},{"type":"paragraph","content":[{"text":"Local-only OSS contribution workflow. The skill itself is the system — there is no separate CLI binary, dashboard, or cloud backend. State lives in three places:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"GitHub itself","type":"text","marks":[{"type":"strong"}]},{"text":" — fetched live via ","type":"text"},{"text":"gh","type":"text","marks":[{"type":"code_inline"}]},{"text":" for any PR/issue state question. Never cached long-term.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Markdown candidate files","type":"text","marks":[{"type":"strong"}]},{"text":" at ","type":"text"},{"text":"~/.contribute-system/candidates/\u003cowner>__\u003crepo>__issue\u003cN>.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — one per issue we're tracking. Frontmatter is the queryable layer (status, scout_score, repo, research_path, overrides). Body holds claim drafts, PR drafts, scope notes.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Markdown repo dossiers","type":"text","marks":[{"type":"strong"}]},{"text":" at ","type":"text"},{"text":"~/.contribute-system/research/\u003cowner>__\u003crepo>.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — one per upstream repo we contribute to. Built by the ","type":"text"},{"text":"@researcher","type":"text","marks":[{"type":"code_inline"}]},{"text":" subagent. Frontmatter is canonical for every gate (CLA, DCO, branch convention, AI policy, draft-first, review bots, issue templates). Body holds curated knowledge: pet peeves, failure log, free-form notes that survive refresh.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Append-only event log","type":"text","marks":[{"type":"strong"}]},{"text":" at ","type":"text"},{"text":"~/.contribute-system/log.jsonl","type":"text","marks":[{"type":"code_inline"}]},{"text":" — every gate run, transition attempt, override, scout/researcher invocation lands here with a UTC timestamp. Filterable via ","type":"text"},{"text":"jq","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Use this skill when the user wants to:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Know what's in flight (open PRs, claimed issues, candidate queue)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Find a new issue to contribute to on GitHub","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Build or refresh a per-repo dossier (delegates to ","type":"text"},{"text":"@researcher","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run gate-checked transitions (claim, work, submit) — every external action passes through ","type":"text"},{"text":"transition.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" first","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Draft a claim comment, Design Issue, or PR description (default: Design Issue, NOT a PR)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run an upstream repo's test suite","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"The pre-2026-04-30 version of the skill used a SQLite tracker (","type":"text"},{"text":"~/.contribute-system/contribute.db","type":"text","marks":[{"type":"code_inline"}]},{"text":", 32 tables) plus a separate ","type":"text"},{"text":"contribute-system/","type":"text","marks":[{"type":"code_inline"}]},{"text":" monorepo (Next.js dashboard, TS CLI, Cloud Functions). Both were deleted because they were never used in practice. The skill now reads markdown directly. That tradeoff is deliberate: human-readable, greppable, git-trackable, survives any tool, no daemon process.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Prerequisites","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"gh","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" CLI","type":"text","marks":[{"type":"strong"}]},{"text":", authenticated as the user (","type":"text"},{"text":"gh auth status","type":"text","marks":[{"type":"code_inline"}]},{"text":" should show \"Logged in\")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"jq","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" on PATH (used by gates + log filtering)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Workspace","type":"text","marks":[{"type":"strong"}]},{"text":" at ","type":"text"},{"text":"~/000-projects/contributing-clanker/","type":"text","marks":[{"type":"code_inline"}]},{"text":" containing upstream clones (each clone has its own ","type":"text"},{"text":"CLAUDE.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for project conventions)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Runtime state dir","type":"text","marks":[{"type":"strong"}]},{"text":" at ","type":"text"},{"text":"~/.contribute-system/","type":"text","marks":[{"type":"code_inline"}]},{"text":" — created on first scout/researcher run if missing","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Run this DCI check at activation (output is auto-injected into the prompt):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"!"},"content":[{"text":"gh auth status >/dev/null 2>&1 && echo \"gh: ok\" || echo \"gh: NOT logged in\"\n[ -d ~/.contribute-system/gates ] && echo \"gates: $(ls ~/.contribute-system/gates/*.sh 2>/dev/null | wc -l) installed\" || echo \"gates: not yet installed\"\n[ -d ~/.contribute-system/candidates ] && echo \"candidates: $(ls ~/.contribute-system/candidates/*.md 2>/dev/null | wc -l) tracked\" || echo \"candidates: empty\"\n[ -d ~/.contribute-system/research ] && echo \"dossiers: $(ls ~/.contribute-system/research/*.md 2>/dev/null | wc -l) built\" || echo \"dossiers: empty\"\n[ -f ~/.contribute-system/profile.md ] && echo \"profile: ok\" || echo \"profile: missing — edit ~/.contribute-system/profile.md\"\n[ -f ~/.contribute-system/log.jsonl ] && echo \"log: $(wc -l \u003c ~/.contribute-system/log.jsonl) events\" || echo \"log: empty\"","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Instructions","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 0 — Refresh state (run first, every time)","type":"text"}]},{"type":"paragraph","content":[{"text":"Before answering anything contribution-related, surface current state. Run these in ","type":"text"},{"text":"parallel","type":"text","marks":[{"type":"strong"}]},{"text":" with the Bash tool:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Upstream PRs in flight (filtered to outside-org repos only —\n# the system tracks contributions INTO repos the user does not own;\n# own-repo PRs are out of scope and must be excluded).\n#\n# OWN_ORGS is the prefix list of repos to exclude. Update if the user\n# adds a new org. (Discoverable via `gh api user/orgs --jq '.[].login'`\n# plus the user's own login from `gh api user --jq '.login'`.)\nOWN_ORGS='jeremylongshore/ intent-solutions-io/'\ngh search prs --author=@me --state=open --limit=50 \\\n --json number,title,url,repository,isDraft,createdAt | \\\n jq --arg own \"$OWN_ORGS\" '\n ($own | split(\" \")) as $excl |\n map(select(.repository.nameWithOwner as $r |\n ($excl | map(. as $p | $r | startswith($p)) | any) | not))\n '\n\n# Recently-merged + closed upstream PRs (last 30, same scope filter)\ngh search prs --author=@me --state=closed --limit=30 \\\n --json number,title,url,repository,closedAt,createdAt | \\\n jq --arg own \"$OWN_ORGS\" '\n ($own | split(\" \")) as $excl |\n map(select(.repository.nameWithOwner as $r |\n ($excl | map(. as $p | $r | startswith($p)) | any) | not))\n '\n\n# Local candidate tracker — markdown frontmatter is the queryable layer.\n# Candidates are upstream-only by construction (scout never enqueues\n# own-repo issues), so no scope filter needed here.\nfor f in ~/.contribute-system/candidates/*.md; do\n awk -v f=\"$(basename \"$f\" .md)\" '\n /^---$/ { fm = !fm ? 1 : 2; next }\n fm == 1 && /^(repo|issue_number|status|scout_score|research_path|pr_number):/ { print f, $0 }\n ' \"$f\"\ndone 2>/dev/null\n\n# Recent activity from the event log\ntail -50 ~/.contribute-system/log.jsonl 2>/dev/null \\\n | jq -c \"select(.event | test(\\\"transition_|gate_|researcher_|scout_\\\"))\" 2>/dev/null","type":"text"}]},{"type":"paragraph","content":[{"text":"Scope rule (non-negotiable)","type":"text","marks":[{"type":"strong"}]},{"text":": this skill applies ","type":"text"},{"text":"only","type":"text","marks":[{"type":"em"}]},{"text":" to contributions made INTO repos the user does not own. Own-org PRs (","type":"text"},{"text":"jeremylongshore/*","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"intent-solutions-io/*","type":"text","marks":[{"type":"code_inline"}]},{"text":") are out of scope — they are personal-project work, not anti-slop OSS contributions. The whole architecture (gates, dossiers, lifecycle) exists because upstream maintainers need protection from low-quality AI work; that concern doesn't apply to the user's own repos. If a candidate file ever references an own-org repo, it's a scout bug — flag it.","type":"text"}]},{"type":"paragraph","content":[{"text":"Then summarize for the user:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"N open / draft PRs (and any blocked on review)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"N candidates in ","type":"text"},{"text":"claimed","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"working","type":"text","marks":[{"type":"code_inline"}]},{"text":" status but not yet ","type":"text"},{"text":"submitted","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"N candidates in ","type":"text"},{"text":"open","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"shortlist","type":"text","marks":[{"type":"code_inline"}]},{"text":" status (sorted by ","type":"text"},{"text":"scout_score","type":"text","marks":[{"type":"code_inline"}]},{"text":" desc)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Any contradictions between ","type":"text"},{"text":"gh","type":"text","marks":[{"type":"code_inline"}]},{"text":" (PR state) and the candidate's ","type":"text"},{"text":"status:","type":"text","marks":[{"type":"code_inline"}]},{"text":" field (e.g., PR merged but candidate still says ","type":"text"},{"text":"submitted","type":"text","marks":[{"type":"code_inline"}]},{"text":") — flag for cleanup","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"N candidates whose ","type":"text"},{"text":"research_path:","type":"text","marks":[{"type":"code_inline"}]},{"text":" is empty or stale (>14d) — flag for ","type":"text"},{"text":"@researcher","type":"text","marks":[{"type":"code_inline"}]},{"text":" build/refresh","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Recent events worth surfacing (gate BLOCKs, overrides, dossier refreshes)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Skip Step 0 only when the user asks about something unrelated to their own contributions.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 0.5 — Ensure dossier exists for any repo we'll touch","type":"text"}]},{"type":"paragraph","content":[{"text":"Every repo we contribute to needs a dossier at ","type":"text"},{"text":"~/.contribute-system/research/\u003cowner>__\u003crepo>.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — that's where every gate in ","type":"text"},{"text":"~/.contribute-system/gates/","type":"text","marks":[{"type":"code_inline"}]},{"text":" reads its rules from (branch convention, CLA/DCO, AI policy, draft-first preference, review bots, etc.).","type":"text"}]},{"type":"paragraph","content":[{"text":"Before any lifecycle transition (claim, work, submit) for a candidate at repo ","type":"text"},{"text":"\u003cowner>/\u003crepo>","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"DOSSIER=~/.contribute-system/research/$(echo \u003cowner>/\u003crepo> | tr '/' '_').md\nDOSSIER=${DOSSIER/__/__} # ensure double underscore\nif [[ ! -f \"$DOSSIER\" ]]; then\n echo \"no dossier — invoking @researcher\"\n # delegate to the researcher subagent\nfi\n# Also check staleness — refresh if >14 days old\nLAST=$(awk '/^last_refreshed:/{print $2; exit}' \"$DOSSIER\")\nif [[ -n \"$LAST\" ]]; then\n AGE_DAYS=$(( ( $(date +%s) - $(date -d \"$LAST\" +%s) ) / 86400 ))\n [[ \"$AGE_DAYS\" -gt 14 ]] && echo \"stale ($AGE_DAYS d) — invoking @researcher refresh\"\nfi","type":"text"}]},{"type":"paragraph","content":[{"text":"Delegate dossier build/refresh to the ","type":"text"},{"text":"@researcher","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" subagent (defined at ","type":"text"},{"text":"${CLAUDE_SKILL_DIR}/agents/researcher.md","type":"text","marks":[{"type":"code_inline"}]},{"text":"). It runs in its own context window so the verbose CONTRIBUTING.md fetch + depth-1 link follows stay out of your main conversation. It writes the dossier to disk and reports back a one-paragraph summary.","type":"text"}]},{"type":"paragraph","content":[{"text":"If the user already invoked ","type":"text"},{"text":"@researcher","type":"text","marks":[{"type":"code_inline"}]},{"text":" earlier in the session for this repo, skip — don't re-build.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 1 — Discover","type":"text"}]},{"type":"paragraph","content":[{"text":"Find issues worth contributing to. Sources, in priority order:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Existing candidates","type":"text","marks":[{"type":"strong"}]},{"text":" with ","type":"text"},{"text":"status: open","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"status: shortlist","type":"text","marks":[{"type":"code_inline"}]},{"text":" already in ","type":"text"},{"text":"~/.contribute-system/candidates/","type":"text","marks":[{"type":"code_inline"}]},{"text":" — already discovered + vetted, ranked by ","type":"text"},{"text":"scout_score:","type":"text","marks":[{"type":"code_inline"}]},{"text":" frontmatter field","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fresh GitHub label searches","type":"text","marks":[{"type":"strong"}]},{"text":" scoped to repos / languages in ","type":"text"},{"text":"~/.contribute-system/profile.md","type":"text","marks":[{"type":"code_inline"}]},{"text":": ","type":"text"},{"text":"gh search issues \"label:'good first issue' state:open language:\u003clang>\" --limit 50","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"Delegate discovery to the ","type":"text"},{"text":"@scout","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" subagent (defined at ","type":"text"},{"text":"${CLAUDE_SKILL_DIR}/agents/scout.md","type":"text","marks":[{"type":"code_inline"}]},{"text":"). It runs in its own context window so the verbose ","type":"text"},{"text":"gh search","type":"text","marks":[{"type":"code_inline"}]},{"text":" output stays out of your main conversation. Pass it a mode: ","type":"text"},{"text":"baseline","type":"text","marks":[{"type":"code_inline"}]},{"text":" (full per-tier sweep), ","type":"text"},{"text":"refresh","type":"text","marks":[{"type":"code_inline"}]},{"text":" (re-evaluate existing candidates for momentum), or an ad-hoc query like \"TypeScript repos at mainstream tier with no competing PRs.\" Scout writes ranked candidate markdown files to ","type":"text"},{"text":"~/.contribute-system/candidates/","type":"text","marks":[{"type":"code_inline"}]},{"text":" and appends events to ","type":"text"},{"text":"~/.contribute-system/log.jsonl","type":"text","marks":[{"type":"code_inline"}]},{"text":". Summarize the top picks for the user from those files; do not re-run the search yourself.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 2 — Qualify","type":"text"}]},{"type":"paragraph","content":[{"text":"Before claiming any issue, run these in parallel against the target repo:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"gh pr list --repo \u003cowner>/\u003crepo> --search \"\u003cissue#>\" --state=all\ngh api repos/\u003cowner>/\u003crepo>/commits --jq '.[0:3] | map({date: .commit.author.date, msg: .commit.message[0:60]})'\ngh api repos/\u003cowner>/\u003crepo>/contents/CONTRIBUTING.md --jq '.content' | base64 -d 2>/dev/null","type":"text"}]},{"type":"paragraph","content":[{"text":"Quick-reject signals:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"2+ active PRs already on the issue","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Issue >90 days old with maintainer silence","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CLA required for trivial work","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stack mismatch with the user's strengths","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Use the bundled ","type":"text"},{"text":"agents/repo-analyzer.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for the structured eligibility / CLA / rules check.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 3 — Claim","type":"text"}]},{"type":"paragraph","content":[{"text":"Draft a claim comment from ","type":"text"},{"text":"assets/claim-template.md","type":"text","marks":[{"type":"code_inline"}]},{"text":". Adapt to the upstream's tone (lowercase if they use lowercase). Show the draft to the user for approval. Never ","type":"text"},{"text":"gh issue comment","type":"text","marks":[{"type":"code_inline"}]},{"text":" autonomously.","type":"text"}]},{"type":"paragraph","content":[{"text":"Gate-checked transitions","type":"text","marks":[{"type":"strong"}]},{"text":" — before showing the claim draft to the user, run the gate-runner via ","type":"text"},{"text":"transition.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" to catch traps (already-assigned, already-shipped, stale labels, AI-policy strikes, etc.):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"~/.contribute-system/bin/transition.sh shortlist→claimed \\\n ~/.contribute-system/candidates/\u003cowner>__\u003crepo>__issue\u003cN>.md","type":"text"}]},{"type":"paragraph","content":[{"text":"If gates BLOCK, surface the blockers + fix hints to the user. They can fix the underlying issue, pick a different candidate, or use ","type":"text"},{"text":"--override-gate \u003cID> \"reason\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" if they have a specific justification (the reason is logged to ","type":"text"},{"text":"~/.contribute-system/log.jsonl","type":"text","marks":[{"type":"code_inline"}]},{"text":").","type":"text"}]},{"type":"paragraph","content":[{"text":"After the user posts the claim and gates pass, the candidate's ","type":"text"},{"text":"status:","type":"text","marks":[{"type":"code_inline"}]},{"text":" field is bumped automatically by ","type":"text"},{"text":"transition.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" (atomic write). No manual SQLite update needed — the markdown candidate file IS the tracker.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 4 — Work","type":"text"}]},{"type":"paragraph","content":[{"text":"Each clone in ","type":"text"},{"text":"~/000-projects/contributing-clanker/","type":"text","marks":[{"type":"code_inline"}]},{"text":" has its own ","type":"text"},{"text":"CLAUDE.md","type":"text","marks":[{"type":"code_inline"}]},{"text":". Read it first. Run the project's native test suite — common patterns:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stack","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Node + pnpm","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pnpm install && pnpm test && pnpm typecheck && pnpm lint","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Node + yarn","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"yarn install && yarn test","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Python","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"pytest -v","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or ","type":"text"},{"text":"flox activate -- bash -c \"pytest -v\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" for posthog)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rust","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"cargo build && cargo test && cargo clippy --all-targets","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Scala","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"sbt compile && sbt test && sbt scalafmtCheckAll","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"agents/test-runner.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for the structured runner that tees output to ","type":"text"},{"text":"~/.contribute-system/test-logs/","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Step 5 — Submit","type":"text"}]},{"type":"paragraph","content":[{"text":"Default to a Design Issue, not a PR.","type":"text","marks":[{"type":"strong"}]},{"text":" Auto-opening PRs creates \"whack-a-mole slopfests\" for maintainers (per the repo's ","type":"text"},{"text":"CLAUDE.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" philosophy).","type":"text"}]},{"type":"paragraph","content":[{"text":"Order:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Open a Design Issue using ","type":"text"},{"text":"assets/pr-template.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" reshaped for an issue body — include problem, proposed solution, diff preview, test results","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Wait for maintainer approval of the approach","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Open the PR using ","type":"text"},{"text":"assets/pr-template.md","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"agents/draft-writer.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for the body drafter. Always show the draft to the user for approval before posting.","type":"text"}]},{"type":"paragraph","content":[{"text":"Gate-checked submission","type":"text","marks":[{"type":"strong"}]},{"text":" — before opening the PR / Design Issue, run:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"~/.contribute-system/bin/transition.sh working→submitted \\\n ~/.contribute-system/candidates/\u003cowner>__\u003crepo>__issue\u003cN>.md","type":"text"}]},{"type":"paragraph","content":[{"text":"This runs phase B (pre-PR), C (PR submission), E (identity), F (legal), and G (infrastructure) gates against the local diff + dossier rules. BLOCK gates refuse the transition; WARN gates surface in the briefing for the user to acknowledge before proceeding.","type":"text"}]},{"type":"paragraph","content":[{"text":"After successful submission, ","type":"text"},{"text":"transition.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" bumps the candidate's ","type":"text"},{"text":"status:","type":"text","marks":[{"type":"code_inline"}]},{"text":" to ","type":"text"},{"text":"submitted","type":"text","marks":[{"type":"code_inline"}]},{"text":" atomically. Manually add the PR number to the candidate's frontmatter:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# After PR is opened\nsed -i \"s/^pr_number:.*/pr_number: \u003cnum>/; s|^pr_url:.*|pr_url: \u003curl>|\" \\\n ~/.contribute-system/candidates/\u003cowner>__\u003crepo>__issue\u003cN>.md","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Reconciliation","type":"text"}]},{"type":"paragraph","content":[{"text":"Periodically (or on user request \"reconcile candidates\"), check candidates with a ","type":"text"},{"text":"pr_number:","type":"text","marks":[{"type":"code_inline"}]},{"text":" field against live GitHub state:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"for f in ~/.contribute-system/candidates/*.md; do\n PR=$(awk '/^pr_number:/{print $2; exit}' \"$f\")\n REPO=$(awk '/^repo:/{print $2; exit}' \"$f\")\n [[ -z \"$PR\" || \"$PR\" == \"null\" ]] && continue\n gh pr view \"$PR\" --repo \"$REPO\" --json state,merged,closedAt\ndone","type":"text"}]},{"type":"paragraph","content":[{"text":"For each candidate whose actual PR state has moved on:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"PR merged → set ","type":"text"},{"text":"status: merged","type":"text","marks":[{"type":"code_inline"}]},{"text":" in the candidate (atomic write)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"PR closed unmerged → set ","type":"text"},{"text":"status: dropped","type":"text","marks":[{"type":"code_inline"}]},{"text":" and append a row to the dossier's ","type":"text"},{"text":"## Failure log","type":"text","marks":[{"type":"code_inline"}]},{"text":" section so we learn from it","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"PR still open → no change (","type":"text"},{"text":"status: submitted","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Mandatory: human approval before external submission","type":"text"}]},{"type":"paragraph","content":[{"text":"Copied verbatim from the repo's ","type":"text"},{"text":"CLAUDE.md","type":"text","marks":[{"type":"code_inline"}]},{"text":":","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Before submitting ANYTHING to external repos:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run all tests — ALL must pass","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run project-specific linters — no errors","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"ASK JEREMY FOR APPROVAL with test summary, file list, proposed body","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Default to Design Issue, NOT a PR","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"NEVER auto-submit PRs. NEVER bypass human approval. Design issues > PRs.","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Output","type":"text"}]},{"type":"paragraph","content":[{"text":"After Step 0, output a status block. After each subsequent step, output structured progress.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"State summary (after Step 0)","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"PRs in flight: \u003cN> open, \u003cM> draft\n - \u003crepo>#\u003cnum>: \u003ctitle> (state, age)\n ...\n\nClaimed but not submitted: \u003cN>\n - \u003cid>: \u003crepo>#\u003cissue> ($value)\n ...\n\nTracked opportunities: \u003cN> (top 5 by value)\n - \u003cid>: \u003crepo>#\u003cissue> ($value, \u003ccompetition flag>)\n ...\n\nDrift: \u003cN> rows where tracker disagrees with GitHub\n - \u003cid>: tracker says \u003cX>, gh says \u003cY> — suggest \u003cZ>","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Per-step output","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Step","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Output","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Discover","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Three sections: Tracker queue / Fresh GitHub / Algora URLs. Top 3 picks highlighted.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Qualify","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Verdict block: ","type":"text"},{"text":"claim","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"wait","type":"text","marks":[{"type":"code_inline"}]},{"text":" / ","type":"text"},{"text":"skip","type":"text","marks":[{"type":"code_inline"}]},{"text":" with one-sentence reason","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Claim","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Markdown draft of the comment, with placeholders filled. Awaits user approval.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Work","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Test summary: pass/fail counts, duration, coverage %, log path","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Submit","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Markdown draft of the PR or Design Issue body. Awaits user approval.","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Audit subcommands","type":"text"}]},{"type":"paragraph","content":[{"text":"When the user asks \"what gates am I overriding most?\" or \"audit my contribution history\" or \"show me override frequency\":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"${CLAUDE_SKILL_DIR}/scripts/audit-overrides.sh # all-time\n${CLAUDE_SKILL_DIR}/scripts/audit-overrides.sh --since=30 # last 30 days\n${CLAUDE_SKILL_DIR}/scripts/audit-overrides.sh --scope=org:posthog # one org\n${CLAUDE_SKILL_DIR}/scripts/audit-overrides.sh --gate=A05 # one gate\n${CLAUDE_SKILL_DIR}/scripts/audit-overrides.sh --json # JSON","type":"text"}]},{"type":"paragraph","content":[{"text":"Output is a per-gate table with ","type":"text"},{"text":"[overrides, blocks, override_rate, top_reason]","type":"text","marks":[{"type":"code_inline"}]},{"text":", sorted by override_rate desc. Gates overridden ≥50% of the time get flagged for investigation — either the gate is too strict (false-positive heavy) or it's catching real risk that's being consistently dismissed. Either way, surface it.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Error Handling","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Symptom","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Likely cause","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Recovery","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"gh: not logged in","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OAuth expired","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tell user to run ","type":"text"},{"text":"gh auth login","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"jq: command not found","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Missing on PATH","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"apt-get install jq","type":"text","marks":[{"type":"code_inline"}]},{"text":" (or equivalent)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"~/.contribute-system/","type":"text","marks":[{"type":"code_inline"}]},{"text":" missing","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"First-time setup","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"mkdir -p ~/.contribute-system/{candidates,research,gates,bin,check-runs}; touch ~/.contribute-system/log.jsonl","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"gh search","type":"text","marks":[{"type":"code_inline"}]},{"text":" returns 0 results unexpectedly","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Rate limit or wrong scope","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wait 60s and retry; check ","type":"text"},{"text":"gh auth status","type":"text","marks":[{"type":"code_inline"}]},{"text":" token scopes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Candidate's ","type":"text"},{"text":"status: submitted","type":"text","marks":[{"type":"code_inline"}]},{"text":" but PR is merged","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Reconciliation drift","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Run reconciliation step (above)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"User asks to claim, but competing PR exists","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Risk","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Surface the competing PR explicitly; gate ","type":"text"},{"text":"A2 already-shipped","type":"text","marks":[{"type":"code_inline"}]},{"text":" will BLOCK if it's a merged dupe","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Test suite hangs (e.g., posthog without flox)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wrong env","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Wrap in ","type":"text"},{"text":"flox activate -- bash -c \"...\"","type":"text","marks":[{"type":"code_inline"}]},{"text":" for flox-managed repos","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"gh issue comment","type":"text","marks":[{"type":"code_inline"}]},{"text":" permission denied","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Repo private or token missing scope","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Show the comment text to the user; they post manually","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Gate run BLOCKs unexpectedly","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Stale dossier or wrong rule","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"@researcher refresh \u003cowner>/\u003crepo>","type":"text","marks":[{"type":"code_inline"}]},{"text":"; if the rule itself is wrong, edit the dossier (manual sections survive refresh) or override with ","type":"text"},{"text":"transition.sh ... --override-gate \u003cID> \"reason\"","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Dossier missing for a candidate's repo","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"First time touching this repo","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"@researcher build \u003cowner>/\u003crepo>","type":"text","marks":[{"type":"code_inline"}]},{"text":" (auto-invoked by Step 0.5 anyway)","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"If any external submission would happen without human approval, ","type":"text"},{"text":"stop and ask","type":"text","marks":[{"type":"strong"}]},{"text":". This is non-negotiable.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Examples","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 1: \"What's my PR status?\"","type":"text"}]},{"type":"paragraph","content":[{"text":"User invokes ","type":"text"},{"text":"/contribute","type":"text","marks":[{"type":"code_inline"}]},{"text":" or asks \"what's in flight?\"","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run Step 0 (parallel ","type":"text"},{"text":"gh pr list","type":"text","marks":[{"type":"code_inline"}]},{"text":" + ","type":"text"},{"text":"gh issue list","type":"text","marks":[{"type":"code_inline"}]},{"text":" + candidate-frontmatter scan + recent log events)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Output the State Summary block","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Stop. The user can drill into any PR with a follow-up question.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 2: \"Find me a new contribution to work on\"","type":"text"}]},{"type":"paragraph","content":[{"text":"User asks \"what should I work on next?\" or \"scout opportunities.\"","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run Step 0 first (state summary)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Delegate to ","type":"text"},{"text":"@scout","type":"text","marks":[{"type":"code_inline"}]},{"text":" (the user-scope subagent at ","type":"text"},{"text":"${CLAUDE_SKILL_DIR}/agents/scout.md","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Output Tracker / Fresh GitHub / Algora sections, top 3 highlighted","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Optional: per top pick, run Step 2 (Qualify) to surface CLA / competing-PR signals","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 3: \"Draft a claim for screenpipe#1234\"","type":"text"}]},{"type":"paragraph","content":[{"text":"User asks to claim a specific issue.","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run Step 2 (Qualify) on ","type":"text"},{"text":"mediar-ai/screenpipe#1234","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"If verdict is ","type":"text"},{"text":"claim","type":"text","marks":[{"type":"code_inline"}]},{"text":", read ","type":"text"},{"text":"assets/claim-template.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Fill placeholders (approach in 1-2 bullets, ETA, CLA status)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Show draft to user","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"On user approval, post via ","type":"text"},{"text":"gh issue comment","type":"text","marks":[{"type":"code_inline"}]},{"text":" AND update tracker (Step 3 SQL)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 4: \"Reconcile the tracker\"","type":"text"}]},{"type":"paragraph","content":[{"text":"User asks to sync local state with GitHub.","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read all tracker rows where ","type":"text"},{"text":"pr_number IS NOT NULL","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"For each, run ","type":"text"},{"text":"gh pr view \u003crepo> \u003cpr_number> --json state,merged","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Update tracker rows whose status disagrees with GitHub state","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Output a diff summary: N rows updated, M unchanged","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Example 5: \"Run tests on cortex\"","type":"text"}]},{"type":"paragraph","content":[{"text":"User asks to verify a working branch.","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Read ","type":"text"},{"text":"agents/test-runner.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Detect cortex stack (Python + pyproject.toml)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"cd ~/000-projects/contributing-clanker/cortex && pytest -v 2>&1 | tee ~/.contribute-system/test-logs/$(date +%Y%m%d-%H%M%S)-cortex.log","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Output test summary block (pass/fail counts, log path)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Resources","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Bundled subagents (load with ","type":"text"},{"text":"Read agents/\u003cname>.md","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@scout","type":"text","marks":[{"type":"code_inline"}]},{"text":" (user-scope subagent at ","type":"text"},{"text":"${CLAUDE_SKILL_DIR}/agents/scout.md","type":"text","marks":[{"type":"code_inline"}]},{"text":") — discovery sweep, GitHub-only, ranked by star-tier brackets. Each candidate it writes carries a ","type":"text"},{"text":"research_path:","type":"text","marks":[{"type":"code_inline"}]},{"text":" frontmatter field pointing at the matching dossier (or empty if not yet built).","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@researcher","type":"text","marks":[{"type":"code_inline"}]},{"text":" (user-scope subagent at ","type":"text"},{"text":"${CLAUDE_SKILL_DIR}/agents/researcher.md","type":"text","marks":[{"type":"code_inline"}]},{"text":") — build / refresh the per-repo dossier at ","type":"text"},{"text":"~/.contribute-system/research/\u003cowner>__\u003crepo>.md","type":"text","marks":[{"type":"code_inline"}]},{"text":". Auto-invoked when a candidate's dossier is missing or older than 14 days.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"agents/repo-analyzer.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — DEPRECATED. Most of its function is now in the dossier system. Keep until Slice 3 retires it.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"agents/draft-writer.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — draft a Design Issue or PR body from a working branch's diff","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"agents/test-runner.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — detect upstream stack and run the native test suite, log to disk","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Bundled templates (read for fill-in)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"assets/claim-template.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — issue claim comment","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"assets/pr-template.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — PR description structure","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"assets/evidence-template.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — test/lint evidence summary block","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"References","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/workflow-guide.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — long-form narrative of the 5-step workflow with project-specific gotchas","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"External","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Anthropic Agent Skills overview","type":"text","marks":[{"type":"link","attrs":{"href":"https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview","title":null}}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The repo's own ","type":"text"},{"text":"CLAUDE.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" at ","type":"text"},{"text":"~/000-projects/contributing-clanker/CLAUDE.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" for project conventions and per-clone build commands","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Old patterns (deprecated, do not reintroduce)","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The pre-2026-04-30 skill referenced a ","type":"text"},{"text":"contribute","type":"text","marks":[{"type":"code_inline"}]},{"text":" CLI binary, EV scoring, judge gates, slack notifications, asciinema work-session recording, evidence bundles, and competition risk scoring. The underlying ","type":"text"},{"text":"contribute-system/","type":"text","marks":[{"type":"code_inline"}]},{"text":" monorepo was deleted because it was never used.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The pre-2026-05-03 skill used a SQLite tracker at ","type":"text"},{"text":"~/.contribute-system/contribute.db","type":"text","marks":[{"type":"code_inline"}]},{"text":" (32 tables, ","type":"text"},{"text":"bounties","type":"text","marks":[{"type":"code_inline"}]},{"text":"-keyed schema) plus an Algora/Gumroad/Cortex bounty-board framing. That DB was wiped; the framing is gone. The system is now markdown-only: candidate files + dossiers + JSONL event log. ","type":"text"},{"text":"The skill is a contribution tool — not a tracker, not a payouts system, not a portfolio","type":"text","marks":[{"type":"strong"}]},{"text":".","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"If a feature from those eras is wanted back, recover code from ","type":"text"},{"text":"git log","type":"text","marks":[{"type":"code_inline"}]},{"text":" in ","type":"text"},{"text":"~/000-projects/contributing-clanker/","type":"text","marks":[{"type":"code_inline"}]},{"text":". The bar to re-add is \"Jeremy actually uses it daily.\"","type":"text"}]}]},"metadata":{"date":"2026-06-05","name":"contribute","tags":["oss","contributions","github","contributing-clanker","ai-slop-prevention"],"author":"@skillopedia","source":{"stars":2275,"repo_name":"claude-code-plugins-plus-skills","origin_url":"https://github.com/jeremylongshore/claude-code-plugins-plus-skills/blob/HEAD/plugins/community/contributing-clanker/skills/contribute/SKILL.md","repo_owner":"jeremylongshore","body_sha256":"5beea6bdea6a1752a762480c96a4978f342c9a4f080beebc40ebd18125b6eac5","cluster_key":"b4abe3174cff76b272630fe899192fa14b81cfc8e2ef36121e35508e8774b218","clean_bundle":{"format":"clean-skill-bundle-v1","source":"jeremylongshore/claude-code-plugins-plus-skills/plugins/community/contributing-clanker/skills/contribute/SKILL.md","attachments":[{"id":"da6f46b9-a112-51db-985f-ab1f3132f12b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/da6f46b9-a112-51db-985f-ab1f3132f12b/attachment.md","path":"agents/draft-writer.md","size":3862,"sha256":"019bb1dcc2852949a56cd4333ce045f8e6da81629e76ef0e2be13de95034c063","contentType":"text/markdown; charset=utf-8"},{"id":"ae6e4199-2083-5fa4-b1ca-7d497e51c96c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ae6e4199-2083-5fa4-b1ca-7d497e51c96c/attachment.md","path":"agents/repo-analyzer.md","size":2418,"sha256":"67bbfa3120e0c8f1c1cca06d682dc0b15975d1d1537ccb8aeac5f959936062ce","contentType":"text/markdown; charset=utf-8"},{"id":"620571fe-3e80-56e8-b5e8-9a1ffb70f6de","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/620571fe-3e80-56e8-b5e8-9a1ffb70f6de/attachment.md","path":"agents/researcher.md","size":9743,"sha256":"9a13be508ebc6c5776a1abef7bf7a66cc867a739b1d8c72d7735d75a2f5d644a","contentType":"text/markdown; charset=utf-8"},{"id":"9f601ff0-b9a1-53b3-bdc5-9793c007b2eb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9f601ff0-b9a1-53b3-bdc5-9793c007b2eb/attachment.md","path":"agents/scout.md","size":7024,"sha256":"2a239645d59fbcd3752487a8b7376aaced681238728c04487a80886c2979c13f","contentType":"text/markdown; charset=utf-8"},{"id":"4c87b5ee-ace5-52e1-80f5-4bcf6eb69069","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4c87b5ee-ace5-52e1-80f5-4bcf6eb69069/attachment.md","path":"agents/test-runner.md","size":2870,"sha256":"37add53cfbfe586bbb219c567abea98bb90127240028aa57405b94effa3fcdd1","contentType":"text/markdown; charset=utf-8"},{"id":"6f56e20a-de2a-5d1c-a968-d376d8b3b6ca","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/6f56e20a-de2a-5d1c-a968-d376d8b3b6ca/attachment.md","path":"assets/claim-template.md","size":739,"sha256":"9162be5986bf77a6a01beb3212f26b09bd56b2b21436da3dd6d5e21866829f95","contentType":"text/markdown; charset=utf-8"},{"id":"7a1a472d-03e5-5541-b5af-f04847b9ad61","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7a1a472d-03e5-5541-b5af-f04847b9ad61/attachment.md","path":"assets/evidence-template.md","size":892,"sha256":"0f4b4bd4cecb811f4093edc129ce4c329945e7406f145ae099290f92e8b480b7","contentType":"text/markdown; charset=utf-8"},{"id":"9e98d1a5-2930-5480-af21-af1a7c6498c8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9e98d1a5-2930-5480-af21-af1a7c6498c8/attachment.md","path":"assets/pr-template.md","size":1104,"sha256":"21511536865e6b3c92aefd2730cda45e09922c0936885a61b7319efe0ddd4cc4","contentType":"text/markdown; charset=utf-8"},{"id":"526e34df-910a-5367-945e-2d722baa97bd","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/526e34df-910a-5367-945e-2d722baa97bd/attachment.md","path":"references/candidate-file-format.md","size":9474,"sha256":"5b7425bf97b9d9fd83f3d29df6da536b669af66a6e7743c0ce501265ee6e034d","contentType":"text/markdown; charset=utf-8"},{"id":"95d61bbc-7bd2-509e-9cca-facbc7fc6554","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/95d61bbc-7bd2-509e-9cca-facbc7fc6554/attachment.md","path":"references/workflow-guide.md","size":7478,"sha256":"915c52e20a5fed0d041f025abdffbbf7e4a0a56d9f733a071c8a9046a62c01be","contentType":"text/markdown; charset=utf-8"},{"id":"d9a0eff5-8682-5d3b-82a8-031a0d81f471","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d9a0eff5-8682-5d3b-82a8-031a0d81f471/attachment.sh","path":"scripts/audit-overrides.sh","size":7364,"sha256":"8791665d7be582e8199c86ee43c728a3d4f53921246fea4e60ba15d018d5fb2e","contentType":"application/x-sh; charset=utf-8"},{"id":"b00ae207-f624-5652-8c62-42c79f8e4bb5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b00ae207-f624-5652-8c62-42c79f8e4bb5/attachment.sh","path":"scripts/catalog-coverage.sh","size":3920,"sha256":"995c3ff39919ae4bfda37396e62412b32e7b8a1c113b695308ca1a62cbc24b2e","contentType":"application/x-sh; charset=utf-8"},{"id":"306cec03-bd87-554d-b997-266bd9d59e06","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/306cec03-bd87-554d-b997-266bd9d59e06/attachment.sh","path":"scripts/gate-runner.sh","size":8685,"sha256":"6eeeb5c64b1c4960553696802f62a0234aa7aa44dbc861ae61e7a580dccd102a","contentType":"application/x-sh; charset=utf-8"},{"id":"3e0342eb-f096-5afb-87ae-119df3fd7ebc","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3e0342eb-f096-5afb-87ae-119df3fd7ebc/attachment.sh","path":"scripts/gates/a01-already-assigned.sh","size":1077,"sha256":"c2153a9f344b7c1b2ec5e03cb9032733664bed5d2e066444b9c2fc59f23c7698","contentType":"application/x-sh; charset=utf-8"},{"id":"9dc9b881-35a7-52bd-80b9-c75f7a73843b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9dc9b881-35a7-52bd-80b9-c75f7a73843b/attachment.sh","path":"scripts/gates/a02-already-shipped.sh","size":1291,"sha256":"a8f28e7f3edda8776164227d51605f6b2789bc1788cddc500093e65993ea6b63","contentType":"application/x-sh; charset=utf-8"},{"id":"adecfac7-3d44-5147-ad59-67636cf934f9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/adecfac7-3d44-5147-ad59-67636cf934f9/attachment.sh","path":"scripts/gates/a03-duplicate-flagged.sh","size":851,"sha256":"1711f709113349c67fa7f9a0485ba85ca20062e0f51a7e5d4ebdbffa3718bff4","contentType":"application/x-sh; charset=utf-8"},{"id":"7d811a27-b476-5fe5-9c5e-d701cc6dc3b4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/7d811a27-b476-5fe5-9c5e-d701cc6dc3b4/attachment.sh","path":"scripts/gates/a04-issue-age.sh","size":3921,"sha256":"dc31104c708b35b12cf234af3031643200c952c1b54f00c67e7fc59dbc30c787","contentType":"application/x-sh; charset=utf-8"},{"id":"1d3e5cf6-03e6-518b-a2c8-b58591cdadac","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1d3e5cf6-03e6-518b-a2c8-b58591cdadac/attachment.sh","path":"scripts/gates/a05-issue-still-open.sh","size":1637,"sha256":"d848a800b8f24ee4d4caf62396984c6237943511c9757aad59e78957b2dc8607","contentType":"application/x-sh; charset=utf-8"},{"id":"5246cdaa-cc6f-5cf8-97c3-5535f7ecdcbb","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5246cdaa-cc6f-5cf8-97c3-5535f7ecdcbb/attachment.sh","path":"scripts/gates/a06-claim-etiquette-required.sh","size":1589,"sha256":"e86c1d3d822585ac04bf49b5fd2390187cd90e97042bd45e981bcd22decf2ffe","contentType":"application/x-sh; charset=utf-8"},{"id":"9f18b2f4-1a83-5c4f-ba67-85af6b52529a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9f18b2f4-1a83-5c4f-ba67-85af6b52529a/attachment.sh","path":"scripts/gates/a09-mention-routing.sh","size":2191,"sha256":"0e4b506121faf4ad37fa794e1f6b922e11e85dbad6d34708a0bbe4e1901fc4c1","contentType":"application/x-sh; charset=utf-8"},{"id":"f07090f7-4596-5ecc-867c-6c3e98e4077c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f07090f7-4596-5ecc-867c-6c3e98e4077c/attachment.sh","path":"scripts/gates/b01-base-branch.sh","size":983,"sha256":"4f011e66b5cbb4b92a1e6502d2db2288b68fdc7efe59d02f30448b2ed1132cc5","contentType":"application/x-sh; charset=utf-8"},{"id":"20b42f4a-21be-5f52-93cc-d7a29d0b0ab8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/20b42f4a-21be-5f52-93cc-d7a29d0b0ab8/attachment.sh","path":"scripts/gates/b02-branch-naming.sh","size":1082,"sha256":"f3fff5c5a9f55ad1d58932d03e49675032772b80a221daa9156a295531f2ec2f","contentType":"application/x-sh; charset=utf-8"},{"id":"26d79cd1-89db-531c-8413-53101359d3fa","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/26d79cd1-89db-531c-8413-53101359d3fa/attachment.sh","path":"scripts/gates/b03-clone-fresh.sh","size":1061,"sha256":"e2278e7c501ed0eac248823311f7c1f8b68c3483db4a1687177f972a183ea7f6","contentType":"application/x-sh; charset=utf-8"},{"id":"d939d24f-7772-5c4c-a456-a959cb6438ce","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d939d24f-7772-5c4c-a456-a959cb6438ce/attachment.sh","path":"scripts/gates/b05-dco-signoff.sh","size":1573,"sha256":"61d66660726195f5033a54ba473454838f71c1942fe3d650b5caeee4ce5132ae","contentType":"application/x-sh; charset=utf-8"},{"id":"23d84bb1-23a3-59dc-9cb9-2247e5b8bac1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/23d84bb1-23a3-59dc-9cb9-2247e5b8bac1/attachment.sh","path":"scripts/gates/b06-commit-format.sh","size":1400,"sha256":"c7092027b2509fcf8b08e4195571f76a34dbf96f6a5e04a923a3f3df5d1bdff9","contentType":"application/x-sh; charset=utf-8"},{"id":"3274e216-3979-5709-adc7-254442faa36f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3274e216-3979-5709-adc7-254442faa36f/attachment.sh","path":"scripts/gates/b07-scope-files.sh","size":2118,"sha256":"0bc35d305d312ebe13ee725319a4877f87fd658366061a34881d5c2010c45fbf","contentType":"application/x-sh; charset=utf-8"},{"id":"2c76f633-5963-5538-9859-8fae6f9d9059","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2c76f633-5963-5538-9859-8fae6f9d9059/attachment.sh","path":"scripts/gates/b12-new-deps.sh","size":1759,"sha256":"b28b3427d218554555061f1aa46e0f4fd245d97ed39b92a4c36a02d489426f42","contentType":"application/x-sh; charset=utf-8"},{"id":"e7af3cdd-4f1c-51a0-85c1-5b5ba958a0d5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e7af3cdd-4f1c-51a0-85c1-5b5ba958a0d5/attachment.sh","path":"scripts/gates/b14-local-checks.sh","size":1794,"sha256":"f2c04535919b7a2b385433ffa783575085a570f16a77aaa1be569a4b4537eebe","contentType":"application/x-sh; charset=utf-8"},{"id":"df75ecf5-0587-5c24-a352-b6ea6789a594","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/df75ecf5-0587-5c24-a352-b6ea6789a594/attachment.sh","path":"scripts/gates/b16-local-check-allowlist.sh","size":1857,"sha256":"d119ca29318a9f4b94d9ad4e103102edd0ccbe9285f1b0c4cfae2441141efa7f","contentType":"application/x-sh; charset=utf-8"},{"id":"ebb6f76f-b32e-5625-81b8-46b31c7a77b8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ebb6f76f-b32e-5625-81b8-46b31c7a77b8/attachment.sh","path":"scripts/gates/c01-draft-first.sh","size":902,"sha256":"9e8f0dd0c4441f534e2cb0b52ee476c37db53354a3ee167b998dfb1a72b1c928","contentType":"application/x-sh; charset=utf-8"},{"id":"d2d34ed1-21e4-58fc-8235-66c5bcfc4f25","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d2d34ed1-21e4-58fc-8235-66c5bcfc4f25/attachment.sh","path":"scripts/gates/c02-pr-title-format.sh","size":1244,"sha256":"5a4618aa7f6f685b363b1b131a59694b9796f9ae578565c61391e4e6d31dd482","contentType":"application/x-sh; charset=utf-8"},{"id":"50114cc5-3917-59ad-bdee-6c9bdb8cf6e2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/50114cc5-3917-59ad-bdee-6c9bdb8cf6e2/attachment.sh","path":"scripts/gates/c03-pr-body-sections.sh","size":2037,"sha256":"142103a2a92d376c87fac38bd2f3764eab23d650bea11d656f7e481cff14e429","contentType":"application/x-sh; charset=utf-8"},{"id":"a29c1fe8-c329-59fe-b26b-56a65efe3393","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/a29c1fe8-c329-59fe-b26b-56a65efe3393/attachment.sh","path":"scripts/gates/c04-ui-screenshots.sh","size":1622,"sha256":"58b2f07a7fa79e8a85b190098f6943fc88ab488a0907ed2bcfa80e61d4fa8840","contentType":"application/x-sh; charset=utf-8"},{"id":"ca5530d5-d385-5787-bbcc-b11d9a38dad9","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/ca5530d5-d385-5787-bbcc-b11d9a38dad9/attachment.sh","path":"scripts/gates/c05-test-evidence.sh","size":1441,"sha256":"744d4266503bd5e8532cd7a36207671f420f57ac58d7b7c3bd2b0a04c9a6435e","contentType":"application/x-sh; charset=utf-8"},{"id":"f7ded7be-f1b6-591b-97a1-5b61a3fd405f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f7ded7be-f1b6-591b-97a1-5b61a3fd405f/attachment.sh","path":"scripts/gates/c07-coauthor-banned.sh","size":1235,"sha256":"a6ca19e8c22c3e407fbf0826a5842ffc1def37e48801ee57b7bc8a090df41d6d","contentType":"application/x-sh; charset=utf-8"},{"id":"c95005fd-ad79-5680-b482-b4682375e9e1","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c95005fd-ad79-5680-b482-b4682375e9e1/attachment.sh","path":"scripts/gates/c09-issue-link.sh","size":1514,"sha256":"42e8046fc77b45cf2dbf88a219eebf8a2a199232ec1fdeb9a36f751a2133b6af","contentType":"application/x-sh; charset=utf-8"},{"id":"c6ca3e8f-cd61-578e-871b-7f07e92aec5d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c6ca3e8f-cd61-578e-871b-7f07e92aec5d/attachment.sh","path":"scripts/gates/c11-no-force-push.sh","size":1332,"sha256":"ee69919a375209a5e8a5337d6ba78ef87ee89e54ac628d8bd02f8a6464420a93","contentType":"application/x-sh; charset=utf-8"},{"id":"83af3fca-dc12-5001-8dbc-9ae03ac5cc1b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/83af3fca-dc12-5001-8dbc-9ae03ac5cc1b/attachment.sh","path":"scripts/gates/c12-ci-green.sh","size":1230,"sha256":"a4876063748344324c9b41f9c8fcc991ee4666f938d71683c0b11bda8f4e1815","contentType":"application/x-sh; charset=utf-8"},{"id":"3cd11459-c930-5142-a8c5-93b2fe15027d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3cd11459-c930-5142-a8c5-93b2fe15027d/attachment.sh","path":"scripts/gates/c13-bots-passed.sh","size":2137,"sha256":"aed27a8b159d7248e3ed0db927f3ef20ebd40503a37039c545826594e1dab0a4","contentType":"application/x-sh; charset=utf-8"},{"id":"d6d16071-0073-5c21-a99e-b6fd9173d7e7","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d6d16071-0073-5c21-a99e-b6fd9173d7e7/attachment.sh","path":"scripts/gates/c16-no-self-merge.sh","size":914,"sha256":"496b516a21f9a23cb93c327443a1b5f8d1afbdec4e12e08cb36e2c3c3ae19eb1","contentType":"application/x-sh; charset=utf-8"},{"id":"97e19ec0-261e-5165-ab9f-008b72b6773c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/97e19ec0-261e-5165-ab9f-008b72b6773c/attachment.sh","path":"scripts/gates/c19-body-claim-vs-diff.sh","size":2934,"sha256":"5164aa105acdcdb7812b8e6898333c6fe39cbdc9d5ca1160d9b497e75ee5d004","contentType":"application/x-sh; charset=utf-8"},{"id":"13ca5973-66aa-5784-989e-ca62f5c45d2c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/13ca5973-66aa-5784-989e-ca62f5c45d2c/attachment.sh","path":"scripts/gates/d02-no-ai-bug-reports.sh","size":1400,"sha256":"253747ebe9e341847dc6a8fca107466ac91c3a266ce73a626ee51afbf1b7b722","contentType":"application/x-sh; charset=utf-8"},{"id":"47e2caec-9452-5f36-ba31-af128923f4c5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/47e2caec-9452-5f36-ba31-af128923f4c5/attachment.sh","path":"scripts/gates/d03-no-ai-pr-reviews.sh","size":1163,"sha256":"a5a8d9341b05342dc3557ae2880d11495c5f70a4baa0ee5edbd34f350280a579","contentType":"application/x-sh; charset=utf-8"},{"id":"c33dc14f-33ac-5fc7-9882-773c498299d3","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c33dc14f-33ac-5fc7-9882-773c498299d3/attachment.sh","path":"scripts/gates/d05-no-reopen.sh","size":1148,"sha256":"6b0d620e2105b38524f8e619323109356ef2e07b8dc9edd2f6af83af60427af8","contentType":"application/x-sh; charset=utf-8"},{"id":"fd31d79c-0d51-5e95-ae7e-80e6c1e79d63","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/fd31d79c-0d51-5e95-ae7e-80e6c1e79d63/attachment.sh","path":"scripts/gates/e02-ai-strike-track.sh","size":2721,"sha256":"081cac7bd1ad4aad675268147de136097d4b930667960f4a155e2b508cfaaff3","contentType":"application/x-sh; charset=utf-8"},{"id":"3142eae3-f24a-5eea-b742-e56959aafbd5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3142eae3-f24a-5eea-b742-e56959aafbd5/attachment.sh","path":"scripts/gates/e04-fork-target.sh","size":1581,"sha256":"a36a97d7e3e449be1b8117ab8c5a0354d7c159b62e5dc2895c85f5e52ee0799f","contentType":"application/x-sh; charset=utf-8"},{"id":"5a7a73ad-f63e-543e-beea-0431fd7d31c8","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/5a7a73ad-f63e-543e-beea-0431fd7d31c8/attachment.sh","path":"scripts/gates/f01-license-compat.sh","size":3563,"sha256":"657eece2834112fcd4e65dff96e7a7676fee37c744869bbc00e5df71721b17be","contentType":"application/x-sh; charset=utf-8"},{"id":"bcbcf25d-fcbb-5e8a-897f-088eedb77386","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/bcbcf25d-fcbb-5e8a-897f-088eedb77386/attachment.sh","path":"scripts/gates/f03-fixtures-clean.sh","size":1195,"sha256":"002b5884d2e0767866936f27f513373ebfee91cad9ef7e84dff2a81c9f19b4f7","contentType":"application/x-sh; charset=utf-8"},{"id":"e7c00f65-e8d3-5ca4-bb00-2ab39ac4db38","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e7c00f65-e8d3-5ca4-bb00-2ab39ac4db38/attachment.sh","path":"scripts/gates/f04-override-disclosure.sh","size":2468,"sha256":"258fa23f7759752c3154fb0f2a10fe6994bddb1efda4336b7c2f3f9543f9c2d7","contentType":"application/x-sh; charset=utf-8"},{"id":"69503934-8d11-56c0-81bf-4756e2de8b09","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/69503934-8d11-56c0-81bf-4756e2de8b09/attachment.sh","path":"scripts/gates/g01-no-vendored-edits.sh","size":1933,"sha256":"ce5ae8385d93bf66772fae1ec912da9f74f1e3fe32571a2526fa428c9a870e71","contentType":"application/x-sh; charset=utf-8"},{"id":"2eda8c67-bdbe-58cf-9f20-102f9ae38583","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2eda8c67-bdbe-58cf-9f20-102f9ae38583/attachment.sh","path":"scripts/gates/g02-protected-paths.sh","size":1285,"sha256":"f5dc771c0814110ae0dcd8e0024c40e06da84995e4433cbb1b50a8ba1cae0731","contentType":"application/x-sh; charset=utf-8"},{"id":"656f0b53-5b29-5cbc-94fc-5383ee044a28","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/656f0b53-5b29-5cbc-94fc-5383ee044a28/attachment.sh","path":"scripts/gates/g03-no-changelog-edits.sh","size":1399,"sha256":"c06a37f761b81eb0f154284fb626cfa93f23a39b4a8de4ef18c0a9231776a4a4","contentType":"application/x-sh; charset=utf-8"},{"id":"b722dbe7-c120-5981-bd27-98354dfb5d65","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/b722dbe7-c120-5981-bd27-98354dfb5d65/attachment.sh","path":"scripts/gates/g04-no-version-bump.sh","size":1412,"sha256":"adb3fb0b5340f1b2f799b2d7c22edbf10cee304999113976f70b171b1a566f31","contentType":"application/x-sh; charset=utf-8"},{"id":"14e61eaf-0d06-5357-a5cf-012bf9b8d4b2","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/14e61eaf-0d06-5357-a5cf-012bf9b8d4b2/attachment.sh","path":"scripts/gates/g06-override-rate-limit.sh","size":1493,"sha256":"591a9926927c36136245e0f24033ad0eeb362af8f8fc99010b634246694c0a97","contentType":"application/x-sh; charset=utf-8"},{"id":"d05df8bc-3d37-5c62-bfdc-7ddec21887fe","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/d05df8bc-3d37-5c62-bfdc-7ddec21887fe/attachment.sh","path":"scripts/gates/lib/preamble.sh","size":4585,"sha256":"7b57b984996225e54c05f159b2cba43905e641db5264682fa46b725cd6d3d7dd","contentType":"application/x-sh; charset=utf-8"},{"id":"9853b67a-b0c8-5f90-a343-abbadbc9e778","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/9853b67a-b0c8-5f90-a343-abbadbc9e778/attachment.sh","path":"scripts/lint-candidate.sh","size":5137,"sha256":"65d165c7d9d2f786f2eef89b88a45d031cb28e3da0b2a80962a2ebe99e96ce17","contentType":"application/x-sh; charset=utf-8"},{"id":"c258e2ef-4a34-58e7-89c7-e997dd3b747c","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/c258e2ef-4a34-58e7-89c7-e997dd3b747c/attachment.sh","path":"scripts/researcher-build.sh","size":19292,"sha256":"fa7691613b130727fd3a5c21594371566ca003633badfc423f7894bdb163efc3","contentType":"application/x-sh; charset=utf-8"},{"id":"80374c3a-bdeb-5d6c-8387-5f5ac5942977","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/80374c3a-bdeb-5d6c-8387-5f5ac5942977/attachment.sh","path":"scripts/test-known-traps.sh","size":5303,"sha256":"f06d847d38d572a571aee673c58da8760bfaf8f15257a212c09261e7bb5e7cae","contentType":"application/x-sh; charset=utf-8"},{"id":"2596824a-1c45-572e-a417-2bc9986799d5","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/2596824a-1c45-572e-a417-2bc9986799d5/attachment.sh","path":"scripts/test-override-audit.sh","size":3407,"sha256":"f54cc3ef2ccd1118abb92c83b3426b2dc5220d335ce76c880b8d9fd7e2af03f6","contentType":"application/x-sh; charset=utf-8"},{"id":"1a11e99e-7192-5646-86cc-1897ae67f83a","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1a11e99e-7192-5646-86cc-1897ae67f83a/attachment.sh","path":"scripts/test-plug-in.sh","size":3882,"sha256":"5b2cf1e66cbfbdd1f7ebcd6d1819246a71c56dfcffe1f253c341520c665fe466","contentType":"application/x-sh; charset=utf-8"},{"id":"f3c2a473-3293-5597-83e9-5e3adc225351","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f3c2a473-3293-5597-83e9-5e3adc225351/attachment.sh","path":"scripts/test-scout-refresh.sh","size":5012,"sha256":"6d5297ba6233f77290145a7ae2d2003dae84dfb7767c4d0ec5ce962e87968ad7","contentType":"application/x-sh; charset=utf-8"},{"id":"64233839-27d6-5c28-a9f6-eaed17546b6b","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/64233839-27d6-5c28-a9f6-eaed17546b6b/attachment.sh","path":"scripts/test-stale-dossier-refresh.sh","size":3658,"sha256":"141f227c9596f2bb2a10ab0900843847cbd6b5b41d18132d05ca85356629ed70","contentType":"application/x-sh; charset=utf-8"},{"id":"3ca869b8-dff1-594e-bcbc-b6721fe83db6","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/3ca869b8-dff1-594e-bcbc-b6721fe83db6/attachment.sh","path":"scripts/transition.sh","size":10299,"sha256":"c6d39867264540c45f3ed622abcd91a753d11af9e209b990835a5b0f996164e3","contentType":"application/x-sh; charset=utf-8"}],"bundle_sha256":"7f3ecf2c5a6c7eb35ee825e52fcb8b59d50bd1671a4b78017348a966fc7dff95","attachment_count":63,"text_attachments":63,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"plugins/community/contributing-clanker/skills/contribute/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"web-development","category_label":"Web"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"web-development","import_tag":"clean-skills-v1","description":"Local-only OSS contribution command center. Auto-refreshes the user's\nin-flight PR and issue state on invoke so conversations start with full\ncontext — no need to brief Claude on what's in flight. Helps the user\nfind issues to contribute to on GitHub, builds per-repo dossiers of what\neach upstream expects (CLA, DCO, branch convention, AI policy, draft-first,\nreview bots, issue templates), runs deterministic gates before any\nexternal action so AI-assisted contributions don't reach maintainers as\nslop. State is markdown-only: candidate files at\n~/.contribute-system/candidates/, repo dossiers at\n~/.contribute-system/research/, append-only event log at\n~/.contribute-system/log.jsonl. No database, no cloud calls.\nUse when the user asks about their PRs / issues / contributions, wants to\nfind new work to take on, claim an issue, build/refresh a repo's dossier,\nor draft a Design Issue or PR. Trigger with \"/contribute\", \"what's my PR\nstatus\", \"find a contribution\", \"claim issue X\", \"draft a Design Issue\nfor Y\", \"refresh dossier for Z\".\n","allowed-tools":["Read","Write","Edit","Glob","Grep","AskUserQuestion","Task","Bash(gh:*)","Bash(git:*)","Bash(node:*)","Bash(pnpm:*)","Bash(yarn:*)","Bash(npm:*)","Bash(cargo:*)","Bash(pytest:*)","Bash(python:*)","Bash(python3:*)","Bash(bash:*)","Bash(jq:*)","Bash(base64:*)"],"compatibility":"Designed for Claude Code; requires gh CLI and jq on PATH"}},"renderedAt":1782980167277}

Contribute Command Center Overview Local-only OSS contribution workflow. The skill itself is the system — there is no separate CLI binary, dashboard, or cloud backend. State lives in three places: 1. GitHub itself — fetched live via for any PR/issue state question. Never cached long-term. 2. Markdown candidate files at — one per issue we're tracking. Frontmatter is the queryable layer (status, scout score, repo, research path, overrides). Body holds claim drafts, PR drafts, scope notes. 3. Markdown repo dossiers at — one per upstream repo we contribute to. Built by the subagent. Frontmatter i…