marketplace/bundles/plan-marshall/skills/workflow-integration-github/SKILL.md
GitHub provider for PR review workflows — three pure verbs (fetch_findings files comments to the ledger, post_responses transmits triaged dispositions, bot_completion reports a review bot's completion state) via gh CLI
npx skillsauth add cuioss/plan-marshall workflow-integration-githubInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
GitHub provider for the findings-pipeline pr-comment producer. The provider surface is exactly THREE pure, zero-LLM verbs — no triage judgment lives here:
fetch_findings — fetch PR review comments, apply the pre-filter (comment-patterns.json), and file one pr-comment finding per surviving comment via manage-findings add. The untrusted comment body is quarantined under raw_input.{body} (never embedded raw in the top-level detail); the batched manage-findings ingest pass promotes it to top-level only after validate_struct.post_responses — apply already-decided triage dispositions back to the PR, keyed by each finding's own hash_id, via a three-way transmit: thread-reply-then-resolve for a thread-bearing finding, ONE batched PR-level comment for the thread-less ones, and skipped only when there is genuinely nothing to say.bot_completion — report a review bot's registry completion_check_name check-run state ({status, in_progress, completed}) for the PR HEAD, so the automatic-review completion-aware poll can wait for a slow bot to finish before fetching; a bot with no completion check-run reports no_check_name and the caller falls back to the review_bot_buffer_seconds wait.All three verbs FAIL LOUD when GitHub is not configured (a typed unconfigured status, never a silent no-op). Uses the gh CLI for all GitHub operations.
Architectural context: This SKILL.md owns the producer-side CLI surface. For the producer→store→consumer→gate flow that connects this producer to the unified store, the per-domain
ext-triageconsumer dispatch, and the invariant gate, seeref-workflow-architecture/standards/findings-pipeline.md.
Execution mode: Three pure provider verbs — fetch_findings files PR review comments to the ledger (untrusted body quarantined under raw_input); post_responses transmits already-decided triage dispositions back to the PR; bot_completion reports a review bot's completion-check state for the completion-aware poll. Triage judgment lives in the consolidated triage pass, NOT in this provider.
Prohibited actions:
gh directly from LLM context; all operations go through script APIraw_input.* from a triage/response surface — read the top-level fields promoted by manage-findings ingestConstraints:
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| pr | int | no | auto-detect | PR number (auto-detects current branch's PR if omitted) |
| unresolved-only | bool | no | false | Only return unresolved comments (pr comments) |
workflow-integration-github (GitHub PR comment workflow)
├─> github_ops.py (GitHub operations via gh CLI — PR, CI, issue)
├─> github_pr.py (PR comment triage — delegates to github_ops for fetch)
├─> github_re_review.py (bot_kind-keyed re-review strategy registry)
└─> triage_helpers (ref-toon-format) — shared triage, error handling
This skill is the GitHub provider in the CI provider model. The central dispatcher (tools-integration-ci:ci) routes to this skill's github_ops.py for all GitHub operations.
# FIND: fetch + pre-filter + file one pr-comment finding per surviving comment (body quarantined under raw_input)
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_pr fetch_findings --pr-number 123 --plan-id EXAMPLE-PLAN
# RESPOND: apply already-decided dispositions (thread-reply + resolve-thread) back to the PR, keyed by hash_id
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_pr post_responses --pr-number 123 --plan-id EXAMPLE-PLAN
# Raw fetch (no filtering, no storage) — for ad-hoc inspection
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_pr fetch-comments --pr 123
# LLM consumer reads stored findings via manage-findings
python3 .plan/execute-script.py plan-marshall:manage-findings:manage-findings list --plan-id EXAMPLE-PLAN --type pr-comment
| Script | Notation | Purpose |
|--------|----------|---------|
| github_ops | plan-marshall:workflow-integration-github:github_ops | GitHub PR, CI, and issue operations via gh CLI |
| github_pr | plan-marshall:workflow-integration-github:github_pr | Producer-side PR review comment fetcher (fetch + pre-filter + store) |
| github_re_review | plan-marshall:workflow-integration-github:github_re_review | bot_kind-keyed re-review strategy registry (request + await a fresh bot review for the current HEAD) |
This skill is consumed by:
tools-integration-ci — CI dispatcher routes GitHub operations hereworkflow-pr-doctor — PR diagnosis workflowsphase-6-finalize — plan finalization with PR creationPurpose: Fetch all review comments for a PR.
Steps:
Get PR Comments
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr comments [--pr-number {number}] [--unresolved-only]
Return Comment List
Purpose: File PR review comments into the per-type finding store with the untrusted body quarantined, then let the consolidated triage pass drive dispositions, then transmit those dispositions back to the PR — all through the two pure provider verbs.
Provider contract: the provider surface is exactly fetch_findings (FIND) and post_responses (RESPOND). Neither makes a triage decision — triage judgment lives in the consolidated triage pass, not in the provider. fetch_findings fetches review comments, applies the comment-patterns.json keyword pre-filter, and files one pr-comment finding per surviving comment with the untrusted body quarantined under raw_input.{body}. The trusted structured metadata (thread_id, comment_id, kind, author, path, line) goes in the finding's detail.
Containment: the untrusted comment body is quarantined at file time under raw_input.{body} and promoted to the top level only by the single batched manage-findings ingest pass, which runs validate_struct over every raw_input.{field} (schema + length-cap + domain-allowlist). Triage then reads the clean top-level fields only, never raw_input.*. Containment is one deterministic batched boundary.
GitHub GraphQL ID Format Rules:
| Operation | Parameter | ID Field | Format Example |
|-----------|-----------|----------|----------------|
| thread-reply --thread-id | Comment's thread_id field | GraphQL node ID | PRRT_kwDO... |
| resolve-thread --thread-id | Comment's thread_id field | GraphQL node ID | PRRT_kwDO... |
Both operations take the same PRRT_ thread ID — pass the comment's thread_id field for either. The comment's id field (format PRRC_...) is never valid for thread-reply or resolve-thread. post_responses reads each finding's thread_id from its own detail block, keyed by hash_id — never a positional pairing. A finding whose thread_id is empty has no thread to reply into; its disposition goes out on the batched PR-comment path instead (see Workflow 2 step 4).
NEVER use numeric IDs — GitHub GraphQL requires global node IDs.
Steps:
FIND — file findings (untrusted body quarantined under raw_input):
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_pr fetch_findings --pr-number {pr} --plan-id {plan_id}
Output reports count_fetched, count_skipped_noise, count_stored, and producer_mismatch_hash_id (set when count_stored ≠ count_fetched − count_skipped_noise; the mismatch is also persisted as a Q-Gate finding under phase 5-execute with title prefix (producer-mismatch)). A status: unconfigured return means GitHub is not authenticated — never a silent zero-findings success.
INGEST — promote validated free-text to top-level (one batched deterministic pass over the whole ledger):
python3 .plan/execute-script.py plan-marshall:manage-findings:manage-findings ingest --plan-id {plan_id}
TRIAGE — one consolidated pass reads the clean top-level fields (never raw_input.*) and records a disposition per finding via manage-findings resolve --hash-id {hash} --resolution {fixed|suppressed|accepted|taken_into_account|rejected} --detail "{rationale}". The rationale becomes the resolution_detail that post_responses transmits.
RESPOND — apply dispositions back to the PR (keyed by hash_id):
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_pr post_responses --pr-number {pr} --plan-id {plan_id}
post_responses transmits every terminal-disposition finding through a three-way branch — no decision is lost and none is guessed at:
| Finding shape | Transmit | Recorded as |
|---------------|----------|-------------|
| no resolution_detail | nothing — there is genuinely nothing to say | skipped[], reason no_resolution_detail |
| thread_id present | thread-reply carrying the resolution_detail, then resolve-thread | responded[], transmit_mode: thread_reply, resolved_on_provider: true |
| thread_id empty, resolution_detail present | ONE batched PR-level comment for ALL such findings in the run, each section anchored on its source comment_id | responded[], transmit_mode: batched_issue_comment, resolved_on_provider: false |
Batching is deliberate: review_body findings from every bot are thread-less, so a per-finding comment would spam the PR. resolved_on_provider: false on that path is truthful — an issue comment has no resolvable thread, and reporting true would be a false signal.
Any disposition that had something to say but could not be delivered — a failed thread-reply, a failed resolve-thread, or a failed batched post (which untransmits the WHOLE batch) — lands in untransmitted[] with a reason, drives count_untransmitted, and sets the envelope status to partial. The envelope reports success only when count_untransmitted is 0; it is never unconditionally success.
Purpose: Close the post-merge re-review gap. When a HEAD-advancing branch operation in phase-6-finalize (branch-cleanup rebase/force-push, or a phase-5 loop-back fix commit) advances HEAD past the reviewed_commit_sha of the staged pr-comment findings, the new commits are unreviewed by automated bots. The re-review subcommand requests a fresh bot review for the new HEAD and polls until a review lands for it.
Strategy registry: github_re_review.py is a bot_kind-keyed registry with a strict two-method contract per strategy (request_fresh_review, await_fresh_review) and no speculative extensibility. The registry is GitHub-only — a sibling GitLab registry would be added separately without changing the consumer-side workflow docs. The canonical bot_kind list is imported from manage-findings/_findings_core.BOT_KINDS; the registry does not inline-copy the enum. Downstream consumers that need the enforcement-critical bot_kind list MUST reference that canonical source (or query a finding's bot_kind field) rather than hard-coding the values.
The strategies differ only in the trigger comment request_fresh_review posts — each posts an explicit trigger and uses the comment-post time as the trigger time:
| bot_kind | request_fresh_review | Trigger time |
|------------|------------------------|--------------|
| coderabbit | Posts @coderabbitai review. CodeRabbit's incremental auto-review on push is not a reliable trigger for the new HEAD (it can be debounced or skipped on a force-push), so the explicit comment is the trigger that guarantees a fresh review lands. | The comment-post time. |
| sourcery | Posts @sourcery-ai review. | The comment-post time. |
| pr-agent | Posts /review (PR-Agent does not auto-review on push). | The comment-post time. |
await_fresh_review is identical for every bot and is satisfied by either of two completion signals, checked in order of evidential strength:
| Signal | Match condition | Envelope |
|--------|-----------------|----------|
| review (preferred) | a review whose reviewed commit SHA equals --head-sha AND whose submittedAt strictly post-dates the trigger time, and which is not a refusal notice | matched_signal: review, matched_review: {…}, head_sha_verified: true |
| issue comment (fallback) | a comment whose author resolves to the awaited bot_kind, whose later of updated_at / created_at strictly post-dates the trigger time, and which is not a refusal notice | matched_signal: issue_comment, matched_comment: {…}, head_sha_verified: false |
Both signals additionally reject a refusal notice — a bot declining to review (rate-limit, diff-size, quota). The rejection is two-layer and identical on both paths: the awaited bot's registry ignore_patterns (its OBSERVED refusal strings) are matched first, with the author-independent structural _is_rate_limit_notice as the last-resort fallback for an unknown or renamed bot. A refusal carries no review, so counting it as a completion signal would assert review coverage that never happened. This applies to the review path as much as the comment path: a bot may submit its refusal as a review object rather than an issue comment, and the review path resolves first — so without the check the strongest signal (head_sha_verified: true) would be the one most likely to be false.
head_sha_verified: false on the comment path is load-bearing: an issue comment carries no reviewed-commit SHA, so the match proves the bot responded, not that it reviewed the new HEAD. The caller learns the strength of the evidence, not just the fact of a match. The comment path uses the LATER of updated_at / created_at so a bot that edits ONE persistent comment in place still registers as fresh activity. No bot is named in code — both matchers are generic across the registry.
Steps:
Invoke the registry for the new HEAD:
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_re_review re-review --pr-number {pr} --bot-kind {coderabbit|sourcery|pr-agent} --head-sha {new HEAD} --push-time {ISO8601 push time} [--timeout {seconds}] --plan-id {plan_id}
The subcommand resolves the strategy by bot_kind, runs request_fresh_review (posts each bot's registry trigger_comment — @coderabbitai review, @sourcery-ai review, /review — each using the comment-post time as the trigger time), then awaits either completion signal. The await budget is configurable via --timeout (default DEFAULT_CI_TIMEOUT); the phase-6-finalize trigger sites pass their re_review_await_timeout_seconds step-param value. It emits a TOON envelope with matched: true|false, timed_out: true|false, matched_signal (review | issue_comment, empty when unmatched), the matched matched_review / matched_comment record, and head_sha_verified.
Consume the match outcome. On matched: true, re-run fetch_findings to file the fresh review's comments, then re-run the consolidated ingest → triage → respond pass (Workflow 2). On matched: false / timed_out: true, the await budget expired with no fresh review — the consumer decides how to handle the timeout. This registry surfaces timed_out and does NOT decide policy itself; the timeout-handling responsibility (the re_review_on_timeout ask/defer/proceed branches) lives in the two trigger docs: trigger A in phase-6-finalize/standards/branch-cleanup.md § "On re-review timeout (trigger A)" and trigger B in automatic-review § "On re-review timeout (trigger B)".
Registry extension pattern: to support a new bot_kind, add its automatic-review/standards/{bot_kind}.md registry doc and nothing else. _findings_core.BOT_KINDS, the login→bot_kind map, the --bot-kind choices= surface, and the strategy instance all DERIVE from that data. There is exactly ONE generic strategy class parameterized by the doc's trigger_comment — no per-bot subclass, and neither request_fresh_review nor await_fresh_review is re-implemented per bot.
standards/comment-patterns.json is a pre-filter only — it drops obvious noise (bot signatures, "lgtm", "thanks!") before findings are written. Classification of surviving comments belongs to the consolidated triage pass, which reads the validated top-level body (promoted from raw_input.{body} by the batched manage-findings ingest pass) — never the raw un-ingested raw_input.*.
The repo merge-queue enable path reads two optional, org-agnostic marshal.json keys under the top-level merge_queue block to weave a bypass actor into the plan-marshall-merge-queue ruleset — letting an org release-automation app (tag + version-bump push straight to the protected branch) proceed without a GH013 push-protection rejection. Both are absent by default; when neither is set the ruleset is created with no bypass_actors (the bypass-less behavior).
| Key | Type | Purpose |
|-----|------|---------|
| merge_queue.bypass_app_id | int | Static numeric GitHub App id — the config-only preferred path. When set, its id is used directly as an Integration bypass actor (bypass_mode: always) with no gh api call, so it works on both org-owned and personal-account repos. |
| merge_queue.bypass_app_slugs | list[str] | App slugs for the best-effort org-list fallback, used only when bypass_app_id is unset. Each slug is matched against gh api /orgs/{owner}/installations and the matched installation's app id is used. This path requires admin:org scope on an org-owned repo; it no-ops gracefully (no bypass actor, no error) when that precondition is unmet. |
On the idempotent already-configured path, enable self-heals the ruleset's bypass_actors: when a resolved id is not already present as an Integration/bypass_mode: always actor — either wholly absent, or present but carrying the wrong actor_type/bypass_mode — the wrong-shaped entry (if any) is dropped and the id is PATCHed back in with the correct Integration/always shape, so the merged set carries exactly one bypass actor per resolved id.
The canonical argparse surface for the three CLI scripts owned by this skill,
github_ops.py, github_pr.py, and github_re_review.py. The D4 plugin-doctor analyzer
(_analyze_manage_invocation.py) reads this section as source-of-truth for markdown
notation occurrences across the marketplace. Consuming skills xref this section by
name (e.g., "see workflow-integration-github Canonical invocations →
pr create") instead of restating the command inline. The sibling
github_provider.py module exposes provider declarations and shared helpers — it
has no CLI surface and is not invoked directly.
Both github_ops and github_pr accept the top-level --plan-id PLAN_ID /
--project-dir DIR routing pair (mutually exclusive) consumed before argparse runs.
github_re_review accepts the same --project-dir DIR routing flag; its
re-review subcommand declares its own --plan-id (accepted for routing uniformity).
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr view \
[--head BRANCH]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr list \
[--head BRANCH] [--state {open|closed|all}]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr create \
--plan-id PLAN_ID --title TEXT \
[--slot SLOT] [--base BRANCH] [--draft] [--head BRANCH]
The PR body is supplied via the path-allocate pattern — call pr prepare-body
first, write the body to the returned path, then run pr create.
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr edit \
--plan-id PLAN_ID --pr-number N \
[--slot SLOT] [--title TEXT]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr reply \
--plan-id PLAN_ID --pr-number N [--slot SLOT]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr resolve-thread \
--thread-id ID [--pr-number N]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr thread-reply \
--plan-id PLAN_ID --pr-number N --thread-id ID [--slot SLOT]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr reviews \
--pr-number N
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr comments \
--pr-number N [--unresolved-only]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr wait-for-comments \
--pr-number N [--timeout SECS] [--interval SECS]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr merge \
(--pr-number N | --head BRANCH) \
[--strategy {merge|squash|rebase}] [--delete-branch]
Exactly one of --pr-number or --head is required (validated by handler).
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr auto-merge \
(--pr-number N | --head BRANCH) \
[--strategy {merge|squash|rebase}]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr update-branch \
(--pr-number N | --head BRANCH)
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr close \
--pr-number N
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr ready \
--pr-number N
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr submit-review \
--review-id PRR_ID \
[--event {COMMENT|APPROVE|REQUEST_CHANGES}]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr prepare-body \
--plan-id PLAN_ID [--for {create|edit}] [--slot SLOT]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops pr prepare-comment \
--plan-id PLAN_ID [--for {reply|thread-reply}] [--slot SLOT]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops checks status \
(--pr-number N | --head BRANCH)
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops checks wait \
--pr-number N [--timeout SECS] [--interval SECS]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops checks wait-for-status-flip \
--pr-number N [--timeout SECS] [--interval SECS] \
[--expected {success|failure|any}]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops checks rerun \
--run-id ID
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops checks logs \
--run-id ID
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops issue create \
--plan-id PLAN_ID --title TEXT \
[--slot SLOT] [--labels CSV]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops issue prepare-body \
--plan-id PLAN_ID [--slot SLOT]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops issue view \
--issue REF
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops issue close \
--issue REF
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops issue wait-for-close \
--issue-number N [--timeout SECS] [--interval SECS]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops issue wait-for-label \
--issue-number N --label TEXT \
[--mode {present|absent}] [--timeout SECS] [--interval SECS]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_ops branch delete \
--remote-only --branch BRANCH
--remote-only is a required, explicit flag.
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_pr fetch-comments \
[--pr N] [--unresolved-only]
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_pr fetch_findings \
--pr-number N --plan-id PLAN_ID
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_pr post_responses \
--pr-number N --plan-id PLAN_ID
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_pr bot_completion \
--pr-number N --bot-kind {coderabbit|sourcery|pr-agent}
Pure provider read — reports the bot's registry completion_check_name check-run state as {status, in_progress, completed} for the PR HEAD. A bot with an empty completion_check_name reports status no_check_name (the caller falls back to the review_bot_buffer_seconds wait); the automatic-review completion-aware poll consumes this verb.
python3 .plan/execute-script.py plan-marshall:workflow-integration-github:github_re_review re-review \
--pr-number N --bot-kind {coderabbit|sourcery|pr-agent} --head-sha SHA --push-time ISO8601 \
[--timeout SECONDS] [--plan-id PLAN_ID]
--timeout SECONDS bounds the await_fresh_review poll (default DEFAULT_CI_TIMEOUT); consumers (the trigger-A / trigger-B re-review sites in phase-6-finalize) supply their re_review_await_timeout_seconds step-param value here.
| Failure | Action |
|---------|--------|
| pr comments failure | Report error to caller with stderr details |
| triage failure | Log warning, skip comment, continue |
| CI router failure | Log warning, continue — best-effort |
plan-marshall:tools-integration-ci — Central CI dispatcherplan-marshall:workflow-integration-gitlab — GitLab provider counterpartplan-marshall:workflow-pr-doctor — PR diagnosis workflowsdevelopment
Domain-owned OpenRewrite log-line finding parser for the java-cui domain — parses the
development
Domain-owned OpenRewrite marker detection for the java-cui domain — scans Java/Kotlin sources for cui-rewrite TODO markers, categorizes them by recipe, and fails the gate on any detected marker
development
Operator control surface for the marshalld build server — enrol/drop a project in the machine-global registry (the opt-in enable signal and anti-laundering wall), manage the daemon lifecycle (start, stop, drain, status, install, upgrade) version-pinned to the verified bundle copy, and inspect the daemon's per-project interaction-audit log (read-only)
tools
The tiny build-consumption client for the marshalld build server — submit a build job, bounded long-poll for its result, ping the daemon identity, and preflight registry-plus-liveness in one call; consumption only, never provisioning or enrolment