--- name: cicd description: > CI/CD lane for culture: branch, commit, push, create PR, wait for automated reviewers, fetch comments, fix or pushback, reply, resolve threads. Delegates `lint` / `open` / `read` / `reply` / `delta` to `agex pr`; keeps two culture-side extensions on top — `status` (SonarCloud gate, OPEN issues, hotspots, unresolved-thread tally) and `await` (`read --wait` + `status`, exits non-zero on Sonar ERROR or unresolved threads). Renamed from `pr-review` in culture 8.8.1; rebased on `agex pr` (from steward 0.12.0) in culture 11.1.0. Use when: creating PRs, handling review feedback, or the user says "create PR", "review comments", "address feedback", "resolve threads", or "use pr-review" / "use cicd". --- # CI/CD Lane (formerly pr-review) Complete pull request lifecycle for the culture project. Follow every step in order. The skill was renamed from `pr-review` to `cicd` in culture 8.8.1 and rebased on `agex pr` in culture 11.1.0; existing prompts that say "use pr-review" still resolve here through the trigger phrases above. `agex pr` (in `agentculture/agex-cli`) is the upstream for the five core PR-lifecycle verbs — `lint`, `open`, `read`, `reply`, `delta`. Culture used to vendor parallel scripts for each (`create-pr-and-wait.sh`, `pr-batch.sh`, `pr-comments.sh`, `wait-and-check.sh`, `poll-readiness.sh`); in 11.1.0 those were dropped in favor of delegating to `agex`. What's left in this skill is the culture-specific gating layer: - `status` — SonarCloud quality gate, OPEN issues, hotspots, deploy preview URL, unresolved-inline-thread tally. - `await` — composes `agex pr read --wait` with `status` and gates on Sonar `ERROR` / unresolved threads. The single command to run after pushing a fix when you want "wake me when this PR is triage-able." Both extensions are filed upstream ([agex-cli#41](https://github.com/agentculture/agex-cli/issues/41)); when they land they migrate out of this skill. ## Prerequisites Hard requirements: `agex` (already a culture runtime dep — `agex-cli>=0.13` in `pyproject.toml`), `gh` (GitHub CLI), `jq`, `bash`, `python3` (stdlib only), `curl` (used by `pr-status.sh`). If `agex` is not on PATH (e.g. running outside a culture venv): ```bash uv tool install agex-cli # or: pip install --user agex-cli ``` ## Step 1 — Branch If you are on `main`, create a feature branch first: ```bash git checkout -b ``` Branch naming conventions: | Type | Pattern | Example | |------|---------|---------| | Bug fix | `fix/` | `fix/server-not-running-crash` | | Feature | `feat/` | `feat/webhook-alerts` | | Docs | `docs/` | `docs/protocol-extensions` | | Skill | `skill/` | `skill/cicd-and-communicate-resync` | ## Step 1b — Check for existing PRs on the branch Before adding new work to an existing branch, check if there's already an open PR: ```bash gh pr view --json number,title,state --jq '{number,title,state}' ``` If the command fails with "no pull requests found", there is no open PR — proceed normally. Only act on the result if it returns valid JSON with `state: "OPEN"`. If an open PR exists and your new changes are **unrelated** to that PR's scope, **stop and ask the user**: > "There's an open PR (#N: 'title') on this branch. The new changes > are unrelated to that PR. Would you like to merge the existing PR > first before starting the new work?" Wait for the user's answer before proceeding. If the user says yes, let them merge (never merge yourself). If they say continue, add the changes to the existing PR. If the new changes ARE related to the existing PR, proceed normally — commit and push to the same branch. ## Step 2 — Make changes, commit, push 1. Edit code 2. Run tests via the `run-tests` skill (parallel by default) 3. Bump the version (required before PR — `version-check` CI gate enforces this): ```bash echo '{"fixed":["..."]}' | python3 .claude/skills/version-bump/scripts/bump.py patch ``` 4. Stage and commit: ```bash git add git commit -m "$(cat <<'EOF' Commit message here. Co-Authored-By: Claude Opus 4.7 (1M context) EOF )" ``` 5. Push the branch (no `--push` flag on `workflow.sh open` anymore — see the [Migration note](#migration-from-create-pr-and-waitsh) at the end): ```bash git push -u origin ``` ## Step 3 — Create PR (and wait for an initial briefing in one shot) Use `workflow.sh open` — it forwards to `agex pr open --delayed-read`, which creates the PR then polls 180s for an initial briefing (CI checks, SonarCloud gate + new issues, all comments, next-step footer). Automated reviewers (qodo, copilot, sonarcloud) need that window to post; checking sooner returns zero comments. ```bash bash .claude/skills/cicd/scripts/workflow.sh open \ --title "Short title" \ --body-file /tmp/pr-body.md ``` Or pipe the body via heredoc on stdin: ```bash bash .claude/skills/cicd/scripts/workflow.sh open --title "Short title" <<'EOF' ## Summary - Bullet points describing changes ## Test plan - [ ] Test items EOF ``` `agex pr open` writes the body via `--body-file` to a tempfile under the hood, so large self-contained briefs don't hit the OS argv length limit. The auto-signature `- (Claude)` is appended by agex from the repo-root `culture.yaml` (first agent's `suffix`) — don't sign manually in the body. **If you must do it by hand** (PR was opened earlier and you only need to fetch feedback now): ```bash bash .claude/skills/cicd/scripts/workflow.sh read ``` Do **not** check for comments before the 3-minute mark on a brand-new PR. Empty comment lists in the first 1–2 minutes don't mean reviewers are done — they mean reviewers haven't started yet. ## Step 4 — Wait another window if needed If the initial briefing came back empty or thin, or you suspect a slow reviewer (or you just pushed a follow-up commit and want a fresh review pass), use `workflow.sh read --wait` for a deliberate poll: ```bash bash .claude/skills/cicd/scripts/workflow.sh read --wait 180 ``` This polls `agex pr read` up to 180 seconds, exiting when required reviewers have posted (or the cap hits). It is **not** open-ended polling — it's "give the reviewers one more deliberate window before deciding they're done." For the SonarCloud-gated variant that exits non-zero on unresolved threads, use `workflow.sh await ` instead. If two windows are also empty, fall back to one-shot reads spaced a minute apart: ```bash bash .claude/skills/cicd/scripts/workflow.sh read # if still empty: sleep 60 bash .claude/skills/cicd/scripts/workflow.sh read ``` Three consecutive reads returning zero comments means reviewers are done / not configured; proceed to triage (or skip directly to merge if there's truly nothing to address). ### Long waits — background polling `agex pr read --wait N` polls in-session for up to N seconds. The Anthropic prompt cache has a 5-minute TTL; sleeping past it burns context every cache miss. Two ways to drive the wait: - **Synchronous** — `workflow.sh await ` after `workflow.sh open`. Fine when readiness is expected within ~5 minutes. - **Asynchronous** — for longer waits, run `agex pr read --wait NNN` inside a background subagent (Agent tool, `run_in_background: true`) so the main session only pays the cache cost when readiness fires. The subagent's only job is to invoke `agex pr read --wait` and echo its headline back. The parent triages with `workflow.sh await` when the notification arrives. ## Step 6 — Triage each comment For every review comment, decide **FIX** or **PUSHBACK** with reasoning. Default to **FIX** for: portability complaints (always valid for culture — recurring bug class), test or doc requests, style nits aligned with project conventions, exception-type mismatches (e.g. breaking reconnect loops), Sonar findings. Default to **PUSHBACK** for: architecture opinions that conflict with the project `CLAUDE.md` (notably the all-backends rule and the citation pattern), greenfield false-positives. ### Alignment-delta rule If the PR touches `CLAUDE.md`, `culture.yaml`, or anything under `.claude/skills/`, run `workflow.sh delta` **before** declaring FIX or PUSHBACK on each comment. Note any sibling that needs a follow-up PR and mention it in your reply. ## Step 7 — Fix code and push 1. Make all code fixes 2. Run tests via the `run-tests` skill 3. Commit with a descriptive message 4. Push: `git push` ## Step 8 — Reply and resolve threads `workflow.sh reply` forwards to `agex pr reply ` which reads JSONL from stdin, posts the replies, and resolves the threads in one call. agex auto-appends `- (Claude)` (resolved from `culture.yaml`'s first agent `suffix`, falling back to repo basename) when the reply body isn't already signed — don't sign manually. Batch reply to all comments at once: ```bash bash .claude/skills/cicd/scripts/workflow.sh reply <<'EOF' {"comment_id": 123, "body": "Fixed -- changed X to Y."} {"comment_id": 456, "body": "Intentional -- this follows the pattern in Z because..."} EOF ``` For a one-off reply that doesn't merit JSONL, the vendored `pr-reply.sh` (with its `_resolve-nick.sh` dependency) is still shipped and runs outside `workflow.sh`: ```bash bash .claude/skills/cicd/scripts/pr-reply.sh --resolve "Fixed -- updated." ``` **Important:** - Don't add `- Claude` or any other signature to the reply body — agex / `pr-reply.sh` appends `- (Claude)` automatically. - `workflow.sh reply` resolves threads by default; `pr-reply.sh` needs `--resolve`. - Every comment must get a reply — no silent fixes. ## Step 9 — Check SonarCloud before declaring ready After CI is green and all inline threads are resolved, run the culture gate: ```bash bash .claude/skills/cicd/scripts/workflow.sh status ``` or the composite wait-then-gate: ```bash bash .claude/skills/cicd/scripts/workflow.sh await ``` `status` (powered by `pr-status.sh`) surfaces the SonarCloud quality gate, the OPEN-issue list with `[SEVERITY] [rule] path:line`, hotspots, the deploy-preview URL, and the unresolved-inline-thread tally. `await` polls `agex pr read --wait` first, then runs `status`, and exits non-zero if Sonar reports `ERROR` or any thread is still open. SonarCloud findings do **not** always arrive as inline PR comments — a fully-resolved thread list plus an all-green `gh pr checks` is **not** sufficient evidence that the PR is clean. If new findings show up, loop back to Step 7. For non-standard project keys, set `SONAR_PROJECT_KEY=` before running the script (the default is `_`). ## Step 10 — Wait for merge **Never merge the PR yourself.** The PR is merged manually on the GitHub site. Report completion back to the IRC channel: ```bash # Using the IRC skill CULTURE_NICK="" culture channel message "#general" "PR # — all review threads addressed and resolved. Ready for merge." ``` ## Script reference | Script | Location | Purpose | |--------|----------|---------| | `workflow.sh ` | `.claude/skills/cicd/scripts/` | Single entry point. Subcommands: `lint`, `open`, `read`, `reply`, `delta`, `status`, `await`, `help`. `lint` / `open` / `read` / `reply` / `delta` forward to `agex pr `; `status` / `await` are culture extensions. | | `pr-status.sh ` | `.claude/skills/cicd/scripts/` | One-shot status overview: PR state, CI checks, SonarCloud quality gate + issue count, inline-thread resolved tally. Backs `workflow.sh status` and the post-wait gate in `workflow.sh await`. | | `pr-reply.sh [--resolve] "body"` | `.claude/skills/cicd/scripts/` | One-off single-comment reply. Auto-signs as `- (Claude)` via `_resolve-nick.sh`. Use when JSONL is overkill. | | `_resolve-nick.sh` | `.claude/skills/cicd/scripts/` | Helper used by `pr-reply.sh`. Resolves the agent's nick from `/culture.yaml`'s first agent `suffix`, falling back to the repo basename. | | `portability-lint.sh [--all]` | `.claude/skills/cicd/scripts/` | Catch absolute `/home//` paths and per-user dotfile references in committed docs/configs. Default mode lints the current diff (staged + unstaged); `--all` lints every tracked file. Run via `workflow.sh lint` (which forwards to `agex pr lint --exit-on-violation`). | All scripts auto-detect `owner/repo` from the current git remote. The full script set is vendored from **guildmaster** (the AgentCulture skills hub, post steward→guildmaster cutover; lineage: steward 0.12.0) — re-cite from `../guildmaster/.claude/skills/cicd/` if you need updates. Scripts that have intentionally diverged from the upstream copy carry a `# culture-divergence:` header documenting what was changed and why; preserve those when re-citing. ## Conventions `agex pr` emits a **"Next step:"** footer at the end of every command that names the right next verb (the same chain `agex learn cicd` documents) — follow that rather than memorizing an order. `workflow.sh help` mirrors the verb table when you need the culture-flavored extensions (`status`, `await`) on top. ## Migration from `create-pr-and-wait.sh` `create-pr-and-wait.sh --push` is gone in culture 11.1.0. The auto-push behavior (originally added under issue #318) was a culture-divergence on top of the upstream steward script; `agex pr open` has no `--push` equivalent yet. The replacement flow is two steps: ```bash # OLD (culture <= 11.0.x): bash .claude/skills/cicd/scripts/create-pr-and-wait.sh --push \ --title "..." --body-file /tmp/pr-body.md # NEW (culture >= 11.1.0): git push -u origin HEAD bash .claude/skills/cicd/scripts/workflow.sh open \ --title "..." --body-file /tmp/pr-body.md ``` Same body-file ergonomics (large briefs still travel via tempfile under the hood), same `--delayed-read` wait, same auto-signature on PR body — only the push moves out into its own line. Bringing `--push` back is filed upstream ([agex-cli#41](https://github.com/agentculture/agex-cli/issues/41)). ## Quick reference — full flow ```text git checkout -b fix/my-fix # ... make changes ... # run-tests skill: bash .claude/skills/run-tests/... (or pytest direct) echo '{"fixed":["desc"]}' | python3 .claude/skills/version-bump/scripts/bump.py patch git add && git commit -m "message" git push -u origin HEAD bash .claude/skills/cicd/scripts/workflow.sh open \ --title "..." --body-file /tmp/pr-body.md # ... triage, fix issues, commit, push ... bash .claude/skills/cicd/scripts/workflow.sh reply <<< '{"comment_id":N,"body":"Fixed"}' bash .claude/skills/cicd/scripts/workflow.sh await # SonarCloud-gated readiness # Wait for manual merge — never merge yourself ```