plan-do-review

star 5

[DEPRECATED — superseded 2026-05-15 by /project-tick + /triage + /plan + /do + /review-pr] Review a proposal issue with adversarial critics, converge on a plan, execute it, and iterate review-fix until clean. Drives the henyey project board (Backlog → in plan → In progress → In review → Done; Blocked on failure).

stellar-experimental By stellar-experimental schedule Updated 5/15/2026

name: plan-do-review description: "[DEPRECATED — superseded 2026-05-15 by /project-tick + /triage + /plan + /do + /review-pr] Review a proposal issue with adversarial critics, converge on a plan, execute it, and iterate review-fix until clean. Drives the henyey project board (Backlog → in plan → In progress → In review → Done; Blocked on failure)." argument-hint: "[issue-number] [--model ] [--max-proposal-rounds N] [--max-review-rounds N]"

⚠️ DEPRECATED — 2026-05-15. This skill is superseded by the modular pipeline:

  • /triage — backlog gate
  • /plan — adversarial plan drafting (parallel critics)
  • /do — implementation (two-mode)
  • /review-pr — adversarial PR review with auto-merge
  • /project-tick — orchestrator that dispatches to the right specialist

Driver loop: scripts/project-tick-loop.sh. Plan document: /home/tomer/.claude/plans/our-current-project-management-calm-biscuit.md.

This skill remains in tree for one release cycle as a fallback. Do not call it from new workflows. After the cycle, this file will be removed.

Parse $ARGUMENTS:

  • The first positional argument, if present, is a GitHub issue number.
  • --model <model>: Model for critic and review agents (default: "gpt-5.4").
  • --max-proposal-rounds N: Max proposal↔critic iterations (default: 5).
  • --max-review-rounds N: Max implement↔review-fix iterations (default: 3).

If no issue number was provided, auto-select one.

Auto-select only unassigned issues. Never auto-select an issue that is already assigned to anyone (including yourself) — an existing assignment signals that another worker, or a prior session of this skill, is or was working on it. Auto-claiming it races with that worker and silently piggybacks on stale context. To resume an in-progress issue, the caller must pass the issue number explicitly as an argument.

Auto-selection is project-board-native. Eligible issues are those on the henyey project (stellar-experimental/henyey, project #2) currently in the Backlog column. Issues not yet on the project are NOT auto-selectable — the operator adds them to the board first. The Blocked, in plan, In progress, In review, and Done columns are excluded automatically by the Backlog filter; that is how the board replaces the legacy not-ready and plan-do-review-loop-failed labels.

Fetch all open Backlog items once, then pick by priority label (urgenthighmediumlow) with oldest-first as the tiebreaker:

# One paginated GraphQL query for all Backlog items on project #2.
# `--paginate` requires the cursor variable to be named exactly $endCursor;
# `jq -s` is required because gh outputs one JSON object per page.
backlog_json=$(gh api graphql --paginate -f query='
  query($org: String!, $proj: Int!, $endCursor: String) {
    organization(login: $org) {
      projectV2(number: $proj) {
        items(first: 100, after: $endCursor) {
          pageInfo { endCursor hasNextPage }
          nodes {
            status: fieldValueByName(name: "Status") {
              ... on ProjectV2ItemFieldSingleSelectValue { optionId }
            }
            content {
              ... on Issue {
                number title createdAt state
                assignees(first: 5) { nodes { login } }
                labels(first: 20)    { nodes { name } }
              }
            }
          }
        }
      }
    }
  }' -f org=stellar-experimental -F proj=2)

# Backlog option id is f75ad846 (verified). Pick the first eligible issue
# per priority tier; oldest first within a tier.
ISSUE=
for priority in urgent high medium low; do
  ISSUE=$(jq -rs --arg p "$priority" '
    [ .[].data.organization.projectV2.items.nodes[]
      | select(.status.optionId == "f75ad846")
      | select(.content.state == "OPEN")
      | select((.content.assignees.nodes | length) == 0)
      | select(.content.labels.nodes | map(.name) | index($p))
    ] | sort_by(.content.createdAt) | .[0].content.number // empty
  ' <<< "$backlog_json")
  [ -n "$ISSUE" ] && break
done

# Priority-5 fallback: any Backlog open unassigned, oldest first.
if [ -z "$ISSUE" ]; then
  ISSUE=$(jq -rs '
    [ .[].data.organization.projectV2.items.nodes[]
      | select(.status.optionId == "f75ad846")
      | select(.content.state == "OPEN")
      | select((.content.assignees.nodes | length) == 0)
    ] | sort_by(.content.createdAt) | .[0].content.number // empty
  ' <<< "$backlog_json")
fi

If $ISSUE is still empty, stop with a message: "No eligible unassigned issues found in Backlog for auto-selection."

Otherwise, set $ISSUE to the selected issue number and announce: "Auto-selected issue #$ISSUE: ".</p> <p><strong>Set <code>$AUTO_SELECTED</code> = <code>true</code></strong> when the issue was auto-selected (no argument provided), or <code>false</code> when an explicit issue number was given.</p> <p><strong>Assign the issue to yourself as a concurrency lock</strong> (applies to both auto-selected and explicit issue numbers):</p> <pre><code class="language-bash">gh issue edit $ISSUE --add-assignee "@me" </code></pre> <p><strong>Verify we hold the lock alone.</strong> <code>gh issue edit --add-assignee</code> does NOT fail when someone else is already assigned — it silently appends. To detect a race (another worker assigned themselves between our query and our assignment), re-fetch the issue and confirm we are the <em>only</em> assignee:</p> <pre><code class="language-bash">ME=$(gh api user -q .login) ASSIGNEES=$(gh issue view $ISSUE --json assignees --jq '[.assignees[].login] | join(",")') if [ "$ASSIGNEES" != "$ME" ]; then # Someone else holds (or shares) the lock — back off and stop. gh issue edit $ISSUE --remove-assignee "@me" echo "Could not claim issue #$ISSUE exclusively — assignees: $ASSIGNEES" exit 0 fi </code></pre> <p>If verification fails, <strong>stop</strong> with the message above. Do NOT proceed to Step 1.</p> <p><strong>Failure handling:</strong> If the skill fails at any point, before stopping:</p> <ol> <li>Unassign yourself to release the concurrency lock:<pre><code class="language-bash">gh issue edit $ISSUE --remove-assignee "$(gh api user -q .login)" </code></pre> </li> <li>Move the issue to the <code>Blocked</code> column. This applies whether the issue was auto-selected or passed explicitly — the operator can review the failure and re-triage by moving it back to <code>Backlog</code>. Auto-select never picks Blocked items, so this also prevents infinite retry loops:<pre><code class="language-bash">bash .github/skills/shared/scripts/move-issue-status.sh "$ISSUE" Blocked </code></pre> </li> </ol> <p>This releases the lock so other workers can see the issue is no longer in progress.</p> <h2>Plan-Do-Review</h2> <p>Adversarial proposal refinement → full implementation → iterative review-fix.</p> <p>This skill automates the workflow: read a GitHub issue proposal, have an independent agent critique it, rewrite incorporating feedback, repeat until the proposal converges, then execute the plan, have it reviewed, fix issues, and iterate until the review is clean.</p> <p>The orchestrator (you) manages state, rewrites proposals, and implements code. All reviews and critiques are delegated to independent sub-agents so that review is adversarial and unbiased.</p> <hr> <h2>Guiding Principles</h2> <p><strong>Prefer long-term readable, sustainable, elegant, safe building blocks over short-term patches — even at the cost of significant refactors.</strong></p> <p>These principles apply at every stage of this skill — when rewriting the proposal, when critiquing it, and when executing it:</p> <ul> <li><strong>Root causes over symptoms.</strong> Address the underlying design flaw, not the surface manifestation. A fix that handles only the one caller that reported a bug is almost always the wrong answer when several callers share the same broken assumption.</li> <li><strong>Readable.</strong> Favor code that is easy for a newcomer to read and reason about. Name things well. Break long functions. Let control flow mirror the problem domain.</li> <li><strong>Sustainable.</strong> Favor designs that remain correct as the code evolves. Push invariants into types (newtypes, enums, const generics), constructors, and shared helpers so future changes cannot accidentally violate them.</li> <li><strong>Elegant.</strong> Favor the minimum set of concepts that expresses the solution cleanly. Prefer standard Rust idioms — ownership over cloning, iterators over index loops, <code>?</code> over <code>match</code> chains, <code>Result</code>/<code>Option</code> over sentinel values.</li> <li><strong>Safe.</strong> Favor designs that make incorrect states unrepresentable and error paths explicit. Never fail silently. Close races; don't paper over them with retries or sleeps.</li> <li><strong>Scope honestly.</strong> If the best fix requires changing a public API, restructuring types, introducing <code>Arc</code>/<code>Cow</code>/lifetimes, splitting or merging modules, or touching several crates — propose that. The goal is long-term code health, not the smallest possible diff. Deferred refactors should be filed as follow-up issues, not papered over.</li> <li><strong>Parity-preserving.</strong> In protocol/consensus/ledger code, long-term elegance never justifies diverging from stellar-core behavior. Match stellar-core semantics exactly; elegance applies to <em>how</em> we express those semantics in Rust.</li> </ul> <p>When these principles conflict with proposal minimalism, the principles win. A significant refactor that produces a sound, idiomatic foundation is always preferable to a narrow patch that leaves the latent design problem in place.</p> <hr> <h2>Step 1: Fetch and Parse the Issue</h2> <p>Initialize context variables:</p> <pre><code class="language-bash">HARNESS="${HARNESS:-Copilot CLI}" export HARNESS </code></pre> <p>Run:</p> <pre><code class="language-bash">gh issue view $ISSUE --json title,body,labels,state,comments,number </code></pre> <p>Extract:</p> <ul> <li><strong>Title</strong>: the issue title</li> <li><strong>Body</strong>: the full proposal / description</li> <li><strong>Comments</strong>: any existing discussion (prior reviews, context)</li> <li><strong>State</strong>: must be open (if closed, stop and report)</li> </ul> <h3>Resume from Prior Run</h3> <p>Before the dependency check or readiness triage, check whether a previous invocation already made progress on this issue. Scan the comments for <code>## 📝 Proposal Draft (Round N/M)</code> and <code>## 🔍 Critic Response (Round N/M)</code> headers.</p> <p><strong>If prior proposal/critic comments exist:</strong></p> <ol> <li>Find the <strong>highest round number</strong> across all <code>📝 Proposal Draft</code> comments. Call this <code>last_proposal_round</code>.</li> <li>Check whether a <code>🔍 Critic Response (Round last_proposal_round/M)</code> comment exists for that round.</li> <li><strong>If a critic response exists for the last round:</strong><ul> <li>Extract its verdict. If <code>APPROVED</code>, check for a <code>## Converged Proposal</code> comment — if present, skip straight to Step 3 (implementation). If no converged proposal, post one and proceed to Step 3.</li> <li>If <code>REVISE</code>, extract the numbered feedback items from that critic response. Set <code>proposal_round = last_proposal_round</code>. Extract the last proposal text as <code>current_proposal</code>. Skip Step 1 exploration and readiness triage — proceed directly to Step 2's REVISE handler (investigate feedback, rewrite, loop to 2a).</li> </ul> </li> <li><strong>If no critic response exists for the last round:</strong><ul> <li>The previous run posted a proposal but crashed before the critic ran. Extract the last proposal text as <code>current_proposal</code>. Set <code>proposal_round = last_proposal_round - 1</code> (so the next increment brings it back to the same round number). Skip exploration — proceed directly to Step 2a (spawn critic for the existing proposal).</li> </ul> </li> <li><strong>If a <code>## Converged Proposal</code> comment exists:</strong><ul> <li>Skip to Step 3. Check for existing worktree/branch from prior run.</li> </ul> </li> </ol> <p><strong>If no prior comments exist</strong>, proceed normally to Blocker-Ancestor Resolution and Readiness Triage below.</p> <blockquote> <p><strong>Why this matters.</strong> Without resume, a context-window crash causes the loop script to restart from scratch — re-exploring, re-proposing from Round 1, and wasting all prior convergence progress. With resume, each restart picks up where the last one left off, making forward progress even across multiple session crashes.</p> </blockquote> <h3>Dependency Check</h3> <p>Before triaging readiness, check whether the issue has <strong>unmet dependencies</strong> (is blocked by another open issue).</p> <p><strong>Procedure:</strong></p> <ol> <li><p>Read the current issue's body and comments. Using your understanding of the text, identify any issue numbers that this issue is <strong>blocked by</strong> — look for patterns like "blocked by #N", "depends on #N", "requires #N first", tasklist items <code>- [ ] #N</code>, or similar contextual references that indicate a prerequisite relationship. <strong>Only</strong> extract issues that are genuine blockers; ignore issues that are merely referenced or related.</p> </li> <li><p>For each candidate blocker, fetch it:</p> <pre><code class="language-bash">gh issue view <N> --json number,state </code></pre> <p>Filter to only <strong>open</strong> issues. If no open blockers remain, the current issue is not actually blocked — continue to Readiness Triage.</p> </li> <li><p>If any open blocker(s) exist, the issue has unmet dependencies. <strong>Stop</strong>:</p> <ol> <li>Move the issue to <code>Blocked</code>:<pre><code class="language-bash">bash .github/skills/shared/scripts/move-issue-status.sh "$ISSUE" Blocked </code></pre> </li> <li>Post a comment listing the blockers:<pre><code class="language-bash">gh issue comment $ISSUE --body "Moved to Blocked: depends on open issue(s) #X, #Y. Will be retried once dependencies are resolved." </code></pre> </li> <li>Unassign yourself:<pre><code class="language-bash">gh issue edit $ISSUE --remove-assignee @me </code></pre> </li> <li><strong>Stop.</strong> Do not proceed to Readiness Triage or Step 2.</li> </ol> </li> </ol> <p>If no unmet dependencies, continue into Readiness Triage.</p> <hr> <h3>Readiness Triage</h3> <p>Before proceeding, assess whether the issue is actionable. An issue is <strong>not ready</strong> if any of these are true:</p> <ul> <li>The body is empty or contains only a vague one-liner with no concrete proposal</li> <li>It requires information or decisions that are not yet available</li> <li>It describes a problem but proposes no approach and the correct approach is unclear even after reading the referenced code</li> </ul> <p>If the issue is <strong>not ready</strong>:</p> <ol> <li>Move the issue to <code>Blocked</code>:<pre><code class="language-bash">bash .github/skills/shared/scripts/move-issue-status.sh "$ISSUE" Blocked </code></pre> </li> <li>Post a comment explaining why the issue is not ready and what is needed:<pre><code class="language-bash">gh issue comment $ISSUE --body "Moved to Blocked: {reason}. This issue needs {what's missing} before it can be picked up." </code></pre> </li> <li>Unassign yourself:<pre><code class="language-bash">gh issue edit $ISSUE --remove-assignee @me </code></pre> </li> <li>Stop. Do not proceed to Step 2.</li> </ol> <p>If the issue <strong>is ready</strong>, the Step 2 entry hook below will move it from its current column (typically <code>Backlog</code>, but sometimes <code>Blocked</code> if a prior run blocked it and the dependency has since been resolved) into <code>in plan</code>. No explicit cleanup is needed here.</p> <p>Build context for the issue — but <strong>budget your context aggressively</strong>.</p> <blockquote> <p><strong>Context budget rule.</strong> You must survive the full skill lifecycle: up to 5 proposal↔critic rounds, then implementation, then up to 3 review-fix rounds. If you exhaust your context window during exploration, the session will exit before the proposal even converges.</p> <p><strong>Hard limits for initial exploration (Round 0):</strong></p> <ul> <li>Read at most <strong>15 files</strong> (via <code>view</code> or <code>view_range</code>).</li> <li>Each read should be a targeted <code>view_range</code> of 20–50 lines, not a full file.</li> <li>Total exploration output should stay under ~3,000 lines. If you hit this, stop and write your first draft with what you have.</li> </ul> <p><strong>Limits for subsequent rounds (Rounds 2+):</strong></p> <ul> <li>Do NOT re-explore broadly. Only re-read specific lines that the critic flagged — typically 1–3 targeted <code>view_range</code> calls per feedback item.</li> <li>If the critic claims a code path exists that you didn't see, verify with a single <code>grep</code> + one <code>view_range</code>. Do not read surrounding context "just in case."</li> </ul> <p><strong>General rules:</strong></p> <ul> <li><strong>Prefer <code>grep</code>/<code>glob</code> over <code>view</code>.</strong> Search for specific symbols, function names, or config keys mentioned in the issue. Do not read entire files when a 5-line match suffices.</li> <li><strong>Prefer <code>view_range</code> over full-file reads.</strong> When you need to read code, read only the relevant function or block (20–50 lines), not the entire file.</li> <li><strong>Stop exploring once you can write a first draft.</strong> Your first proposal does not need to be perfect — the critic agent has its own full context window and will verify claims against the codebase. Trust the critic to catch what you missed; that is the whole point of the adversarial loop.</li> <li><strong>Do not pre-read code "just in case."</strong> Only read code that directly informs a specific claim in your proposal.</li> </ul> </blockquote> <p>Initialize tracking:</p> <pre><code>proposal_round = 0 review_round = 0 current_proposal = <issue body + any relevant context> </code></pre> <h3>Proposal Output Requirements</h3> <p>Every proposal (initial and subsequent rewrites) should include:</p> <ul> <li><strong>## Problem</strong> — clear statement of the root cause</li> <li><strong>## Proposed Fix</strong> — concrete changes and their rationale</li> <li><strong>## Affected Paths</strong> — files, functions, and code paths that will be modified</li> <li><strong>### Parity Verification</strong> — evidence that this change maintains stellar-core parity:<ul> <li>Feature behavior vs stellar-core v25: [link to code reference or "N/A — new feature"]</li> <li>Determinism/config assumptions: [file:line evidence or rationale]</li> <li>Edge cases checked: [list or "n/a"]</li> </ul> </li> <li><strong>### Test Strategy</strong> — (optional but strongly recommended) outline of how the fix will be tested:<ul> <li>Unit tests: [areas to cover]</li> <li>Integration/regression tests: [scenarios]</li> <li>Existing tests affected: [changes needed or "none"]</li> </ul> </li> <li><strong>### Affected/Similar Paths Searched</strong> — list search terms, call graph paths checked, confirmed affected locations, and "not affected because…" notes. Allow <code>n/a</code> only with concrete justification (e.g., "new invariant local to this module").</li> </ul> <hr> <h2>Step 2: Proposal Convergence Loop</h2> <p><strong>Move the issue to <code>in plan</code></strong> — proposal convergence is starting. This is idempotent on resume: a prior run may have already moved it here, in which case this is a no-op.</p> <pre><code class="language-bash">bash .github/skills/shared/scripts/move-issue-status.sh "$ISSUE" "in plan" </code></pre> <p>Repeat until <code>VERDICT: APPROVED</code> or <code>proposal_round >= max_proposal_rounds</code>:</p> <blockquote> <p><strong>Forced convergence rule.</strong> If <code>proposal_round == max_proposal_rounds - 1</code> (the penultimate round) and the critic returns <code>REVISE</code>, do NOT loop back for another critic round. Instead, incorporate the feedback into a final rewrite, treat it as converged, and proceed to Step 2c. This prevents the common failure mode where the agent crashes from context exhaustion on the last round, wasting all prior progress. The converged proposal comment should note: "Converged after forced acceptance at round N (critic did not fully approve)."</p> </blockquote> <h3>2a: Spawn Critic Agent</h3> <p>Increment <code>proposal_round</code>.</p> <p><strong>Post the proposal draft to the issue.</strong></p> <blockquote> <p><strong>CRITICAL — read before posting.</strong> The pattern <code>gh issue comment ... --body "$(cat <<'EOF' ... EOF)"</code> is a template. The <code>{placeholder}</code> tokens must be replaced with <strong>literal text</strong> before the bash command runs. Do NOT substitute a placeholder with a shell expression like <code>$(cat /tmp/foo.md)</code> — the heredoc is single-quoted, so no shell expansion happens, and the literal string <code>$(cat /tmp/foo.md)</code> ends up in the GitHub comment body. To avoid this class of bug, use <code>--body-file</code> to point <code>gh</code> at a file on disk, which bypasses shell interpolation entirely. Write the full comment body (header + content + footer) to a temporary file, then post it as one unit.</p> </blockquote> <pre><code class="language-bash"># Preferred pattern — write to a file, then post via --body-file. tmpfile=$(mktemp) { printf '## 📝 Proposal Draft (Round %s/%s)\n\n' "$proposal_round" "$max_proposal_rounds" cat data/pdr-$ISSUE/proposal_r$proposal_round.md printf '\n\n---\n\n*Submitting to adversarial critic for review…*\n' printf '\n---\n\n*Created by `/plan-do-review` skill (%s, model: %s)*\n' "$HARNESS" "$MODEL" } > "$tmpfile" gh issue comment $ISSUE --body-file "$tmpfile" rm -f "$tmpfile" </code></pre> <p><strong>Do NOT use this legacy heredoc pattern</strong> — it is a footgun when sub-agents do the textual substitution, as evidenced by the 16-comment incident on #1759/#1768:</p> <pre><code class="language-bash"># ❌ DO NOT USE — substituting {current_proposal} with `$(cat ...)` leaves the literal string in the comment. gh issue comment $ISSUE --body "$(cat <<'DRAFT_EOF' ## 📝 Proposal Draft (Round {proposal_round}/{max_proposal_rounds}) {current_proposal} --- *Submitting to adversarial critic for review…* DRAFT_EOF )" </code></pre> <p>Launch a background agent using the Task tool:</p> <ul> <li><strong>agent_type</strong>: <code>"general-purpose"</code></li> <li><strong>model</strong>: <code>$MODEL</code></li> <li><strong>name</strong>: <code>"critic-round-{proposal_round}"</code></li> <li><strong>description</strong>: <code>"Critique proposal round {proposal_round}"</code></li> </ul> <p>The critic agent prompt must include:</p> <pre><code>You are an independent technical reviewer for a software proposal on the henyey project (a Rust implementation of stellar-core). Your job is to find gaps, incorrect assumptions, missing edge cases, scope issues, and impractical suggestions. ## The Proposal {current_proposal} ## Codebase Context This is the henyey project — a Rust port of stellar-core. Key conventions: - Determinism and stellar-core parity are non-negotiable - Tests use Rust's built-in #[test] framework - Code lives in crates/ (e.g., crates/tx, crates/ledger, crates/herder) - stellar-core reference is at stellar-core/ (git submodule, pinned to v25.x) ## Your Task Evaluate this proposal thoroughly: 1. **Correctness**: Are the technical claims accurate? Do the referenced code paths exist and behave as described? 2. **Completeness**: Does the proposal cover all affected code paths? Are there cases it misses? 3. **Spec clarity**: Does the proposal define exact ledger state, protocol version limits, config flags, pre/post conditions, and determinism assumptions? Are boundary conditions explicit? 4. **Feasibility**: Can this be implemented as described? Are there practical obstacles? 5. **Risk**: What could go wrong? What edge cases are not addressed? 6. **Scope**: Is the scope appropriate? Too broad? Too narrow? 7. **Stellar-core parity**: Will the proposed changes maintain or improve parity with stellar-core? Are citations to stellar-core code provided? 8. **Structural ambition**: Does the proposal go far enough? Could a bigger refactor — changing public APIs, restructuring types, using Arc/Cow/lifetimes, redesigning enums, splitting or merging modules — eliminate the *class* of bug rather than patching the one symptom? Prefer structural solutions that make incorrect states unrepresentable over minimal fixes that address one instance. 9. **Readability & sustainability**: Will the resulting code be easy to read, modify, and extend in six months? Does it push invariants into types (newtypes, enums, constructors) or shared helpers so future callers can't silently get it wrong? Does it use idiomatic Rust — ownership over cloning, iterators over index loops, `?` over match chains, `Result`/`Option` over sentinel values? Does it name things well and keep functions focused? 10. **Long-term vs. short-term tradeoff**: Is the proposed fix a durable building block, or a short-term patch that leaves the underlying design problem in place? If the latter, flag it — this skill explicitly prefers significant refactors that produce sound foundations over narrow patches that will need to be undone. ## Output Format You MUST end your response with exactly one of these verdicts: VERDICT: APPROVED (if the proposal is sound and ready for implementation) VERDICT: REVISE (if the proposal needs changes — list specific actionable items below) If REVISE, list each required change as a numbered item: 1. [specific actionable feedback] 2. [specific actionable feedback] ... Be concrete. "Needs more detail" is not actionable. "Add handling for the case where X is None at file.rs:123" is actionable. </code></pre> <h3>2b: Process Critic Result</h3> <p>Read the agent result. Extract the verdict line.</p> <p><strong>You MUST post the critic response to the issue before processing the verdict.</strong> This is not optional — the issue comment trail is the audit log. Do not skip this step, even if the verdict is APPROVED.</p> <blockquote> <p><strong>Context hygiene.</strong> The critic's full response can be very large. After posting it to the issue (below), extract only the <strong>verdict</strong> and the <strong>numbered feedback items</strong> into your working state. Do not keep the full critique text in your conversational context — it is preserved in the issue comment for the audit trail. When investigating feedback items, do targeted <code>grep</code>/<code>view_range</code> lookups rather than re-reading everything the critic referenced.</p> <p><strong>Prior-round trimming.</strong> After each round, discard all prior proposal drafts and critic responses from your working context. Your working state should contain only:</p> <ol> <li>The <strong>current (latest) proposal</strong> text</li> <li>The <strong>numbered feedback items</strong> from the most recent critic response</li> <li>The issue title and body (for reference)</li> </ol> <p>Prior rounds are preserved in the issue comment trail — you do not need them in context. This is critical for surviving 5 rounds without context exhaustion.</p> </blockquote> <pre><code class="language-bash"># Use --body-file (see the CRITICAL note in Step 2a). tmpfile=$(mktemp) { printf '## 🔍 Critic Response (Round %s/%s)\n\n' "$proposal_round" "$max_proposal_rounds" printf '<details>\n<summary>Full critique (click to expand)</summary>\n\n' cat data/pdr-$ISSUE/critic_r$proposal_round.md printf '\n\n</details>\n\n' printf '**Verdict: %s**\n\n' "$verdict" # If REVISE, append the numbered feedback items outside the <details> block # here (either inline printf lines or cat a second file). printf '---\n\n*Created by `/plan-do-review` skill → `critic-round-%s` sub-agent (%s, model: %s)*\n' "$proposal_round" "$HARNESS" "$MODEL" } > "$tmpfile" gh issue comment $ISSUE --body-file "$tmpfile" rm -f "$tmpfile" </code></pre> <p><strong>If <code>VERDICT: APPROVED</code></strong>:</p> <ul> <li>The proposal has converged. Proceed to Step 3.</li> </ul> <p><strong>If <code>VERDICT: REVISE</code></strong>:</p> <ul> <li>Extract the numbered feedback items.</li> <li><strong>Drop all prior proposal/critic text from your working context</strong> — you only need the current proposal and these feedback items going forward.</li> <li>Investigate each feedback item — but <strong>respect the Round 2+ exploration budget</strong>: only read specific lines the critic referenced (1–3 targeted <code>view_range</code> calls per item). Do not re-explore the codebase broadly.</li> <li>Verify the critic's claims, determine which feedback is valid.</li> <li>Rewrite <code>current_proposal</code> incorporating valid feedback. Discard feedback that is incorrect (explain why in the rewrite).</li> <li>The rewrite should be a complete, self-contained proposal (not a diff).</li> <li><strong>Check forced convergence</strong>: if <code>proposal_round >= max_proposal_rounds - 1</code>, do not loop back to 2a. Instead, proceed to 2c with this rewrite as the final proposal. Note in the converged proposal that the critic did not fully approve.</li> <li>Otherwise, loop back to 2a with the updated proposal.</li> </ul> <p><strong>If neither verdict found</strong> (agent error):</p> <ul> <li>Treat as <code>VERDICT: REVISE</code> with feedback "Agent did not produce a clear verdict — review the proposal structure and clarity."</li> </ul> <h3>2c: Post Converged Proposal</h3> <p>After convergence (or max rounds), post the final proposal as a GitHub issue comment:</p> <pre><code class="language-bash"># Use --body-file (see the CRITICAL note in Step 2a). tmpfile=$(mktemp) { printf '## Converged Proposal (Round %s/%s)\n\n' "$proposal_round" "$max_proposal_rounds" cat data/pdr-$ISSUE/proposal_final.md printf '\n\n---\n\n*This proposal was refined through %s round(s) of adversarial review using the `plan-do-review` skill.*\n' "$proposal_round" printf '\n---\n\n*Skill: `/plan-do-review` | Harness: %s | Model: %s*\n' "$HARNESS" "$MODEL" } > "$tmpfile" gh issue comment $ISSUE --body-file "$tmpfile" rm -f "$tmpfile" </code></pre> <p><strong>Move the issue to <code>In progress</code></strong> — proposal converged, execution begins:</p> <pre><code class="language-bash">bash .github/skills/shared/scripts/move-issue-status.sh "$ISSUE" "In progress" </code></pre> <hr> <h2>Step 3: Execute the Proposal</h2> <p>Implement the converged proposal in full. This is the core implementation phase — you (the orchestrator) do the actual coding work.</p> <p><strong>Apply the Guiding Principles above.</strong> Implement the proposal as a durable building block, not a short-term patch. If carrying out the proposal reveals that a larger refactor — changing public API signatures, restructuring types, introducing Arc/Cow/lifetimes, redesigning enums, splitting modules, or touching several crates — would produce a significantly more readable, sustainable, elegant, or safe result, do the refactor rather than working around it. File any intentionally-deferred refactors as follow-up issues so they aren't lost; don't paper them over with inline workarounds.</p> <h3>3a: Create an isolated worktree for implementation</h3> <p>All code edits in Step 3 happen in a git worktree rooted on a dedicated branch, NOT in the caller's main checkout. Rationale: this skill may be invoked from a running <code>/monitor-loop</code>, a CI-driving script, or another long-lived session that owns the main checkout. Dirtying that checkout mid-implementation blocks its git operations (pull, status checks, other deploys) until we commit and push. A worktree costs ~30 s to create and makes the isolation guarantee explicit.</p> <pre><code class="language-bash"># Create a dedicated worktree and branch off origin/main WORKTREE_BRANCH="plan-do-review/issue-$ISSUE" WORKTREE_PATH=".claude/worktrees/plan-do-review-$ISSUE" git fetch origin main git worktree add -B "$WORKTREE_BRANCH" "$WORKTREE_PATH" origin/main cd "$WORKTREE_PATH" </code></pre> <p>For the remainder of Step 3 (Plan, Implement, Verify, Commit), all <code>cargo</code>, <code>git</code>, and editor operations happen inside <code>$WORKTREE_PATH</code>. Any tool or subagent you invoke for implementation work must be pointed at this directory (via <code>cwd</code>, the agent <code>Plan</code> tool's worktree option, or an explicit <code>cd</code>).</p> <p>If the worktree or branch already exists from a prior failed run (<code>$ISSUE</code> has been worked before), inspect it first rather than clobbering — <code>git worktree list</code> + <code>git log -3 "$WORKTREE_BRANCH"</code> — and decide whether to resume (checkout + rebase onto current origin/main) or discard (<code>git worktree remove --force</code> then recreate). Do not silently overwrite uncommitted work.</p> <h3>3b: Plan the Implementation</h3> <p>Break the proposal into concrete implementation steps. Use SQL todos for tracking:</p> <pre><code class="language-sql">INSERT INTO todos (id, title, description, status) VALUES ('step-1', '...', '...', 'pending'), ('step-2', '...', '...', 'pending'); INSERT INTO todo_deps (todo_id, depends_on) VALUES ('step-2', 'step-1'); </code></pre> <h3>3c: Implement</h3> <p>For each step (inside <code>$WORKTREE_PATH</code>):</p> <ol> <li>Update the todo to <code>in_progress</code></li> <li>Make the code changes</li> <li>Run <code>cargo check --all</code> after each logical change</li> <li>Run focused tests: <code>cargo test -p <crate></code></li> <li>Update the todo to <code>done</code></li> </ol> <h3>3d: Verify</h3> <p>After all steps are complete (inside <code>$WORKTREE_PATH</code>):</p> <ol> <li><code>cargo test --all</code> — full test suite passes</li> <li><code>cargo clippy --all</code> — no warnings</li> <li><code>cargo fmt --all -- --check</code> — formatting clean</li> </ol> <p>Fix any issues before proceeding.</p> <h3>3e: Commit on the worktree branch</h3> <pre><code class="language-bash"># Still inside $WORKTREE_PATH git add -A git commit -m "<short imperative description> <longer description of what was implemented> Closes #$ISSUE Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>" </code></pre> <p>This commit lives on <code>$WORKTREE_BRANCH</code>, NOT on main. We land it on main in the next sub-step.</p> <h3>3f: Land the change on main and clean up</h3> <p>The commit needs to reach <code>origin/main</code> for the caller (e.g. the monitoring loop) to pick it up on its next redeploy. From the main checkout (NOT the worktree), fast-forward or rebase main onto the worktree branch and push:</p> <pre><code class="language-bash"># Return to the main checkout root — wherever the caller invoked us cd "$MAIN_CHECKOUT" # typically the repo root, not $WORKTREE_PATH git fetch origin main git checkout main git pull --rebase # Fast-forward main to the worktree branch. # If this fails, rebase the worktree branch onto main and retry. if ! git merge --ff-only "$WORKTREE_BRANCH"; then git -C "$WORKTREE_PATH" rebase main git merge --ff-only "$WORKTREE_BRANCH" fi git push </code></pre> <p>If the push is rejected (upstream moved between <code>pull --rebase</code> and <code>push</code>), <code>git pull --rebase && git push</code> and retry once.</p> <p>After the push succeeds, clean up the worktree and its branch:</p> <pre><code class="language-bash">git worktree remove "$WORKTREE_PATH" git branch -d "$WORKTREE_BRANCH" </code></pre> <p><strong>Move the issue to <code>In review</code></strong> — code has landed on <code>main</code> and the review-fix loop is about to start:</p> <pre><code class="language-bash">bash .github/skills/shared/scripts/move-issue-status.sh "$ISSUE" "In review" </code></pre> <p>If the push fails repeatedly (branch protection blocks direct pushes, for example), open a PR from <code>$WORKTREE_BRANCH</code> with <code>gh pr create --fill</code> and let the PR-level review gate handle the landing. The worktree stays until the PR merges; do NOT <code>git worktree remove</code> it in that case. <strong>Do NOT move the issue to <code>In review</code> in this branch</strong> — the column should advance only when the change has actually reached <code>main</code>. Leave the issue in <code>In progress</code> until the PR merges, then move it manually or rerun the skill.</p> <p>If any verification step (3c–3e) failed and you could not converge on a green state, do NOT clean up — leave the worktree in place so the caller or a follow-up invocation can inspect the partial work.</p> <hr> <h2>Step 4: Review-Fix Loop</h2> <p>Repeat until <code>VERDICT: SOUND</code> or <code>review_round >= max_review_rounds</code>:</p> <h3>4a: Spawn Review-Fix Agent</h3> <p>Increment <code>review_round</code>.</p> <p>Get the commit hash:</p> <pre><code class="language-bash">git log -1 --format='%H' </code></pre> <p>Launch a background agent using the Task tool:</p> <ul> <li><strong>agent_type</strong>: <code>"general-purpose"</code></li> <li><strong>model</strong>: <code>$MODEL</code></li> <li><strong>name</strong>: <code>"review-fix-round-{review_round}"</code></li> <li><strong>description</strong>: <code>"Review-fix round {review_round}"</code></li> </ul> <p>The review-fix agent prompt must include the full review-fix skill protocol. Read the review-fix skill template to assemble the prompt:</p> <pre><code class="language-bash">cat .github/skills/review-fix/SKILL.md </code></pre> <p>Substitute <code>$COMMIT</code> with the actual commit hash and set <code>$MODE = review</code>.</p> <p>Prepend context about what was implemented:</p> <pre><code>You are reviewing commit {commit_hash} in the current repository. This commit implements the proposal from GitHub issue #{issue_number}. {brief summary of what was implemented} Follow the review-fix skill instructions below exactly. Mode is review (do NOT make changes). Produce the full structured report. {contents of .github/skills/review-fix/SKILL.md} </code></pre> <h3>4b: Process Review Result</h3> <p>Read the agent result. Extract the verdict from the Fix Analysis section.</p> <blockquote> <p><strong>Context hygiene (same as 2b).</strong> After posting the full review to the issue, keep only the verdict and the specific issue list in your working state. The full report is preserved in the issue comment.</p> </blockquote> <p><strong>Post the review result to the issue</strong>:</p> <pre><code class="language-bash"># Use --body-file (see the CRITICAL note in Step 2a). tmpfile=$(mktemp) { printf '## 🔬 Review-Fix Report (Round %s/%s)\n\n' "$review_round" "$max_review_rounds" printf '<details>\n<summary>Full review report (click to expand)</summary>\n\n' cat data/pdr-$ISSUE/review_r$review_round.md printf '\n\n</details>\n\n' printf '**Verdict: %s**\n\n' "$verdict" # If not SOUND, append the key issues outside the <details> block. printf '---\n\n*Created by `/plan-do-review` skill → `review-fix-round-%s` sub-agent (%s, model: %s)*\n' "$review_round" "$HARNESS" "$MODEL" } > "$tmpfile" gh issue comment $ISSUE --body-file "$tmpfile" rm -f "$tmpfile" </code></pre> <p><strong>If <code>SOUND</code></strong>:</p> <ul> <li>The implementation is clean. Proceed to Step 5.</li> </ul> <p><strong>If <code>CONCERNS</code>, <code>INCOMPLETE</code>, or <code>WRONG</code></strong>:</p> <ul> <li>Extract every specific issue from the review report:<ul> <li>Missing per-op checks</li> <li>Untested code paths</li> <li>Similar issues found</li> <li>Architectural gaps</li> <li>Parity issues</li> <li>Fundamental design issues</li> </ul> </li> <li><strong>Address every single issue.</strong> Do not skip, defer, or dismiss any feedback. Read the relevant code to understand each issue, then fix it. If a reviewer raised it, it gets fixed — period.</li> <li>For each issue:<ol> <li>Read the relevant code to understand the problem</li> <li>Make the code changes to fully resolve it — <strong>inside the same <code>$WORKTREE_PATH</code> worktree used in Step 3</strong>. If the worktree was already cleaned up (3f completed), recreate it with <code>git worktree add -B "$WORKTREE_BRANCH" "$WORKTREE_PATH" main</code> and rebase onto main before editing.</li> <li>Add or update tests to cover the fix</li> <li>Run <code>cargo test --all</code> and <code>cargo clippy --all</code> inside the worktree</li> </ol> </li> <li>Once all issues are resolved, commit on the worktree branch, land on main via the Step 3f "fast-forward + push" recipe (re-clean the worktree after landing), and loop back to 4a.</li> <li>If <code>WRONG</code>, consider whether a revert and re-implementation is cleaner than incremental fixes. Either way, all feedback must be addressed before the next review round.</li> </ul> <p><strong>If verdict unclear</strong> (agent error):</p> <ul> <li>Treat as <code>CONCERNS</code> and extract all actionable items from the agent output. Address every item — do not skip any.</li> </ul> <hr> <h2>Step 5: Completion</h2> <p><strong>5a. Validate, post, then close.</strong> The completion comment must be posted <em>before</em> moving the issue to <code>Done</code> and unassigning. This way, if posting fails the issue stays in <code>In review</code> and the assignee lock is preserved.</p> <pre><code class="language-bash"># Fail-closed: refuse to close with empty critical sections. # $what_was_deferred is allowed to be empty (renders as "_(none)_"). for var_name in commit_list brief_summary what_was_done; do eval "val=\$$var_name" if [ -z "$val" ]; then echo "ERROR: Step 5 blocked — \$$var_name is empty." >&2 # Do NOT move to Done or unassign — leave issue In review exit 1 fi done </code></pre> <pre><code class="language-bash"># Use --body-file (see the CRITICAL note in Step 2a). tmpfile=$(mktemp) # Compose the completion comment by writing each section in turn. Any # variable-length sections (commit list, What-was-done, etc.) should be # either printed inline with printf or cat'd from a pre-written file — # never embedded as `$(cat ...)` inside a quoted heredoc. { printf '## Implementation Complete\n\n' printf 'Implemented in commit(s):\n%s\n\n' "$commit_list" # or cat a prebuilt file printf '### Summary\n%s\n\n' "$brief_summary" printf '### Review Status\nPassed review-fix in %s round(s).\nFinal verdict: **SOUND**\n\n' "$review_round" printf '### What was done\n%s\n\n' "$what_was_done" printf '### What was deferred (if any)\n%s\n\n' "${what_was_deferred:-_(none)_}" printf -- '---\n\n*Implemented and reviewed using the `plan-do-review` skill.*\n' printf '\n---\n\n*Skill: `/plan-do-review` | Harness: %s | Model: %s*\n' "$HARNESS" "$MODEL" } > "$tmpfile" gh issue comment $ISSUE --body-file "$tmpfile" rm -f "$tmpfile" </code></pre> <p>Only after the comment is successfully posted, move the issue and unassign:</p> <pre><code class="language-bash">bash .github/skills/shared/scripts/move-issue-status.sh "$ISSUE" Done gh issue edit $ISSUE --remove-assignee @me </code></pre> <h3>5b: File Issues for Deferred Work</h3> <p>If the completion summary includes deferred items (from the "What was deferred" section, reviewer recommendations, or remaining concerns noted during implementation), create a GitHub issue for <strong>each</strong> deferred item.</p> <p><strong>Classify each deferred item</strong> to determine its priority label:</p> <table> <thead> <tr> <th>Category</th> <th>Priority label</th> <th>Examples</th> </tr> </thead> <tbody><tr> <td>Correctness bugs</td> <td><code>urgent</code></td> <td>Wrong result, data corruption, logic error, missing validation that causes incorrect behavior</td> </tr> <tr> <td>Security issues</td> <td><code>high</code></td> <td>Unauthenticated access, input injection, credential exposure, DoS vectors</td> </tr> <tr> <td>Performance issues</td> <td><code>medium</code></td> <td>Unnecessary allocations, O(n²) where O(n) suffices, missing caching, redundant I/O</td> </tr> <tr> <td>Everything else</td> <td><code>low</code></td> <td>Refactors, testing gaps, documentation, code cleanup, naming improvements</td> </tr> </tbody></table> <p>Apply the highest applicable category — e.g., a performance issue that also causes incorrect results is <code>urgent</code> (correctness), not <code>medium</code>.</p> <pre><code class="language-bash">gh issue create \ --title "<short imperative title>" \ --body "Follow-up from #$ISSUE (<original issue title>). ## Context <why this was deferred — e.g., out of scope, needs design, blocked on X> ## What needs to happen <concrete description of the work> ## Dependencies <list any issues that must be completed before this one can start, or state "None"> - Blocked by #NNN — <short reason why this depends on that issue> ## References - Parent issue: #$ISSUE - Implementation commit(s): <commit hashes>" \ --label "follow-up" \ --label "<priority>" </code></pre> <p>where <code><priority></code> is one of <code>urgent</code>, <code>high</code>, <code>medium</code>, or <code>low</code> based on the classification above.</p> <p>Guidelines for deferred-work issues:</p> <ul> <li>One issue per distinct work item — do not bundle unrelated items.</li> <li>Title should be actionable and imperative (e.g., "Add integration tests for RPC semaphore rejection", not "Testing gaps").</li> <li>Include enough context that someone unfamiliar with the parent issue can understand and execute the work.</li> <li>Reference the parent issue and implementation commits.</li> <li>Add relevant labels beyond <code>follow-up</code> and the priority label (e.g., <code>testing</code>, <code>refactor</code>, crate-specific labels).</li> <li>If a deferred item is trivial or speculative, skip it — only file issues for work that genuinely should be done.</li> <li><strong>State dependencies explicitly.</strong> If a deferred issue depends on another issue (including other deferred issues being filed in the same batch), say so in the Dependencies section with "Blocked by #NNN" and a short reason. If multiple deferred items form a sequence (e.g., Phase 1 → Phase 2 → Phase 3), each later phase must list the earlier one as a blocker. An issue with no prerequisites should say "None". This is critical for the dependency check in this skill to work correctly.</li> </ul> <p>Update the completion comment's "What was deferred" section to include the newly created issue links (edit the comment or post a follow-up).</p> <h3>5c: Clean Up Per-Issue Build Artifacts</h3> <p>Each invocation of this skill accumulates a per-issue <code>CARGO_TARGET_DIR</code> at <code>~/data/pdr-$ISSUE/</code> (25–50 GB per dir for a full henyey workspace build), plus optionally <code>~/data/pdr-$ISSUE-target/</code> when the caller used that alternate naming. Once the fix has landed on <code>main</code> and the issue is closed, these caches are stale — the next relevant build comes from the monitor-loop rebuilding <code>main</code>, not from this target. Leaving them behind is the dominant disk-pressure driver on the shared <code>~/data/</code> volume (observed 68 such dirs totalling ~500 GB on 2026-04-22).</p> <p>Remove the per-issue build targets:</p> <pre><code class="language-bash">N="$ISSUE" rm -rf "$HOME/data/pdr-$N" 2>/dev/null || true rm -rf "$HOME/data/pdr-$N-target" 2>/dev/null || true </code></pre> <p><strong>Do not run this cleanup before Step 5.</strong> The target dir is still needed across Step 3 (implementation), Step 4 (review-fix re-compiles), and any review-round rebuilds that happen if the worktree was recreated in 4b. Only remove the target once the completion comment has been posted and the skill is truly done.</p> <p><strong>Do not clean up if verification failed and the worktree was left in place</strong> (see Step 3f's failure clause). In that case the caller or a follow-up invocation needs the cached build to resume, so preserve the target dir along with the worktree. Check: if <code>.claude/worktrees/plan-do-review-$ISSUE/</code> still exists, skip the <code>rm -rf</code> above.</p> <p>Print a summary to the terminal:</p> <pre><code>═══ Plan-Do-Review Complete ═══ Issue: #$ISSUE Proposal rounds: {proposal_round} / {max_proposal_rounds} Review rounds: {review_round} / {max_review_rounds} Commits: {count} Deferred issues: {count} filed Final verdict: SOUND ═════════════════════════════════ </code></pre> <hr> <h2>Guidelines</h2> <ul> <li><strong>The orchestrator implements; agents review.</strong> You write code and make changes. Sub-agents only analyze and critique. This separation ensures reviews are independent.</li> <li><strong>Post everything to the issue — no exceptions.</strong> The GitHub issue is the complete audit trail. Skipping any comment is a skill violation. Every step below MUST appear as an issue comment:<ul> <li>Each proposal draft (before critic review)</li> <li><strong>Each critic response (with verdict)</strong> — this is the most commonly skipped step. Post it immediately after the critic agent returns, before processing the verdict or rewriting the proposal.</li> <li>The converged proposal (delimiter between planning and execution)</li> <li>Each review-fix report (with verdict)</li> <li>The completion summary (with commits, deferred work, and issue links)</li> </ul> </li> <li><strong>Respect max rounds.</strong> If proposal convergence or review-fix hits the max, post whatever you have and note the remaining concerns. Do not loop forever.</li> <li><strong>Be honest about feedback.</strong> If a critic's feedback is wrong, explain why in the rewrite. If it is right, fix it. Do not ignore valid feedback.</li> <li><strong>Address all review-fix feedback.</strong> Every issue raised in a review-fix round must be fully resolved before the next round. No feedback may be skipped, deferred, or dismissed. If the reviewer raised it, fix it.</li> <li><strong>Use the codebase, not assumptions.</strong> Before rewriting a proposal or implementing code, read the actual files. Do not rely on memory or the issue description alone.</li> <li><strong>One commit per review round.</strong> Each review-fix iteration should produce one commit addressing all feedback from that round.</li> <li><strong>Parity is non-negotiable.</strong> For any change touching protocol, consensus, or ledger logic, verify against stellar-core.</li> <li><strong>Big swings are expected.</strong> Large refactors, public API modifications, struct redesigns, and design-level changes are not only permitted — they are preferred when the result is cleaner, more idiomatic Rust, and more scalable. Do not artificially constrain scope to minimal patches. Examples of encouraged changes:<ul> <li>Changing function signatures to accept <code>&T</code> or <code>Arc<T></code> instead of requiring callers to clone</li> <li>Restructuring types (adding Cow, newtype wrappers, splitting enums)</li> <li>Moving fields between structs to improve ownership semantics</li> <li>Cross-crate API changes when a pattern spans module boundaries</li> <li>Replacing C-style output parameters with return values</li> <li>Introducing trait bounds or generics to eliminate repetition The bar is: does the code end up clearer, cleaner, and more maintainable? If yes, the refactor is worth it regardless of diff size.</li> </ul> </li> </ul> </article> </div> <!-- Right: Metadata & Command Sidebar --> <div class="w-full lg:w-80 shrink-0 flex flex-col gap-6" data-astro-cid-7zzsworf> <!-- Install Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-4 shadow-sm" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-mono" data-astro-cid-7zzsworf>Install via CLI</span> <div class="flex flex-col gap-2" data-astro-cid-7zzsworf> <div id="detail-install-cmd" class="font-mono text-[11px] p-3 rounded-lg bg-black/40 border border-border select-all break-all text-primary font-bold leading-relaxed" data-astro-cid-7zzsworf> npx skills add https://github.com/stellar-experimental/henyey --skill plan-do-review </div> <button id="detail-copy-btn" class="w-full py-2.5 rounded-lg bg-primary hover:bg-primary-hover text-on-primary font-sans font-bold text-sm shadow transition-all active:scale-95 flex items-center justify-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px]" data-astro-cid-7zzsworf>content_copy</span> <span data-astro-cid-7zzsworf>Copy Command</span> </button> </div> </div> <!-- Details & Stats Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-4 shadow-sm text-on-surface" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-sans" data-astro-cid-7zzsworf>Repository Details</span> <div class="flex flex-col gap-3.5" data-astro-cid-7zzsworf> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>star</span> Stars </span> <span class="font-mono font-bold text-on-surface" data-astro-cid-7zzsworf>5</span> </div> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>call_split</span> Forks </span> <span class="font-mono font-bold text-on-surface" data-astro-cid-7zzsworf>1</span> </div> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>navigation</span> Branch </span> <span class="font-mono bg-surface border border-border px-2 py-0.5 rounded text-[11px] text-on-surface-variant" data-astro-cid-7zzsworf>main</span> </div> <div class="flex justify-between items-start text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5 mt-0.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>article</span> Path </span> <span class="font-mono bg-surface border border-border px-2 py-0.5 rounded text-[11px] text-on-surface-variant truncate max-w-[150px]" title="SKILL.md" data-astro-cid-7zzsworf>SKILL.md</span> </div> </div> </div> <!-- Occupations Tag Card --> <!-- Related Creators Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-3 shadow-sm" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-sans" data-astro-cid-7zzsworf>More from Creator</span> <div class="flex items-center gap-2" data-astro-cid-7zzsworf> <img class="w-8 h-8 rounded-full border border-border" src="https://avatars.githubusercontent.com/u/267119280?v=4" alt="stellar-experimental" onerror="this.src='https://avatars.githubusercontent.com/u/9919?v=4'" data-astro-cid-7zzsworf> <div class="flex flex-col min-w-0" data-astro-cid-7zzsworf> <span class="font-bold text-sm truncate text-on-surface" data-astro-cid-7zzsworf>stellar-experimental</span> <a href="/?creator=stellar-experimental" class="text-xs text-primary hover:underline font-semibold transition-all" data-astro-cid-7zzsworf>Explore all skills →</a> </div> </div> </div> </div> </div> </div> </div> <script> const copyBtn = document.getElementById("detail-copy-btn"); const installCmd = document.getElementById("detail-install-cmd"); if (copyBtn && installCmd) { copyBtn.addEventListener("click", () => { const cmd = installCmd.textContent.trim(); navigator.clipboard.writeText(cmd).then(() => { const originalText = copyBtn.innerHTML; copyBtn.innerHTML = ` <span class="material-symbols-outlined text-[16px]">check</span> <span>Copied!</span> `; copyBtn.style.background = "#10b981"; copyBtn.style.borderColor = "#10b981"; setTimeout(() => { copyBtn.innerHTML = originalText; copyBtn.style.background = ""; copyBtn.style.borderColor = ""; }, 1500); }); }); } </script> </div> <!-- Footer --> <footer class="border-t border-border bg-surface-container-low text-on-surface-variant py-8 px-gutter mt-16 rounded-xl"> <div class="max-w-container-max mx-auto flex flex-col md:flex-row justify-between items-center gap-6"> <div class="flex items-center gap-2"> <div class="w-6 h-6 rounded bg-primary bg-opacity-20 flex items-center justify-center"> <span class="material-symbols-outlined text-primary text-sm">code_blocks</span> </div> <span class="font-bold text-on-surface text-sm">SkillMD</span> </div> <div class="flex flex-wrap justify-center gap-6 text-sm"> <a href="/about" class="hover:text-primary transition-colors">About Us</a> <a href="/contact" class="hover:text-primary transition-colors">Contact Us</a> <a href="/privacy" class="hover:text-primary transition-colors">Privacy Policy</a> <a href="/terms" class="hover:text-primary transition-colors">Terms of Service</a> <a href="/support" class="hover:text-primary transition-colors">Support</a> </div> <div class="text-xs text-on-surface-variant/80"> © 2026 SkillMD. All rights reserved. </div> </div> </footer> </main> <!-- Script for Theme Toggle, Mobile Menu, and Sidebar Filter Redirection --> <script> // Theme setup const savedTheme = localStorage.getItem("theme") || "dark"; function applyTheme(theme) { document.documentElement.classList.remove("dark", "green", "dracula", "nord"); if (theme === "dark") { document.documentElement.classList.add("dark"); } else if (theme === "green") { document.documentElement.classList.add("dark", "green"); } else if (theme === "dracula") { document.documentElement.classList.add("dark", "dracula"); } else if (theme === "nord") { document.documentElement.classList.add("dark", "nord"); } document.documentElement.setAttribute("data-theme", theme); const themeMoon = document.getElementById("theme-moon"); const themeSun = document.getElementById("theme-sun"); const themeLeaf = document.getElementById("theme-leaf"); const themeDracula = document.getElementById("theme-dracula"); const themeNord = document.getElementById("theme-nord"); if (themeMoon && themeSun && themeLeaf && themeDracula && themeNord) { themeMoon.style.display = theme === "dark" ? "inline" : "none"; themeSun.style.display = theme === "light" ? "inline" : "none"; themeLeaf.style.display = theme === "green" ? "inline" : "none"; themeDracula.style.display = theme === "dracula" ? "inline" : "none"; themeNord.style.display = theme === "nord" ? "inline" : "none"; } } applyTheme(savedTheme); const themeToggleBtn = document.getElementById("theme-toggle-btn"); if (themeToggleBtn) { themeToggleBtn.addEventListener("click", () => { const currentTheme = document.documentElement.getAttribute("data-theme") || "dark"; let newTheme = "dark"; if (currentTheme === "dark") { newTheme = "light"; } else if (currentTheme === "light") { newTheme = "green"; } else if (currentTheme === "green") { newTheme = "dracula"; } else if (currentTheme === "dracula") { newTheme = "nord"; } else { newTheme = "dark"; } applyTheme(newTheme); localStorage.setItem("theme", newTheme); }); } // Mobile menu toggle and sidebar logic const mobileMenuToggle = document.getElementById("mobile-menu-toggle"); const sidebarMenu = document.getElementById("sidebar-menu"); const sidebarOverlay = document.getElementById("sidebar-overlay"); function isMobile() { return window.innerWidth < 768; // 768px is the 'md' breakpoint in Tailwind } function openSidebar() { if (sidebarMenu) { sidebarMenu.classList.remove("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.remove("hidden"); } } function closeSidebar() { if (sidebarMenu && isMobile()) { sidebarMenu.classList.add("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } if (mobileMenuToggle && sidebarMenu) { mobileMenuToggle.addEventListener("click", (e) => { e.stopPropagation(); if (isMobile()) { const isClosed = sidebarMenu.classList.contains("-translate-x-full"); if (isClosed) { openSidebar(); } else { closeSidebar(); } } }); document.addEventListener("click", (e) => { if (isMobile()) { if (!sidebarMenu.contains(e.target) && !mobileMenuToggle.contains(e.target)) { closeSidebar(); } } }); if (sidebarOverlay) { sidebarOverlay.addEventListener("click", () => { if (isMobile()) { closeSidebar(); } }); } // Collapse sidebar when clicking a filter button, creator button, or nav item inside it sidebarMenu.addEventListener("click", (e) => { if (isMobile()) { const clickTarget = e.target.closest("button, a"); if (clickTarget) { closeSidebar(); } } }); // Sync sidebar state on window resize window.addEventListener("resize", () => { if (!isMobile()) { // Desktop: sidebar should be visible, no overlay if (sidebarMenu) { sidebarMenu.classList.remove("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } else { // Mobile: start collapsed if (sidebarMenu) { sidebarMenu.classList.add("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } }); } // If not on homepage, redirect on sidebar filter click const isHomepage = window.location.pathname === "/"; document.querySelectorAll("#occupation-filters .filter-btn").forEach(btn => { btn.addEventListener("click", (e) => { const occ = e.currentTarget.getAttribute("data-occupation"); if (!isHomepage) { window.location.href = occ ? `/?occupation=${encodeURIComponent(occ)}` : "/"; } }); }); document.querySelectorAll("#creator-filters .creator-btn").forEach(btn => { btn.addEventListener("click", (e) => { const creator = e.currentTarget.getAttribute("data-creator"); if (!isHomepage) { window.location.href = `/?creator=${encodeURIComponent(creator)}`; } }); }); </script> </body> </html>