General-purpose coding policy for Baruch's AI agents
73
92%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Process steps in order. Do not skip ahead.
Structured workflow for shipping code: PR creation, automated policy review, merge, and cleanup. Do not stop between steps; the skill runs end-to-end from git push through merge + cleanup verification in a single agent session.
main/master)rules/ or skills/ → rules/context-writing-style.md; new scripts → rules/script-delegation.md and rules/testing-standards.md)because, therefore, since, etc. per rules/context-writing-style.mdouter-boundary-process-contract token per rules/error-handling.mdbash -n <script> must exit 0 on shell scriptsgit push -u origin <branch>gh pr create:
<type>(<scope>): <imperative summary>## Summary
<what changed and why — 1-3 bullet points>
## Test plan
- [ ] <verification steps>When this step is wrapped in a reusable script (e.g., release.sh that other devs run unattended), see the script-wrapping gates at:
skills/release/SCRIPTING.mdProceed immediately to Step 3.
Decide the bump per semver, then apply it the way the channels named in Step 7 require:
smart-publish step; update the manifest version only for minor or majoragent-plugin.yaml for an ACR package), and cut Step 7's release tag at that same versionpublish-mode: as-is with .github/workflows/publish-plugin.yml, or mode: as-is when calling .github/actions/smart-publish directly. For another publisher, inspect its supported mechanism before proceeding. Keep the required skill review, registry confirmation and moderation checks enabledOpening the PR, or pushing further commits to an existing PR, automatically triggers the policy reviewer, which reviews the diff against the in-tree rules/*.md and posts a verdict. The machinery depends on the repo:
review-codex.yml workflow (Codex CLI on a ChatGPT subscription), posting as github-actions[bot].coding-policy-fleet-reviewer[bot] GitHub App; the in-repo review-trigger.yml dispatches it on each pull_request event, with a scheduled marker-gated poll as backstop (coding-policy#202).In both, the PR event (opened / synchronize / reopened) is the primary trigger. A plain git push to a non-PR branch does NOT fire a review; the consumer path's scheduled poll is only a backstop for a dispatch that never fired. Fork PRs are skipped in both — no secret or token access to a fork head. Adopt a fork PR via adopt-fork-pr (works in any repo where the policy review is fork-guarded). See the trigger / authorship / dismissal mechanics for both at:
skills/release/REVIEW_DETAILS.mdAlso request Copilot. Copilot is a deliberate second reviewer with a different lens — the policy reviewer enforces rules/*.md compliance, Copilot reads for correctness, bugs, security, and test gaps. The policy reviewer gates the merge only on blocking findings; advisory-only reviews post COMMENT and never gate, and Copilot is always advisory (read it, never gate on it) — see rules/review-severity.md:
skills/release/request-copilot-review.sh <owner> <repo> <pr-number>Proceed immediately to Step 5.
Block until the PR reaches a merge-gate-relevant terminal state. The watcher polls poll-pr-reviews.sh at a script-owned interval up to a script-owned budget and watches exactly the fields the Step 7 merge gate reads — each gating bot's latest review state (resolved by bot login), CI status, and merge state. Do not hand-roll a poll loop, and do not wrap the watch in an invented wall-clock timeout (see rules/ci-safety.md "Always Watch CI"):
skills/release/watch-pr-reviews.sh <owner> <repo> <pr-number>It returns the full poll-pr-reviews.sh snapshot plus a watch object — {"result": ..., "attempts": N, "elapsed_seconds": N}. The interval/budget constants and the result contract are the script's, not restated here (rules/script-as-black-box.md — see the header's result matrix). Branch on .watch.result:
ready (exit 0) — mergeable, CI success/none, both bots posted, the policy reviewer not CHANGES_REQUESTED (Copilot may be, and this still reaches ready — it is always advisory). Read every non-empty reviews.*.body (a COMMENTED verdict with zero inline comments still carries a body per rules/reviewer-feedback-reading.md), then proceed to Step 6.changes_requested (exit 0) — the policy reviewer requested changes (a blocking finding). Go to Step 6, address it, push; the next push re-fires the review, so re-run the watcher.ci_failure (exit 0) — a check failed. Fix it (Step 6), push, re-run the watcher.dirty (exit 0) — the branch conflicts with main and GitHub skipped the pull_request: workflows. Rebase onto current main, resolve, force-push, then re-run the watcher — the push re-fires the missed workflows.pending_at_budget (exit 1) — a signal never arrived within the budget (a reviewer that never posted, CI stuck pending). Inspect which field is still none/pending in the returned snapshot. If the policy reviewer never posted on coding-policy's own PRs, check the review-codex.yml run (gh run list --workflow review-codex.yml) — a missing or expired CODEX_AUTH_JSON secret is the usual cause. On a consumer repo, confirm review-trigger.yml dispatched and the fleet App ran in coding-policy; its scheduled poll is the backstop. Re-run the watcher to keep waiting once the cause is understood.reviews.*.body and every inline comment body before judging any item — a COMMENTED state or zero inline comments is not a license to skip the body (see rules/reviewer-feedback-reading.md)rules/review-severity.md): blocking findings — fix now; advisory findings — acknowledge, fold in only when a blocking round is already happening, else defer to a follow-up. Never burn a dedicated re-review round on a lone advisoryFixed in <sha> (literal phrase; Done / Accepted and fixed do not satisfy)Declining — <reason with cited evidence> (em dash —, not hyphen or period)Acknowledged — deferred to <follow-up ref> (em dash —; names where it is tracked)skills/release/request-copilot-review.sh (same args as Step 4). Copilot does not re-post on its own.review-codex.yml pull_request: synchronize; consumers via review-trigger.yml re-dispatching the fleet App). No manual re-request.APPROVED, or COMMENTED with its body read and only advisories — and every thread has a reply.Only proceed when:
.watch.result as ready — its exit-0 readiness conjunction (mergeable, CI success/none, both bots posted, the policy reviewer not CHANGES_REQUESTED); the field predicate is the watcher's, not restated here (rules/script-as-black-box.md — see skills/release/watch-pr-reviews.sh header). ready already requires each bot's state to have left none, so a reviewer that never ran cannot satisfy the gate vacuously, ANDreviews.*.body in the returned snapshot has been read in full — a COMMENTED state with zero inline comments is not a license to skip the body (see rules/reviewer-feedback-reading.md), ANDinline_comments count has a Fixed in <sha>, Declining — <reason>, or Acknowledged — deferred to <follow-up ref> reply per Step 6 (verify by listing the PR's review comments — the poll script tracks counts, not reply state, so the operator confirms thread closure). An advisory comment deferred with the Acknowledged — deferred reply closes its thread and never blocks the merge per rules/review-severity.md.A COMMENTED review never gates the merge on its state alone — but its body must be read before merge, zero inline comments included. With inline comments, it is mergeable once every thread also has a reply. Advisory findings (the reviewer's ## Advisory findings section, and every Copilot comment) do not block the merge — acknowledge them and defer per rules/review-severity.md; only a blocking finding gates.
Once these conditions hold, merge automatically per rules/ship-on-green.md — the green gates are the approval, stakes raise care not permission, and the only blocks are its three objective exits (Red / No undo / Murky). Do not pause to ask a human whether to merge.
Clear superseded review gates first. This applies to coding-policy's OWN releases, where the policy reviewer posts as github-actions[bot], which cannot APPROVE (GitHub returns HTTP 422), so a clean re-review lands as a COMMENT that does NOT supersede the bot's earlier CHANGES_REQUESTED — the stale request keeps merge_state.status at BLOCKED. On consumer repos the reviewer is the central fleet App coding-policy-fleet-reviewer[bot] (coding-policy#202), which CAN APPROVE and supersedes its own earlier CHANGES_REQUESTED directly — no dismissal needed there. Dismiss every superseded github-actions[bot] review before merging:
skills/release/dismiss-stale-reviews.sh <owner> <repo> <pr-number>Run it once Step 5's poll shows every bot's latest verdict clean. It emits a JSON summary of what it dismissed and what it left active, exits non-zero on API failure, and is idempotent on re-run. Which reviews it dismisses and which it leaves is the script's decision contract — see skills/release/dismiss-stale-reviews.sh header, not restated here (rules/script-as-black-box.md).
Name this repo's publication channels before merging. The confirmation a release owes is keyed on the publication, never on the package — a package that publishes through more than one channel owes the duty once per publication, each confirmed against the channel that carried it (rules/ci-safety.md Always Watch CI). Read the repo's publish workflow and its manifest, and name every channel it publishes on:
.tessl-plugin/plugin.json, legacy tile.json), or a publish path calling tessl plugin publish or tesslio/patch-version-publishon: push: tags:) that creates a release carrying the package's assets, an ACR package published by acr publishTessl publication: capture the registry baseline before merging. A publication on another channel skips this step:
PRE=$(skills/release/capture-registry-baseline.sh <workspace> <plugin> | jq -r .version)It emits one JSON object and exits non-zero on a parse miss or an empty registry value, so an unparseable baseline fails loudly instead of flowing into verify-publish-landed.sh as an empty PRE (which would pass conjunct 2 vacuously). The parse hardening and numeric-only output contract are the script's — see skills/release/capture-registry-baseline.sh header, not restated here (rules/script-as-black-box.md).
Pick the right cleanup path based on where you ran the skill from.
(A) From the base checkout (no additional worktree):
# Merge
gh pr merge <N> --merge --delete-branch
# Update local
git checkout main && git pull --ff-only
# Clean up local branch
git branch -d <branch>
# Prune stale remote refs
git remote prune origin(B) From an additional worktree (per rules/agent-worktree-isolation.md):
# Merge (running from inside the worktree is fine)
gh pr merge <N> --merge --delete-branch
# Return to the base checkout, fast-forward main
cd <path-to-base-checkout>
git checkout main && git pull --ff-only
# Tear down the worktree — directory + `.git/worktrees/` metadata
git worktree remove <path-to-worktree>
# Now safe: branch is no longer checked out anywhere and is fully merged
git branch -d <branch>
# Prune stale remote refs
git remote prune originOrder in (B) is mandatory: git branch -d refuses to delete a branch that is checked out in any worktree, so the git worktree remove step must come before git branch -d. Reversing the order produces a "checked out at <path>" error and leaves a stranded branch.
After merge — per rules/ci-safety.md's Always Watch CI duty extended through release:
Verify the merge landed on main (git pull --ff-only succeeds; git log -1 --oneline shows the merge commit)
GitHub tag/asset publication: push the release tag from the fast-forwarded main before resolving anything. Its publish workflow fires on the tag, never on the merge. The version follows Step 3:
git tag <tag> && git push origin <tag>Every publication, whatever channel carries it: resolve that publication's own run, watch it to a terminal state, and require its conclusion to be success. Bind the resolution to the workflow, the exact commit, the push event and the ref that fired it, never to "latest on main". The lookup polls until the run is listed, past the publish workflow's enqueue latency:
Each channel keeps its own run id in its own variable. A mixed publication runs both blocks and holds both ids at once; each confirmation below reads the id for its own channel.
# Tessl — the publish workflow fires on the merge commit.
merge_sha=$(gh pr view <N> --json mergeCommit --jq '.mergeCommit.oid')
tessl_run_id=$(skills/release/resolve-publish-run.sh <owner> <repo> "$merge_sha" "<tessl-publish-workflow>" | jq -r '.database_id')
gh run watch "$tessl_run_id"
# GitHub tag/asset — the publish workflow fires on the pushed tag, whose
# run carries the tag name as its `headBranch`. Pass the tag as the fifth
# argument and the commit the tag points at as the third.
tag_sha=$(git rev-list -n 1 "<tag>")
tag_run_id=$(skills/release/resolve-publish-run.sh <owner> <repo> "$tag_sha" "<tag-publish-workflow>" "<tag>" | jq -r '.database_id')
gh run watch "$tag_run_id"Omit --exit-status from the watch. Read the run conclusion through each channel's confirmation helper — verify-publish-landed.sh for Tessl, verify-github-release.sh for a tag publication.
gh pr view returns the specific merge commit for this PR, unaffected by parallel merges. The four facts the resolver binds, its enqueue-latency retry, and its refusal to pick between two runs matching all four are the script's decision contract — see skills/release/resolve-publish-run.sh header, not restated here (rules/script-as-black-box.md). Output is {"database_id": N} per rules/script-delegation.md — extract with jq -r '.database_id'. The watch is a timing precondition for the confirmations below, not the gate
Tessl publication: confirm the publish landed via the conjunction check — conjuncts 1 and 2 (resolved run's conclusion == success AND registry's Latest Version > PRE). Capture the emitted current version for the moderation check that follows:
# Gate on the exit code — only a clean conjunction (rc 0) may proceed to
# the moderation step. A non-zero rc (publish did not land, or a tool
# error) stops the release here; do not fall through to moderation.
landed=$(skills/release/verify-publish-landed.sh <workspace> <plugin> "$PRE" "$tessl_run_id") \
|| { echo "Publish not confirmed — $(jq -r '.reason // "see stderr"' <<<"$landed")" >&2; exit 1; }
CURRENT=$(jq -r '.current' <<<"$landed")Output is exit-code-dependent: rc 0/1 emits the JSON envelope {"ok": bool, "reason": "...", "run_conclusion": "...", "pre": "...", "current": "..."} on stdout (parse it for the finding); rc 2 emits the stderr diagnostic (tool-state errors: run still in flight, gh/tessl unreachable). Exception: the missing-jq guard at rc 2 emits a minimal JSON envelope on stdout (the script can't use jq to format JSON when jq itself is absent) so wrappers that always parse stdout still see a parseable failure. Do not compare against a specific expected version. See rules/ci-safety.md for full release-contract semantics and failed-publish recovery
Tessl publication: once conjuncts 1 and 2 hold, confirm moderation cleared — conjunct 3. A freshly published version can be install-blocked until its moderation state reaches pass; poll with exponential backoff (the script owns the backoff constants and the cleared/blocked decision):
skills/release/verify-moderation-cleared.sh <workspace> <plugin> "$CURRENT"Exit 0 = moderation cleared. Exit 1 = blocked or still-pending at budget exhaustion — an unconfirmed release; surface it and do not report success. Exit 2 = tool-state error (tessl unreachable, jq missing). Never report the release confirmed until this clears. See rules/ci-safety.md for the full three-conjunct contract. Every Tessl publication keeps this whole contract, mixed distribution included
GitHub tag/asset publication: confirm its own two conjuncts — the resolved run's conclusion is success, AND the release the run was supposed to create exists at that exact tag, is published, and carries retrievable assets. Run none of the three Tessl helpers above for it:
# Gate on the exit code, the same way the Tessl path gates on
# verify-publish-landed.sh. Pass THIS channel's run id: its conclusion
# is this check's first conjunct, and a mixed publication must not
# confirm the tag release against the Tessl run.
skills/release/verify-github-release.sh <owner> <repo> "<tag>" "$tag_run_id"Exit 0 = both conjuncts hold. Exit 1 = a definitive no (the run concluded something other than success, or the release is absent, draft, empty, or carries an asset still uploading) — an unconfirmed release; surface it and do not report success. Exit 2 = indeterminate (run still in flight, gh absent or unreachable); an indeterminate answer is never a landing. Which conjuncts it reads is the script's decision contract — see skills/release/verify-github-release.sh header, not restated here (rules/script-as-black-box.md)
Report the outcome: merged PR URL, the version published, and each publication's own confirmation — registry advance plus moderation clear for a Tessl publication, the published release and its retrievable assets for a tag publication, both for a package on both channels
When this step is wrapped in a reusable script (e.g., merge-and-cleanup.sh that other devs run unattended), see skills/release/SCRIPTING.md for the gates the script must enforce.
Finish here — the skill is complete.
.tessl-plugin
hooks
rules
skills
adopt-fork-pr
herdr-standup
herdr-teamlead
references
teamlead
templates
tests
migrate-to-plugin
onboard-repo
release
tests