diff --git a/.claude/agents/.AGENTS_PROVENANCE b/.claude/agents/.AGENTS_PROVENANCE index 545411de..5ed9fd3b 100644 --- a/.claude/agents/.AGENTS_PROVENANCE +++ b/.claude/agents/.AGENTS_PROVENANCE @@ -1,6 +1,6 @@ # VENDORED from harmon-devkit — DO NOT EDIT the managed agents here. # source: https://github.com/evanharmon1/harmon-devkit.git -# ref: v0.29.0 (e6596fb0fa1890a187e26ed9aae170517960d861) +# ref: v0.30.0 (92451b5b419779ef7823ea8acaaad4826ffebd22) # path: ai/agents # names: * # managed: implementer diff --git a/.claude/skills/.SKILLS_PROVENANCE b/.claude/skills/.SKILLS_PROVENANCE index bf842870..8eefe0de 100644 --- a/.claude/skills/.SKILLS_PROVENANCE +++ b/.claude/skills/.SKILLS_PROVENANCE @@ -1,6 +1,6 @@ # VENDORED from harmon-devkit — DO NOT EDIT the managed skills here. # source: https://github.com/evanharmon1/harmon-devkit.git -# ref: v0.29.0 (e6596fb0fa1890a187e26ed9aae170517960d861) +# ref: v0.30.0 (92451b5b419779ef7823ea8acaaad4826ffebd22) # path: ai/skills # categories: universal, repo # agents-dest: .claude/agents diff --git a/.claude/skills/shepherd/SKILL.md b/.claude/skills/shepherd/SKILL.md index 71a4c3b2..17b4862b 100644 --- a/.claude/skills/shepherd/SKILL.md +++ b/.claude/skills/shepherd/SKILL.md @@ -700,8 +700,12 @@ you rather than the bot): reply to, so no act on GitHub can ever record that you answered it and `check` returns exit 10 for that head forever — the deadlock the inline adjudication path was built to end, reappearing on the two surfaces it - cannot see. Answer the finding on the PR as usual (fix it, decline it with - reasoning, or file it), then record the disposition: + cannot see. Answer the finding on the PR as usual — and note that only two + of the three answers end here. **Fixing** it means a push, which moves the + head and starts a fresh cycle that reviews the fix on its own merits; + `settle` neither applies nor accepts that disposition. For the two answers + that leave the code alone — declining with reasoning, or filing it as + follow-up work — record the disposition: ```bash "$helper" settle --state "$state" --actor-id 199175422 \ @@ -1262,17 +1266,21 @@ loops indefinitely: When current-head Codex cloud review is enabled, **Codex Automatic reviews must be disabled in the external integration before the first promotion**. Otherwise `gh pr ready` can start a new asynchronous review after the gate - that supposedly completed automated work. Three knobs, all of them: + that supposedly completed automated work. Three knobs carry it: personal **Auto review** off, the repository's **Auto code review** - preference on **Follow personal**, and the repository's review **Trigger** - on Follow personal — an "On every push" trigger is dormant while Auto - review is off and arms the moment that toggle changes. GitHub does not - expose a reliable - repository API for this setting, so treat it honestly as a human-configured - prerequisite: use the repository setup record or maintainer confirmation, - never claim it was mechanically verified — a passing `readiness-gate.sh` - deliberately does not check it, and says so. If its state is unknown, stop - blocked and ask rather than promote. + preference on **Follow personal**, and the repository's + review **Trigger** on Follow personal — an "On every push" trigger is + dormant while Auto review is off and arms the moment that toggle changes. + + **This is settled configuration, not a promotion-time check.** The + consuming repository's `AGENTS.md` carries the maintainer's confirmation + and its setup checklist carries the how-to; nothing in this stage gates on + it, and `readiness-gate.sh` says nothing about it either. + The one thing worth raising is an anomaly you happen to observe: if a + Codex cloud review fires **unsolicited** — after a push or a promotion + that no `@codex review` comment triggered — tell the maintainer, because + that is the signature of the knobs drifting back on. Report it and carry + on; it blocks nothing and there is no state to poll. Report the ready state honestly rather than over-claiming: `BLOCKED` or `REVIEW_REQUIRED` mean "ready for review and awaiting the diff --git a/.claude/skills/shepherd/assets/readiness-gate.sh b/.claude/skills/shepherd/assets/readiness-gate.sh index ba6925eb..2bacc90c 100755 --- a/.claude/skills/shepherd/assets/readiness-gate.sh +++ b/.claude/skills/shepherd/assets/readiness-gate.sh @@ -55,13 +55,11 @@ # checks-indeterminate, merge-state-unknown, fetch-failed, # malformed-data, codex-indeterminate, usage (indeterminate) # -# Three readiness conditions are deliberately NOT verified here, because no +# Two readiness conditions are deliberately NOT verified here, because no # API answers them — the caller must hold them as prose prerequisites: # - required automation that reacts only to `pull_request.ready_for_review` # (a configuration blocker: promotion would notify humans before its # result exists); -# - the Codex Automatic-review knobs being off/Follow-personal (a -# human-configured prerequisite; GitHub exposes no reliable API for it); # - a required context that NEVER REGISTERED on this head. The checks # condition judges every check GitHub reports for the commit; a required # workflow that failed to trigger appears in no list, and the only state @@ -411,9 +409,19 @@ evaluate_checks() { # suites asynchronously, so a read moments after a push reports nothing # having run rather than nothing to run. A repo with genuinely no CI # needs a human to say so — this gate cannot tell the two apart. + # --slurpfile over process substitution, never --argjson: a much-rerun + # head's check-runs payload exceeds the per-argument limit as argv and jq + # dies "Argument list too long", which this gate could only report as + # `malformed-data` — indeterminate for a purely mechanical reason, on + # exactly the heads it matters most for (observed on harmon-init#821's + # gate after three infra reruns, 2026-08-12, where it also masked a real + # merge-state-behind condition). printf is a shell builtin, so no exec + # carries the payload; the fd does. $runs/$statuses are bound below so the + # classification program itself is unchanged. checks_summary="$(jq -cn \ - --argjson runs "$check_runs" \ - --argjson statuses "$statuses" ' + --slurpfile runs_sf <(printf '%s' "$check_runs") \ + --slurpfile statuses_sf <(printf '%s' "$statuses") ' + $runs_sf[0] as $runs | $statuses_sf[0] as $statuses | def run_state: if .status != "completed" then "pending" elif (.conclusion == "success" or .conclusion == "neutral" @@ -432,10 +440,21 @@ evaluate_checks() { indeterminate malformed-data "check states could not be classified" [ "$(jq -r '.total' <<<"$checks_summary")" -gt 0 ] || indeterminate checks-indeterminate "GitHub reports no checks for this head — populated asynchronously, so re-poll; if the repo truly has no CI, that is a human call, not a pass" - failing_checks="$(jq -r '.failing | join(", ")' <<<"$checks_summary")" + # Bound the name lists the detail carries: with thousands of failing + # checks the joined names are themselves a multi-megabyte string, and + # emit's `jq --arg detail` puts that back into a single argv entry — the + # same ARG_MAX death the --slurpfile change above just removed, one step + # downstream. Twenty names diagnose as well as twenty thousand. + failing_checks="$(jq -r ' + .failing | if length > 20 + then (.[0:20] | join(", ")) + " … and \(length - 20) more" + else join(", ") end' <<<"$checks_summary")" [ -z "$failing_checks" ] || fail_condition checks-failing "checks failing: $failing_checks" - pending_checks="$(jq -r '.pending | join(", ")' <<<"$checks_summary")" + pending_checks="$(jq -r ' + .pending | if length > 20 + then (.[0:20] | join(", ")) + " … and \(length - 20) more" + else join(", ") end' <<<"$checks_summary")" [ -z "$pending_checks" ] || fail_condition checks-pending "checks not yet concluded: $pending_checks" } @@ -691,7 +710,7 @@ esac if [ "$require_draft" = 1 ]; then verdict_condition=ready - verdict_detail="every mechanically checkable readiness condition holds; ready_for_review-only automation and the Codex Auto-review knobs remain human-verified prerequisites" + verdict_detail="every mechanically checkable readiness condition holds" else verdict_condition=audit verdict_detail="every mechanically checkable condition except the draft requirement holds; this audits an existing promotion and never authorizes gh pr ready" diff --git a/.skills-sync.yaml b/.skills-sync.yaml index 760d69df..f6ebbd68 100644 --- a/.skills-sync.yaml +++ b/.skills-sync.yaml @@ -8,7 +8,7 @@ source: repo: https://github.com/evanharmon1/harmon-devkit.git # renovate: datasource=github-releases depName=evanharmon1/harmon-devkit - ref: v0.29.0 # pinned tag — bump deliberately to a released harmon-devkit tag + ref: v0.30.0 # pinned tag — bump deliberately to a released harmon-devkit tag categories: - universal - repo diff --git a/template/[% if use_skills_sync %].skills-sync.yaml[% endif %].jinja b/template/[% if use_skills_sync %].skills-sync.yaml[% endif %].jinja index fa542aff..a8c6c34f 100644 --- a/template/[% if use_skills_sync %].skills-sync.yaml[% endif %].jinja +++ b/template/[% if use_skills_sync %].skills-sync.yaml[% endif %].jinja @@ -9,7 +9,7 @@ source: repo: https://github.com/evanharmon1/harmon-devkit.git # renovate: datasource=github-releases depName=evanharmon1/harmon-devkit - ref: v0.29.0 # pinned tag — bump deliberately to a released harmon-devkit tag + ref: v0.30.0 # pinned tag — bump deliberately to a released harmon-devkit tag categories: [% for cat in skill_categories %] - [[ cat ]]