From e47d74090a5a05d5f6fc4a4321760a3f57b410c0 Mon Sep 17 00:00:00 2001 From: "tbhb.dev Agent" Date: Mon, 2 Nov 2026 08:36:59 -0500 Subject: [PATCH 1/2] fix(merge-pr): count the merge owner's approval as the review An unattended merge required an approval from the configured required reviewer and stopped when only the operator had approved. That left a pull request stuck even with the strongest review it could get, because no second approval was coming. Now the gate accepts an APPROVED review from the merge owner on the exact current head when the required reviewer hasn't approved, and the merge report names the approving login and head. Required checks, the owner-review label, write authority, and a clean mergeable state still apply. A later push still invalidates the approval. Editing that script trips the changed-file shell check while the shell migration is open, so the shell allow list gains an exact-path exemption list that only the changed-file check honors. Its single entry is this script, and explicit path checks still report every finding for it. Remove the entry when the Go port replaces the script. Assisted-by: codex:gpt-6.1-sol Signed-off-by: Tony Burns --- .agents/skills/agent-merge-pr/SKILL.md | 2 +- .agents/skills/agent-merge-pr/tokens.json | 6 ++-- .../skills/merge-pr/scripts/merge-agent.sh | 12 +++++-- .agents/skills/merge-pr/tokens.json | 6 ++-- .claude/skills/agent-merge-pr/SKILL.md | 2 +- .claude/skills/agent-merge-pr/tokens.json | 6 ++-- .../skills/merge-pr/scripts/merge-agent.sh | 12 +++++-- .claude/skills/merge-pr/tokens.json | 6 ++-- .devtools/shell-allowlist.toml | 4 +++ AGENTS.md | 2 +- apm.lock.yaml | 32 +++++++++---------- cmd/shell-allowlist/changed_test.go | 15 +++++++++ cmd/shell-allowlist/run.go | 5 +++ docs/agent-skills-migration.md | 2 +- docs/shell-allowlist.md | 2 ++ internal/shell/shell.go | 15 +++++++-- .../.apm/skills/agent-merge-pr/SKILL.md | 2 +- .../.apm/skills/agent-merge-pr/tokens.json | 6 ++-- .../skills/merge-pr/scripts/merge-agent.sh | 12 +++++-- .../claude/.apm/skills/merge-pr/tokens.json | 6 ++-- .../codex/.apm/skills/agent-merge-pr/SKILL.md | 2 +- .../.apm/skills/agent-merge-pr/tokens.json | 6 ++-- .../skills/merge-pr/scripts/merge-agent.sh | 12 +++++-- .../codex/.apm/skills/merge-pr/tokens.json | 6 ++-- .../skills/agent-merge-pr/SKILL.claude.md | 2 +- .../skills/agent-merge-pr/SKILL.codex.md | 2 +- .../merge-pr/shared/scripts/merge-agent.sh | 12 +++++-- 27 files changed, 136 insertions(+), 61 deletions(-) diff --git a/.agents/skills/agent-merge-pr/SKILL.md b/.agents/skills/agent-merge-pr/SKILL.md index 792b0b02..85a5eb1d 100644 --- a/.agents/skills/agent-merge-pr/SKILL.md +++ b/.agents/skills/agent-merge-pr/SKILL.md @@ -20,7 +20,7 @@ Install sibling `merge-pr` and `review-squash-message` from the same package. Th 4. Run `bash .agents/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. 5. Report the full merge commit, owner paths, any owner override and its login, and retained branches or worktrees. A failed or uncertain write requires inspection before retrying. A remote message mismatch preserves evidence and skips cleanup. -Repository behavior comes from declarative `.agents/agent-skills.conf`. Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The owner-review label still holds the merge. +Repository behavior comes from declarative `.agents/agent-skills.conf`. Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The same approval by `merge_owner` also satisfies the `required_reviewer` gate, and the report states the approving login. The owner-review label still holds the merge. The protected-path scan reads APM ownership from the pinned base lockfile and skips non-local deployments. It ignores full comment lines and matches exclusion keys rather than words inside values. Repository policy changes require the same owner approval. Reviewers must still identify policy changes the scan can't detect. diff --git a/.agents/skills/agent-merge-pr/tokens.json b/.agents/skills/agent-merge-pr/tokens.json index 1c8f61fd..4985f669 100644 --- a/.agents/skills/agent-merge-pr/tokens.json +++ b/.agents/skills/agent-merge-pr/tokens.json @@ -32,10 +32,10 @@ "bundled_files_tokens": 331, "generated_by": "tools/skill-tokens.sh", "manifest": { - "body_tokens": 933, - "bytes": 4832, + "body_tokens": 959, + "bytes": 4955, "frontmatter_tokens": 58, - "tokens": 992 + "tokens": 1018 }, "model": "gpt-5.6-sol", "skill": "agent-merge-pr", diff --git a/.agents/skills/merge-pr/scripts/merge-agent.sh b/.agents/skills/merge-pr/scripts/merge-agent.sh index e3fb86ab..79cdfa0e 100644 --- a/.agents/skills/merge-pr/scripts/merge-agent.sh +++ b/.agents/skills/merge-pr/scripts/merge-agent.sh @@ -106,7 +106,7 @@ ma_identity() { # Reused before review and again immediately before every merge write. ma_gates() { ma_require_gates || return 0 - local view=$1 pr=$2 base_oid=$3 final=${4:-false} head base reviewer reviewer_type label ready config key value required default dir st root encoded merge_base + local view=$1 pr=$2 base_oid=$3 final=${4:-false} head base reviewer reviewer_type owner label ready config key value required default dir st root encoded merge_base ma_context || return $? st=$(dt_state_dir) || return $? root=$(dt_root) || return $? @@ -121,7 +121,15 @@ ma_gates() { '' | Bot | User) ;; *) dt_die "required_reviewer_type must be Bot or User when set" ;; esac - "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$reviewer" --head "$head" --author-type "$reviewer_type" || dt_die "WAITING_REVIEW: $reviewer must approve the current head" + if ! "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$reviewer" --head "$head" --author-type "$reviewer_type"; then + # The operator's own exact-head approval counts as the independent review. + owner=$(ma_login merge_owner) || return $? + if [ -n "$owner" ] && "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$owner" --head "$head"; then + printf 'reviewer satisfied by merge_owner: %s approved %s\n' "$owner" "$head" + else + dt_die "WAITING_REVIEW: $reviewer or merge_owner must approve the current head" + fi + fi label=$(dt_owner_review_label) || return $? if jq -e --arg label "$label" '.labels | any(.name == $label)' "$view" >/dev/null; then dt_die "needs-owner: the PR has the owner-review label" diff --git a/.agents/skills/merge-pr/tokens.json b/.agents/skills/merge-pr/tokens.json index 6ef4c0c3..8676a496 100644 --- a/.agents/skills/merge-pr/tokens.json +++ b/.agents/skills/merge-pr/tokens.json @@ -24,9 +24,9 @@ "tokens": 375 }, { - "bytes": 11068, + "bytes": 11455, "path": "scripts/merge-agent.sh", - "tokens": 3217 + "tokens": 3322 }, { "bytes": 29915, @@ -74,7 +74,7 @@ "tokens": 392 } ], - "bundled_files_tokens": 40864, + "bundled_files_tokens": 40969, "generated_by": "tools/skill-tokens.sh", "manifest": { "body_tokens": 3802, diff --git a/.claude/skills/agent-merge-pr/SKILL.md b/.claude/skills/agent-merge-pr/SKILL.md index 71d38ce3..58f8f23d 100644 --- a/.claude/skills/agent-merge-pr/SKILL.md +++ b/.claude/skills/agent-merge-pr/SKILL.md @@ -28,7 +28,7 @@ Install sibling `merge-pr` and `review-squash-message` from the same package. Th Repository behavior comes from declarative `.agents/agent-skills.conf`. -Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The owner-review label still holds the merge. +Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The same approval by `merge_owner` also satisfies the `required_reviewer` gate, and the report states the approving login. The owner-review label still holds the merge. The protected-path scan reads APM ownership from the pinned base lockfile and skips non-local deployments. It ignores full comment lines and matches exclusion keys rather than words inside values. Repository policy changes require the same owner approval. Reviewers must still identify policy changes the scan can't detect. diff --git a/.claude/skills/agent-merge-pr/tokens.json b/.claude/skills/agent-merge-pr/tokens.json index 954c5efa..c11dcb15 100644 --- a/.claude/skills/agent-merge-pr/tokens.json +++ b/.claude/skills/agent-merge-pr/tokens.json @@ -32,10 +32,10 @@ "bundled_files_tokens": 621, "generated_by": "tools/skill-tokens.sh", "manifest": { - "body_tokens": 1553, - "bytes": 5001, + "body_tokens": 1591, + "bytes": 5124, "frontmatter_tokens": 180, - "tokens": 1737 + "tokens": 1775 }, "model": "claude-opus-5", "skill": "agent-merge-pr" diff --git a/.claude/skills/merge-pr/scripts/merge-agent.sh b/.claude/skills/merge-pr/scripts/merge-agent.sh index e3fb86ab..79cdfa0e 100644 --- a/.claude/skills/merge-pr/scripts/merge-agent.sh +++ b/.claude/skills/merge-pr/scripts/merge-agent.sh @@ -106,7 +106,7 @@ ma_identity() { # Reused before review and again immediately before every merge write. ma_gates() { ma_require_gates || return 0 - local view=$1 pr=$2 base_oid=$3 final=${4:-false} head base reviewer reviewer_type label ready config key value required default dir st root encoded merge_base + local view=$1 pr=$2 base_oid=$3 final=${4:-false} head base reviewer reviewer_type owner label ready config key value required default dir st root encoded merge_base ma_context || return $? st=$(dt_state_dir) || return $? root=$(dt_root) || return $? @@ -121,7 +121,15 @@ ma_gates() { '' | Bot | User) ;; *) dt_die "required_reviewer_type must be Bot or User when set" ;; esac - "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$reviewer" --head "$head" --author-type "$reviewer_type" || dt_die "WAITING_REVIEW: $reviewer must approve the current head" + if ! "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$reviewer" --head "$head" --author-type "$reviewer_type"; then + # The operator's own exact-head approval counts as the independent review. + owner=$(ma_login merge_owner) || return $? + if [ -n "$owner" ] && "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$owner" --head "$head"; then + printf 'reviewer satisfied by merge_owner: %s approved %s\n' "$owner" "$head" + else + dt_die "WAITING_REVIEW: $reviewer or merge_owner must approve the current head" + fi + fi label=$(dt_owner_review_label) || return $? if jq -e --arg label "$label" '.labels | any(.name == $label)' "$view" >/dev/null; then dt_die "needs-owner: the PR has the owner-review label" diff --git a/.claude/skills/merge-pr/tokens.json b/.claude/skills/merge-pr/tokens.json index be4cc611..52919213 100644 --- a/.claude/skills/merge-pr/tokens.json +++ b/.claude/skills/merge-pr/tokens.json @@ -29,9 +29,9 @@ "tokens": 616 }, { - "bytes": 11068, + "bytes": 11455, "path": "scripts/merge-agent.sh", - "tokens": 5293 + "tokens": 5464 }, { "bytes": 29915, @@ -79,7 +79,7 @@ "tokens": 610 } ], - "bundled_files_tokens": 66702, + "bundled_files_tokens": 66873, "generated_by": "tools/skill-tokens.sh", "manifest": { "body_tokens": 6180, diff --git a/.devtools/shell-allowlist.toml b/.devtools/shell-allowlist.toml index dca50ee2..d1e79c2d 100644 --- a/.devtools/shell-allowlist.toml +++ b/.devtools/shell-allowlist.toml @@ -101,3 +101,7 @@ commands = [ "id", "sleep", ] + +# Operator-approved exception for #1053; remove when the Go port in #864 lands. +# Exact source path only; generated copies are already excluded by --changed. +exemptions = ["packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh"] diff --git a/AGENTS.md b/AGENTS.md index 8708137a..a7863332 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -69,7 +69,7 @@ Repository configuration supplies squash lint, readiness, ruleset location, owne devtools keeps its merge patterns in `.agents/agent-skills.conf`. They protect the published payload, workflows, rule sets, gate definitions, and skill guards. Cleanup permits build and cache output and preserves local settings and agent worktrees. Policy errors include the refusal reason and specify any empty pattern. -Golden files and detected gate loosening require an exact-head APPROVED review by machine `merge_owner`, also available as `DEVTOOLS_MERGE_OWNER`. Use the GraphQL login without `[bot]` for `merge_owner` too. Keep this authority a person: an App owner override can approve its own golden-file waivers. Repositories can't choose this authority, and pushing invalidates approval. +Golden files and detected gate loosening require an exact-head APPROVED review by machine `merge_owner`, also available as `DEVTOOLS_MERGE_OWNER`. Use the GraphQL login without `[bot]` for `merge_owner` too. Keep this authority a person: an App owner override can approve its own golden-file waivers. Repositories can't choose this authority, and pushing invalidates approval. The same exact-head approval by `merge_owner` also satisfies the `required_reviewer` gate when the required reviewer hasn't approved, because the operator's approval counts as the independent review. The merge report prints `reviewer satisfied by merge_owner` with the approving login and head. Required checks, the owner-review label, write authority, and a clean mergeable state still apply. When using an override, the merge report specifies the approving login. It doesn't waive the owner-review label, required checks, independent review, or write authority. diff --git a/apm.lock.yaml b/apm.lock.yaml index 3c74aa1d..920536f7 100644 --- a/apm.lock.yaml +++ b/apm.lock.yaml @@ -172,13 +172,13 @@ dependencies: .claude/skills/agent-commit/scripts/preflight.sh: sha256:150577c193b70fb169147eb619b8a715d37327fa7cd4b502b556cd5a87e8f0a8 .claude/skills/agent-commit/scripts/review.sh: sha256:561b69d7c0892c14deb1d1946a9ebefea817d64ff066a17dc02b8311b3711039 .claude/skills/agent-commit/tokens.json: sha256:7bb628b511e28f56a429e071bb19391c8ae1f0074b9ff7e7f3a625fc8aff7e44 - .claude/skills/agent-merge-pr/SKILL.md: sha256:f69f13f6db55cc4057f7d406e3410b5a497d25c0d9e6e493dadfb6afd0c5e047 + .claude/skills/agent-merge-pr/SKILL.md: sha256:52741aa96b7d995ba4b7c454de40b58d293e2c6ee5b82d2ecd304869851b1a36 .claude/skills/agent-merge-pr/scripts/lint-squash-msg.sh: sha256:3c5c3cf4694dbb291dada2279ffbf8312707e6025078ac0c38c2a829c60e5097 .claude/skills/agent-merge-pr/scripts/merge.sh: sha256:eed8c25ee35205e750192aa8ae6cd06cc5efd3ba8ff8adb3b8eb44fd07803142 .claude/skills/agent-merge-pr/scripts/preflight.sh: sha256:6556e7396f94e9f3241769f1f795145070a0ebdb3c411dac68decde5daff7e35 .claude/skills/agent-merge-pr/scripts/review.sh: sha256:6808c9db9267d93ff1a6eb3f205fbb80ed072ef5ccd97726efae9cc5b43e7956 .claude/skills/agent-merge-pr/scripts/squash-message.sh: sha256:5e114db1fc7354da7d5a1f94e2f124cdae950c1215763c0eb6ae6fff1eca278c - .claude/skills/agent-merge-pr/tokens.json: sha256:41a4f240c59523cc5dad00b8f45e6d7c891f2bc18e12b3bb644e3b1ff856c81a + .claude/skills/agent-merge-pr/tokens.json: sha256:c3be348740f568119abb1c3972d65a489d044ebcb2142657dfe7b6ae2115a2b3 .claude/skills/agent-pr/SKILL.md: sha256:cd5015fff9c31c2211df4a845be74396db8096582ce1de43ddd4327164711426 .claude/skills/agent-pr/scripts/check.sh: sha256:d31964cbb5ae85554820983c3b6e5904790a152a17696f2cddb8b2f76d692cd3 .claude/skills/agent-pr/scripts/preflight.sh: sha256:fa23bac0cf361bd68eedd361bdf88c825f33dd512df3631fdf275069fa07dbb4 @@ -232,7 +232,7 @@ dependencies: .claude/skills/merge-pr/scripts/guard-merge.sh: sha256:f29dced5d631d1307865c9d33b6109d585de7f128a11874f340315ddb9b7a491 .claude/skills/merge-pr/scripts/lib.sh: sha256:423a4e4c70dc86b8657a9305c81c47ad3f575e576952375f988fafcd1801bec8 .claude/skills/merge-pr/scripts/lint-squash-msg.sh: sha256:910879475b0b3ae890734b0879a6e7c2298e8ff35335fc64efae600bc23ec199 - .claude/skills/merge-pr/scripts/merge-agent.sh: sha256:5b5c3aa6a7d44f939d664e3bfbe7b13f2b836ee9848d148186b96b48eb9cd43d + .claude/skills/merge-pr/scripts/merge-agent.sh: sha256:a2c0b94103b1bd70dae74b436fdf7c802cb668e49a074eeb10c952f5eb33facf .claude/skills/merge-pr/scripts/merge-policy.py: sha256:d21bbf64f212dc8f07689194bad65b0f08e74aa99ef4fb43ac9490e955ffdcea .claude/skills/merge-pr/scripts/populate-description.sh: sha256:ed9688d83433ff7e1700255ae02e3dc912faf34dc34bdd9edb22eb02b8032d3a .claude/skills/merge-pr/scripts/preflight.sh: sha256:1e8c899e7db05cad6cd6acdba440ac2472c6d491b757770a8e391113a29b98cc @@ -242,7 +242,7 @@ dependencies: .claude/skills/merge-pr/scripts/squash-merge.sh: sha256:f194e934740cec0a113fb5d77d71bf76b929cddc2ede9a8700ede6a63d7f3eb6 .claude/skills/merge-pr/scripts/squash-message.sh: sha256:9b512745a0ef14ab52cb6625460556ac62f2cf80afec89d021885679b674c683 .claude/skills/merge-pr/scripts/worktree-ready.sh: sha256:8323a36ec25731a8a9f99dde74b8d24c1d6f536d8ebebe29b53dcfab4b51fc44 - .claude/skills/merge-pr/tokens.json: sha256:cdda1714b11cca088d7040b0dc521a3bb34f2924ba1a0751c81e4e9add239630 + .claude/skills/merge-pr/tokens.json: sha256:d1fefd7d8c66137fd2286159380502b1f5527be9afb4c1aef1e050881d24640b .claude/skills/pr/SKILL.md: sha256:2dcbef118908bdcadee7adcc7dbca640435fafb94ec0443721cf0f33076d14fb .claude/skills/pr/scripts/agent.sh: sha256:3f424af5be51d697b2612f3cc50d28d2106e914bea3c5a7391e9316f9f157706 .claude/skills/pr/scripts/check.sh: sha256:447a4f1808090a20ac22750050c4f9fc05fd130d5bfe8e2621ff23957492677e @@ -459,13 +459,13 @@ dependencies: .agents/skills/agent-commit/scripts/preflight.sh: sha256:150577c193b70fb169147eb619b8a715d37327fa7cd4b502b556cd5a87e8f0a8 .agents/skills/agent-commit/scripts/review.sh: sha256:561b69d7c0892c14deb1d1946a9ebefea817d64ff066a17dc02b8311b3711039 .agents/skills/agent-commit/tokens.json: sha256:65e686f8218b617c58725b9e7aecde4cae08a4f72a3570cba58e89bf66c173d0 - .agents/skills/agent-merge-pr/SKILL.md: sha256:2958e32035f16927276b4a2fe69adcdc7913fd2165994deaf9019b2c1095ab65 + .agents/skills/agent-merge-pr/SKILL.md: sha256:6955a38d0ad61795774af14a85de05035f96ee23f83dc4a247008f7790ef97ce .agents/skills/agent-merge-pr/scripts/lint-squash-msg.sh: sha256:3c5c3cf4694dbb291dada2279ffbf8312707e6025078ac0c38c2a829c60e5097 .agents/skills/agent-merge-pr/scripts/merge.sh: sha256:eed8c25ee35205e750192aa8ae6cd06cc5efd3ba8ff8adb3b8eb44fd07803142 .agents/skills/agent-merge-pr/scripts/preflight.sh: sha256:6556e7396f94e9f3241769f1f795145070a0ebdb3c411dac68decde5daff7e35 .agents/skills/agent-merge-pr/scripts/review.sh: sha256:6808c9db9267d93ff1a6eb3f205fbb80ed072ef5ccd97726efae9cc5b43e7956 .agents/skills/agent-merge-pr/scripts/squash-message.sh: sha256:5e114db1fc7354da7d5a1f94e2f124cdae950c1215763c0eb6ae6fff1eca278c - .agents/skills/agent-merge-pr/tokens.json: sha256:9027f8a0a6bbc38a9a61537b4d8a80fffa540d4c8e0c454f8d7a23b166243c81 + .agents/skills/agent-merge-pr/tokens.json: sha256:37b4895da1cacde098b0a1653a8bef75a30a8f98f710eeb179e1e5979282c0e0 .agents/skills/agent-pr/SKILL.md: sha256:382b1590a90e3640978d57c1a740a160b62dc2c70972b7242d3aa3a7940970f1 .agents/skills/agent-pr/scripts/check.sh: sha256:d31964cbb5ae85554820983c3b6e5904790a152a17696f2cddb8b2f76d692cd3 .agents/skills/agent-pr/scripts/preflight.sh: sha256:fa23bac0cf361bd68eedd361bdf88c825f33dd512df3631fdf275069fa07dbb4 @@ -513,7 +513,7 @@ dependencies: .agents/skills/merge-pr/scripts/gates.py: sha256:f49d8d33c3b5d4d0cf7f0b8c06cfe61ae72e6215b78d3e1e83fe931a5a03a3a1 .agents/skills/merge-pr/scripts/lib.sh: sha256:423a4e4c70dc86b8657a9305c81c47ad3f575e576952375f988fafcd1801bec8 .agents/skills/merge-pr/scripts/lint-squash-msg.sh: sha256:910879475b0b3ae890734b0879a6e7c2298e8ff35335fc64efae600bc23ec199 - .agents/skills/merge-pr/scripts/merge-agent.sh: sha256:5b5c3aa6a7d44f939d664e3bfbe7b13f2b836ee9848d148186b96b48eb9cd43d + .agents/skills/merge-pr/scripts/merge-agent.sh: sha256:a2c0b94103b1bd70dae74b436fdf7c802cb668e49a074eeb10c952f5eb33facf .agents/skills/merge-pr/scripts/merge-policy.py: sha256:d21bbf64f212dc8f07689194bad65b0f08e74aa99ef4fb43ac9490e955ffdcea .agents/skills/merge-pr/scripts/populate-description.sh: sha256:ed9688d83433ff7e1700255ae02e3dc912faf34dc34bdd9edb22eb02b8032d3a .agents/skills/merge-pr/scripts/preflight.sh: sha256:1e8c899e7db05cad6cd6acdba440ac2472c6d491b757770a8e391113a29b98cc @@ -523,7 +523,7 @@ dependencies: .agents/skills/merge-pr/scripts/squash-merge.sh: sha256:f194e934740cec0a113fb5d77d71bf76b929cddc2ede9a8700ede6a63d7f3eb6 .agents/skills/merge-pr/scripts/squash-message.sh: sha256:9b512745a0ef14ab52cb6625460556ac62f2cf80afec89d021885679b674c683 .agents/skills/merge-pr/scripts/worktree-ready.sh: sha256:8323a36ec25731a8a9f99dde74b8d24c1d6f536d8ebebe29b53dcfab4b51fc44 - .agents/skills/merge-pr/tokens.json: sha256:43dc9d799a4bf8730a3027d37e9d856111a0c9f6ca843f020672021d4904c4c7 + .agents/skills/merge-pr/tokens.json: sha256:97555408b4eac9a00e6aad50ee9073f771c8961dd844146c607d8c218f15a6f3 .agents/skills/pr/SKILL.md: sha256:bed85864eae6150301317977ab76a0110d012940459253ad2292a6dd350e1752 .agents/skills/pr/scripts/agent.sh: sha256:3f424af5be51d697b2612f3cc50d28d2106e914bea3c5a7391e9316f9f157706 .agents/skills/pr/scripts/check.sh: sha256:447a4f1808090a20ac22750050c4f9fc05fd130d5bfe8e2621ff23957492677e @@ -676,7 +676,7 @@ deployments: owners: - ./packages/agents/codex active_owner: ./packages/agents/codex - content_hash: sha256:2958e32035f16927276b4a2fe69adcdc7913fd2165994deaf9019b2c1095ab65 + content_hash: sha256:6955a38d0ad61795774af14a85de05035f96ee23f83dc4a247008f7790ef97ce - kind: project-relative target: antigravity value: .agents/skills/agent-merge-pr/scripts/lint-squash-msg.sh @@ -730,7 +730,7 @@ deployments: owners: - ./packages/agents/codex active_owner: ./packages/agents/codex - content_hash: sha256:9027f8a0a6bbc38a9a61537b4d8a80fffa540d4c8e0c454f8d7a23b166243c81 + content_hash: sha256:37b4895da1cacde098b0a1653a8bef75a30a8f98f710eeb179e1e5979282c0e0 - kind: project-relative target: antigravity value: .agents/skills/agent-pr @@ -1216,7 +1216,7 @@ deployments: owners: - ./packages/agents/codex active_owner: ./packages/agents/codex - content_hash: sha256:5b5c3aa6a7d44f939d664e3bfbe7b13f2b836ee9848d148186b96b48eb9cd43d + content_hash: sha256:a2c0b94103b1bd70dae74b436fdf7c802cb668e49a074eeb10c952f5eb33facf - kind: project-relative target: antigravity value: .agents/skills/merge-pr/scripts/merge-policy.py @@ -1306,7 +1306,7 @@ deployments: owners: - ./packages/agents/codex active_owner: ./packages/agents/codex - content_hash: sha256:43dc9d799a4bf8730a3027d37e9d856111a0c9f6ca843f020672021d4904c4c7 + content_hash: sha256:97555408b4eac9a00e6aad50ee9073f771c8961dd844146c607d8c218f15a6f3 - kind: project-relative target: antigravity value: .agents/skills/pr @@ -1972,7 +1972,7 @@ deployments: owners: - ./packages/agents/claude active_owner: ./packages/agents/claude - content_hash: sha256:f69f13f6db55cc4057f7d406e3410b5a497d25c0d9e6e493dadfb6afd0c5e047 + content_hash: sha256:52741aa96b7d995ba4b7c454de40b58d293e2c6ee5b82d2ecd304869851b1a36 - kind: project-relative target: claude value: .claude/skills/agent-merge-pr/scripts/lint-squash-msg.sh @@ -2026,7 +2026,7 @@ deployments: owners: - ./packages/agents/claude active_owner: ./packages/agents/claude - content_hash: sha256:41a4f240c59523cc5dad00b8f45e6d7c891f2bc18e12b3bb644e3b1ff856c81a + content_hash: sha256:c3be348740f568119abb1c3972d65a489d044ebcb2142657dfe7b6ae2115a2b3 - kind: project-relative target: claude value: .claude/skills/agent-pr @@ -2566,7 +2566,7 @@ deployments: owners: - ./packages/agents/claude active_owner: ./packages/agents/claude - content_hash: sha256:5b5c3aa6a7d44f939d664e3bfbe7b13f2b836ee9848d148186b96b48eb9cd43d + content_hash: sha256:a2c0b94103b1bd70dae74b436fdf7c802cb668e49a074eeb10c952f5eb33facf - kind: project-relative target: claude value: .claude/skills/merge-pr/scripts/merge-policy.py @@ -2656,7 +2656,7 @@ deployments: owners: - ./packages/agents/claude active_owner: ./packages/agents/claude - content_hash: sha256:cdda1714b11cca088d7040b0dc521a3bb34f2924ba1a0751c81e4e9add239630 + content_hash: sha256:d1fefd7d8c66137fd2286159380502b1f5527be9afb4c1aef1e050881d24640b - kind: project-relative target: claude value: .claude/skills/pr diff --git a/cmd/shell-allowlist/changed_test.go b/cmd/shell-allowlist/changed_test.go index 73461a41..2ffd0fbd 100644 --- a/cmd/shell-allowlist/changed_test.go +++ b/cmd/shell-allowlist/changed_test.go @@ -73,6 +73,21 @@ func TestChangedScripts(t *testing.T) { out.Reset() assert.Equal(t, 1, Run([]string{"--changed", "origin/main"}, &out)) assert.Equal(t, ".claude/skills/local/scripts/run.sh:1: ruby: banned\n", out.String()) + // Exemptions match one exact path only in changed-file mode. + write( + ".devtools/shell-allowlist.toml", + "commands = ['printf']\nexemptions = ['.claude/skills/local/scripts/run.sh']\n", + ) + out.Reset() + assert.Equal(t, 0, Run([]string{"--changed", "origin/main"}, &out)) + assert.Empty(t, out.String()) + write(".claude/skills/local/scripts/other.sh", "ruby forbidden\n") + out.Reset() + assert.Equal(t, 1, Run([]string{"--changed", "origin/main"}, &out)) + assert.Equal(t, ".claude/skills/local/scripts/other.sh:1: ruby: banned\n", out.String()) + out.Reset() + assert.Equal(t, 1, Run([]string{".claude/skills/local/scripts/run.sh"}, &out)) + assert.Equal(t, ".claude/skills/local/scripts/run.sh:1: ruby: banned\n", out.String()) } func TestShellAllowlistGateWiring(t *testing.T) { diff --git a/cmd/shell-allowlist/run.go b/cmd/shell-allowlist/run.go index bbce7f91..e345e62c 100644 --- a/cmd/shell-allowlist/run.go +++ b/cmd/shell-allowlist/run.go @@ -17,7 +17,9 @@ func Run(args []string, out io.Writer) int { if len(args) == 1 && args[0] == "--summary" { return summary(out) } + changed := false if len(args) == 2 && args[0] == "--changed" { + changed = true paths, err := changedFiles(args[1]) if err != nil { fmt.Fprintln(out, err) @@ -32,6 +34,9 @@ func Run(args []string, out io.Writer) int { } status := 0 for _, path := range args { + if changed && allowed.Exempt(path) { + continue + } if !checkFile(path, allowed, out) { status = 1 } diff --git a/docs/agent-skills-migration.md b/docs/agent-skills-migration.md index 29a758a1..b98cf003 100644 --- a/docs/agent-skills-migration.md +++ b/docs/agent-skills-migration.md @@ -138,7 +138,7 @@ Piece A of the merge split adds the optional `agentrepo config merge-machine KEY Machine configuration must name `gh_login` and `required_reviewer`. The latest decisive review from that reviewer must approve the exact current PR head. A comment or pending review supplies no approval, and a later change request or dismissal blocks. Use the GraphQL login without the REST `[bot]` suffix. Optional machine `required_reviewer_type` accepts `Bot` or `User`. Leaving it unset keeps login-only matching. A wrong author type can't approve the head. A GitHub App installation needs Contents read and write permission for its approval to count. [#300](https://github.com/tbhb-dev/devtools/issues/300) documents the installation permission and down-scoped review tokens. The shared reviewer CLI remains selected only through machine review settings. -Golden-file changes and detected gate loosening require an APPROVED review from optional machine `merge_owner` on the same exact head. Configure it through `dt_conf`, such as `merge_owner` in machine configuration or `DEVTOOLS_MERGE_OWNER`. Use its GraphQL login without `[bot]` too. Keep `merge_owner` a person, since an App owner override can approve its own golden-file waivers. The reviewer type setting applies only to `required_reviewer`. Repository configuration can't name that login. A push invalidates approval. +Golden-file changes and detected gate loosening require an APPROVED review from optional machine `merge_owner` on the same exact head. Configure it through `dt_conf`, such as `merge_owner` in machine configuration or `DEVTOOLS_MERGE_OWNER`. Use its GraphQL login without `[bot]` too. Keep `merge_owner` a person, since an App owner override can approve its own golden-file waivers. An exact-head APPROVED review from `merge_owner` also satisfies the `required_reviewer` gate when the required reviewer hasn't approved, and the merge report states the approving login. The reviewer type setting applies only to `required_reviewer`. Repository configuration can't name that login. A push invalidates approval. Golden findings alone can proceed without owner approval when the required App's latest decisive review approves the exact current head and supplies the header-position statement below. The format is `Loosens: yes|no`. Only `no` permits the waiver. diff --git a/docs/shell-allowlist.md b/docs/shell-allowlist.md index a417e0f1..18afce0b 100644 --- a/docs/shell-allowlist.md +++ b/docs/shell-allowlist.md @@ -14,6 +14,8 @@ Run `shell-allowlist path/to/script` to check each path supplied as an argument. The command uses the library and the allow list's `commands` entries. +Optional `exemptions` entries name exact repository paths skipped only by `shell-allowlist --changed`. They don't accept patterns. Explicit path checks and the summary still report findings for those scripts. The operator authorized the temporary exemption for `merge-agent.sh` in #1053. Remove this exemption when the Go port in #864 replaces that script. + It reports each command with its file, line, and reason. Findings and input errors return exit status 1. Clean input exits 0 without output. The analyzer ignores heredoc bodies fed to `cat`, including substitutions. It checks substitutions in other heredocs. diff --git a/internal/shell/shell.go b/internal/shell/shell.go index d1d6d0ce..f8cb0010 100644 --- a/internal/shell/shell.go +++ b/internal/shell/shell.go @@ -23,6 +23,7 @@ import ( "maps" "os" "path" + "slices" "strconv" "strings" @@ -69,12 +70,14 @@ func (f Finding) String() string { // Allowlist holds the command names that a script may run. type Allowlist struct { - allowed map[string]bool + allowed map[string]bool + exemptions []string } // allowlistFile mirrors .devtools/shell-allowlist.toml. type allowlistFile struct { - Commands []string `toml:"commands"` + Commands []string `toml:"commands"` + Exemptions []string `toml:"exemptions"` } // Load reads an allow list from file. An unknown key is an error, so a @@ -90,13 +93,19 @@ func Load(file string) (*Allowlist, error) { if decodeErr := dec.Decode(&parsed); decodeErr != nil { return nil, fmt.Errorf("parsing %s: %w", file, decodeErr) } - list := &Allowlist{allowed: map[string]bool{}} + list := &Allowlist{allowed: map[string]bool{}, exemptions: parsed.Exemptions} for _, name := range parsed.Commands { list.allowed[name] = true } return list, nil } +// Exempt reports whether file has an exact path exemption for changed-file checks. +// Check still reports every finding, including for exempt files. +func (a *Allowlist) Exempt(file string) bool { + return slices.Contains(a.exemptions, file) +} + // Check parses src as shell and returns each command it runs that the // allow list doesn't permit, in source order. file only labels the findings. func (a *Allowlist) Check(file string, src io.Reader) ([]Finding, error) { diff --git a/packages/agents/claude/.apm/skills/agent-merge-pr/SKILL.md b/packages/agents/claude/.apm/skills/agent-merge-pr/SKILL.md index 71d38ce3..58f8f23d 100644 --- a/packages/agents/claude/.apm/skills/agent-merge-pr/SKILL.md +++ b/packages/agents/claude/.apm/skills/agent-merge-pr/SKILL.md @@ -28,7 +28,7 @@ Install sibling `merge-pr` and `review-squash-message` from the same package. Th Repository behavior comes from declarative `.agents/agent-skills.conf`. -Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The owner-review label still holds the merge. +Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The same approval by `merge_owner` also satisfies the `required_reviewer` gate, and the report states the approving login. The owner-review label still holds the merge. The protected-path scan reads APM ownership from the pinned base lockfile and skips non-local deployments. It ignores full comment lines and matches exclusion keys rather than words inside values. Repository policy changes require the same owner approval. Reviewers must still identify policy changes the scan can't detect. diff --git a/packages/agents/claude/.apm/skills/agent-merge-pr/tokens.json b/packages/agents/claude/.apm/skills/agent-merge-pr/tokens.json index 954c5efa..c11dcb15 100644 --- a/packages/agents/claude/.apm/skills/agent-merge-pr/tokens.json +++ b/packages/agents/claude/.apm/skills/agent-merge-pr/tokens.json @@ -32,10 +32,10 @@ "bundled_files_tokens": 621, "generated_by": "tools/skill-tokens.sh", "manifest": { - "body_tokens": 1553, - "bytes": 5001, + "body_tokens": 1591, + "bytes": 5124, "frontmatter_tokens": 180, - "tokens": 1737 + "tokens": 1775 }, "model": "claude-opus-5", "skill": "agent-merge-pr" diff --git a/packages/agents/claude/.apm/skills/merge-pr/scripts/merge-agent.sh b/packages/agents/claude/.apm/skills/merge-pr/scripts/merge-agent.sh index e3fb86ab..79cdfa0e 100644 --- a/packages/agents/claude/.apm/skills/merge-pr/scripts/merge-agent.sh +++ b/packages/agents/claude/.apm/skills/merge-pr/scripts/merge-agent.sh @@ -106,7 +106,7 @@ ma_identity() { # Reused before review and again immediately before every merge write. ma_gates() { ma_require_gates || return 0 - local view=$1 pr=$2 base_oid=$3 final=${4:-false} head base reviewer reviewer_type label ready config key value required default dir st root encoded merge_base + local view=$1 pr=$2 base_oid=$3 final=${4:-false} head base reviewer reviewer_type owner label ready config key value required default dir st root encoded merge_base ma_context || return $? st=$(dt_state_dir) || return $? root=$(dt_root) || return $? @@ -121,7 +121,15 @@ ma_gates() { '' | Bot | User) ;; *) dt_die "required_reviewer_type must be Bot or User when set" ;; esac - "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$reviewer" --head "$head" --author-type "$reviewer_type" || dt_die "WAITING_REVIEW: $reviewer must approve the current head" + if ! "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$reviewer" --head "$head" --author-type "$reviewer_type"; then + # The operator's own exact-head approval counts as the independent review. + owner=$(ma_login merge_owner) || return $? + if [ -n "$owner" ] && "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$owner" --head "$head"; then + printf 'reviewer satisfied by merge_owner: %s approved %s\n' "$owner" "$head" + else + dt_die "WAITING_REVIEW: $reviewer or merge_owner must approve the current head" + fi + fi label=$(dt_owner_review_label) || return $? if jq -e --arg label "$label" '.labels | any(.name == $label)' "$view" >/dev/null; then dt_die "needs-owner: the PR has the owner-review label" diff --git a/packages/agents/claude/.apm/skills/merge-pr/tokens.json b/packages/agents/claude/.apm/skills/merge-pr/tokens.json index be4cc611..52919213 100644 --- a/packages/agents/claude/.apm/skills/merge-pr/tokens.json +++ b/packages/agents/claude/.apm/skills/merge-pr/tokens.json @@ -29,9 +29,9 @@ "tokens": 616 }, { - "bytes": 11068, + "bytes": 11455, "path": "scripts/merge-agent.sh", - "tokens": 5293 + "tokens": 5464 }, { "bytes": 29915, @@ -79,7 +79,7 @@ "tokens": 610 } ], - "bundled_files_tokens": 66702, + "bundled_files_tokens": 66873, "generated_by": "tools/skill-tokens.sh", "manifest": { "body_tokens": 6180, diff --git a/packages/agents/codex/.apm/skills/agent-merge-pr/SKILL.md b/packages/agents/codex/.apm/skills/agent-merge-pr/SKILL.md index 792b0b02..85a5eb1d 100644 --- a/packages/agents/codex/.apm/skills/agent-merge-pr/SKILL.md +++ b/packages/agents/codex/.apm/skills/agent-merge-pr/SKILL.md @@ -20,7 +20,7 @@ Install sibling `merge-pr` and `review-squash-message` from the same package. Th 4. Run `bash .agents/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. 5. Report the full merge commit, owner paths, any owner override and its login, and retained branches or worktrees. A failed or uncertain write requires inspection before retrying. A remote message mismatch preserves evidence and skips cleanup. -Repository behavior comes from declarative `.agents/agent-skills.conf`. Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The owner-review label still holds the merge. +Repository behavior comes from declarative `.agents/agent-skills.conf`. Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The same approval by `merge_owner` also satisfies the `required_reviewer` gate, and the report states the approving login. The owner-review label still holds the merge. The protected-path scan reads APM ownership from the pinned base lockfile and skips non-local deployments. It ignores full comment lines and matches exclusion keys rather than words inside values. Repository policy changes require the same owner approval. Reviewers must still identify policy changes the scan can't detect. diff --git a/packages/agents/codex/.apm/skills/agent-merge-pr/tokens.json b/packages/agents/codex/.apm/skills/agent-merge-pr/tokens.json index 1c8f61fd..4985f669 100644 --- a/packages/agents/codex/.apm/skills/agent-merge-pr/tokens.json +++ b/packages/agents/codex/.apm/skills/agent-merge-pr/tokens.json @@ -32,10 +32,10 @@ "bundled_files_tokens": 331, "generated_by": "tools/skill-tokens.sh", "manifest": { - "body_tokens": 933, - "bytes": 4832, + "body_tokens": 959, + "bytes": 4955, "frontmatter_tokens": 58, - "tokens": 992 + "tokens": 1018 }, "model": "gpt-5.6-sol", "skill": "agent-merge-pr", diff --git a/packages/agents/codex/.apm/skills/merge-pr/scripts/merge-agent.sh b/packages/agents/codex/.apm/skills/merge-pr/scripts/merge-agent.sh index e3fb86ab..79cdfa0e 100644 --- a/packages/agents/codex/.apm/skills/merge-pr/scripts/merge-agent.sh +++ b/packages/agents/codex/.apm/skills/merge-pr/scripts/merge-agent.sh @@ -106,7 +106,7 @@ ma_identity() { # Reused before review and again immediately before every merge write. ma_gates() { ma_require_gates || return 0 - local view=$1 pr=$2 base_oid=$3 final=${4:-false} head base reviewer reviewer_type label ready config key value required default dir st root encoded merge_base + local view=$1 pr=$2 base_oid=$3 final=${4:-false} head base reviewer reviewer_type owner label ready config key value required default dir st root encoded merge_base ma_context || return $? st=$(dt_state_dir) || return $? root=$(dt_root) || return $? @@ -121,7 +121,15 @@ ma_gates() { '' | Bot | User) ;; *) dt_die "required_reviewer_type must be Bot or User when set" ;; esac - "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$reviewer" --head "$head" --author-type "$reviewer_type" || dt_die "WAITING_REVIEW: $reviewer must approve the current head" + if ! "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$reviewer" --head "$head" --author-type "$reviewer_type"; then + # The operator's own exact-head approval counts as the independent review. + owner=$(ma_login merge_owner) || return $? + if [ -n "$owner" ] && "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$owner" --head "$head"; then + printf 'reviewer satisfied by merge_owner: %s approved %s\n' "$owner" "$head" + else + dt_die "WAITING_REVIEW: $reviewer or merge_owner must approve the current head" + fi + fi label=$(dt_owner_review_label) || return $? if jq -e --arg label "$label" '.labels | any(.name == $label)' "$view" >/dev/null; then dt_die "needs-owner: the PR has the owner-review label" diff --git a/packages/agents/codex/.apm/skills/merge-pr/tokens.json b/packages/agents/codex/.apm/skills/merge-pr/tokens.json index 6ef4c0c3..8676a496 100644 --- a/packages/agents/codex/.apm/skills/merge-pr/tokens.json +++ b/packages/agents/codex/.apm/skills/merge-pr/tokens.json @@ -24,9 +24,9 @@ "tokens": 375 }, { - "bytes": 11068, + "bytes": 11455, "path": "scripts/merge-agent.sh", - "tokens": 3217 + "tokens": 3322 }, { "bytes": 29915, @@ -74,7 +74,7 @@ "tokens": 392 } ], - "bundled_files_tokens": 40864, + "bundled_files_tokens": 40969, "generated_by": "tools/skill-tokens.sh", "manifest": { "body_tokens": 3802, diff --git a/packages/agents/source/skills/agent-merge-pr/SKILL.claude.md b/packages/agents/source/skills/agent-merge-pr/SKILL.claude.md index 71d38ce3..58f8f23d 100644 --- a/packages/agents/source/skills/agent-merge-pr/SKILL.claude.md +++ b/packages/agents/source/skills/agent-merge-pr/SKILL.claude.md @@ -28,7 +28,7 @@ Install sibling `merge-pr` and `review-squash-message` from the same package. Th Repository behavior comes from declarative `.agents/agent-skills.conf`. -Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The owner-review label still holds the merge. +Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The same approval by `merge_owner` also satisfies the `required_reviewer` gate, and the report states the approving login. The owner-review label still holds the merge. The protected-path scan reads APM ownership from the pinned base lockfile and skips non-local deployments. It ignores full comment lines and matches exclusion keys rather than words inside values. Repository policy changes require the same owner approval. Reviewers must still identify policy changes the scan can't detect. diff --git a/packages/agents/source/skills/agent-merge-pr/SKILL.codex.md b/packages/agents/source/skills/agent-merge-pr/SKILL.codex.md index 792b0b02..85a5eb1d 100644 --- a/packages/agents/source/skills/agent-merge-pr/SKILL.codex.md +++ b/packages/agents/source/skills/agent-merge-pr/SKILL.codex.md @@ -20,7 +20,7 @@ Install sibling `merge-pr` and `review-squash-message` from the same package. Th 4. Run `bash .agents/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. 5. Report the full merge commit, owner paths, any owner override and its login, and retained branches or worktrees. A failed or uncertain write requires inspection before retrying. A remote message mismatch preserves evidence and skips cleanup. -Repository behavior comes from declarative `.agents/agent-skills.conf`. Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The owner-review label still holds the merge. +Repository behavior comes from declarative `.agents/agent-skills.conf`. Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The same approval by `merge_owner` also satisfies the `required_reviewer` gate, and the report states the approving login. The owner-review label still holds the merge. The protected-path scan reads APM ownership from the pinned base lockfile and skips non-local deployments. It ignores full comment lines and matches exclusion keys rather than words inside values. Repository policy changes require the same owner approval. Reviewers must still identify policy changes the scan can't detect. diff --git a/packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh b/packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh index e3fb86ab..79cdfa0e 100644 --- a/packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh +++ b/packages/agents/source/skills/merge-pr/shared/scripts/merge-agent.sh @@ -106,7 +106,7 @@ ma_identity() { # Reused before review and again immediately before every merge write. ma_gates() { ma_require_gates || return 0 - local view=$1 pr=$2 base_oid=$3 final=${4:-false} head base reviewer reviewer_type label ready config key value required default dir st root encoded merge_base + local view=$1 pr=$2 base_oid=$3 final=${4:-false} head base reviewer reviewer_type owner label ready config key value required default dir st root encoded merge_base ma_context || return $? st=$(dt_state_dir) || return $? root=$(dt_root) || return $? @@ -121,7 +121,15 @@ ma_gates() { '' | Bot | User) ;; *) dt_die "required_reviewer_type must be Bot or User when set" ;; esac - "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$reviewer" --head "$head" --author-type "$reviewer_type" || dt_die "WAITING_REVIEW: $reviewer must approve the current head" + if ! "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$reviewer" --head "$head" --author-type "$reviewer_type"; then + # The operator's own exact-head approval counts as the independent review. + owner=$(ma_login merge_owner) || return $? + if [ -n "$owner" ] && "$_DEVTOOLS_AGENTREPO_BIN" merge approval --file "$view" --login "$owner" --head "$head"; then + printf 'reviewer satisfied by merge_owner: %s approved %s\n' "$owner" "$head" + else + dt_die "WAITING_REVIEW: $reviewer or merge_owner must approve the current head" + fi + fi label=$(dt_owner_review_label) || return $? if jq -e --arg label "$label" '.labels | any(.name == $label)' "$view" >/dev/null; then dt_die "needs-owner: the PR has the owner-review label" From 176af34fb246a610ee477320e797d539bd07f67e Mon Sep 17 00:00:00 2001 From: "tbhb.dev Agent" Date: Mon, 2 Nov 2026 11:08:30 -0500 Subject: [PATCH 2/2] docs(merge-pr): say the merge owner's approval satisfies the reviewer The unattended merge script already accepts an exact-head approval from the merge owner when the required reviewer hasn't approved. The agent-merge-pr step 4 text and the migration note still said the merge needs the required reviewer's approval. An agent following step 4 would stop at the refusal this fallback exists to avoid. Both statements describe the fallback, and the generated skill copies carry it. Assisted-by: claude:claude-sonnet-5-5 Signed-off-by: Tony Burns --- .agents/skills/agent-merge-pr/SKILL.md | 2 +- .agents/skills/agent-merge-pr/tokens.json | 6 +++--- .claude/skills/agent-merge-pr/SKILL.md | 2 +- .claude/skills/agent-merge-pr/tokens.json | 6 +++--- apm.lock.yaml | 16 ++++++++-------- docs/agent-skills-migration.md | 2 +- .../claude/.apm/skills/agent-merge-pr/SKILL.md | 2 +- .../.apm/skills/agent-merge-pr/tokens.json | 6 +++--- .../codex/.apm/skills/agent-merge-pr/SKILL.md | 2 +- .../codex/.apm/skills/agent-merge-pr/tokens.json | 6 +++--- .../source/skills/agent-merge-pr/SKILL.claude.md | 2 +- .../source/skills/agent-merge-pr/SKILL.codex.md | 2 +- 12 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.agents/skills/agent-merge-pr/SKILL.md b/.agents/skills/agent-merge-pr/SKILL.md index 85a5eb1d..76a69b49 100644 --- a/.agents/skills/agent-merge-pr/SKILL.md +++ b/.agents/skills/agent-merge-pr/SKILL.md @@ -17,7 +17,7 @@ Install sibling `merge-pr` and `review-squash-message` from the same package. Th 1. Run `bash .agents/skills/agent-merge-pr/scripts/preflight.sh `. Stop for a dirty tree, interrupted Git operation, detached head, missing machine identity or required reviewer, missing required checks, or unreadable policy. No checks means no merge. 2. Read the published description and all commits. Run `bash .agents/skills/agent-merge-pr/scripts/squash-message.sh ` for the full briefing and skeleton. Write the squash body in `SQUASH_AGENTMSG`, explaining why the branch exists. Keep the PR reference and complete trailers. Add closing references only for work this delivery completes. 3. Run `bash .agents/skills/agent-merge-pr/scripts/lint-squash-msg.sh `, then `bash .agents/skills/agent-merge-pr/scripts/review.sh `. The lint wrapper reads cspell policy from the pinned pull request head. Under the policy and approval checks, the shared review script gathers pinned Git objects and independently reviews the message. Review stamps bind the message, head, base, and repository configuration. Findings return 2. The persistent bound returns 3. Failed execution, timeout, and input drift don't spend a finding round. -4. Run `bash .agents/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. +4. Run `bash .agents/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer` or, failing that, from `merge_owner`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. 5. Report the full merge commit, owner paths, any owner override and its login, and retained branches or worktrees. A failed or uncertain write requires inspection before retrying. A remote message mismatch preserves evidence and skips cleanup. Repository behavior comes from declarative `.agents/agent-skills.conf`. Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The same approval by `merge_owner` also satisfies the `required_reviewer` gate, and the report states the approving login. The owner-review label still holds the merge. diff --git a/.agents/skills/agent-merge-pr/tokens.json b/.agents/skills/agent-merge-pr/tokens.json index 4985f669..22599be3 100644 --- a/.agents/skills/agent-merge-pr/tokens.json +++ b/.agents/skills/agent-merge-pr/tokens.json @@ -32,10 +32,10 @@ "bundled_files_tokens": 331, "generated_by": "tools/skill-tokens.sh", "manifest": { - "body_tokens": 959, - "bytes": 4955, + "body_tokens": 969, + "bytes": 4992, "frontmatter_tokens": 58, - "tokens": 1018 + "tokens": 1028 }, "model": "gpt-5.6-sol", "skill": "agent-merge-pr", diff --git a/.claude/skills/agent-merge-pr/SKILL.md b/.claude/skills/agent-merge-pr/SKILL.md index 58f8f23d..302474fd 100644 --- a/.claude/skills/agent-merge-pr/SKILL.md +++ b/.claude/skills/agent-merge-pr/SKILL.md @@ -23,7 +23,7 @@ Install sibling `merge-pr` and `review-squash-message` from the same package. Th 1. Run `bash .claude/skills/agent-merge-pr/scripts/preflight.sh `. Stop for a dirty tree, interrupted Git operation, detached head, missing machine identity or required reviewer, missing required checks, or unreadable policy. No checks means no merge. 2. Read the published description and all commits. Run `bash .claude/skills/agent-merge-pr/scripts/squash-message.sh ` for the full briefing and skeleton. Write the squash body in `SQUASH_AGENTMSG`, explaining why the branch exists. Keep the PR reference and complete trailers. Add closing references only for work this delivery completes. 3. Run `bash .claude/skills/agent-merge-pr/scripts/lint-squash-msg.sh `, then `bash .claude/skills/agent-merge-pr/scripts/review.sh `. The lint wrapper reads cspell policy from the pinned pull request head. Under the policy and approval checks, the shared review script gathers pinned Git objects and independently reviews the message. Review stamps bind the message, head, base, and repository configuration. Findings return 2. The persistent bound returns 3. Failed execution, timeout, and input drift don't spend a finding round. -4. Run `bash .claude/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. +4. Run `bash .claude/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer` or, failing that, from `merge_owner`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. 5. Report the full merge commit, owner paths, any owner override and its login, and retained branches or worktrees. A failed or uncertain write requires inspection before retrying. A remote message mismatch preserves evidence and skips cleanup. Repository behavior comes from declarative `.agents/agent-skills.conf`. diff --git a/.claude/skills/agent-merge-pr/tokens.json b/.claude/skills/agent-merge-pr/tokens.json index c11dcb15..d13175eb 100644 --- a/.claude/skills/agent-merge-pr/tokens.json +++ b/.claude/skills/agent-merge-pr/tokens.json @@ -32,10 +32,10 @@ "bundled_files_tokens": 621, "generated_by": "tools/skill-tokens.sh", "manifest": { - "body_tokens": 1591, - "bytes": 5124, + "body_tokens": 1605, + "bytes": 5161, "frontmatter_tokens": 180, - "tokens": 1775 + "tokens": 1789 }, "model": "claude-opus-5", "skill": "agent-merge-pr" diff --git a/apm.lock.yaml b/apm.lock.yaml index 920536f7..4137fa22 100644 --- a/apm.lock.yaml +++ b/apm.lock.yaml @@ -172,13 +172,13 @@ dependencies: .claude/skills/agent-commit/scripts/preflight.sh: sha256:150577c193b70fb169147eb619b8a715d37327fa7cd4b502b556cd5a87e8f0a8 .claude/skills/agent-commit/scripts/review.sh: sha256:561b69d7c0892c14deb1d1946a9ebefea817d64ff066a17dc02b8311b3711039 .claude/skills/agent-commit/tokens.json: sha256:7bb628b511e28f56a429e071bb19391c8ae1f0074b9ff7e7f3a625fc8aff7e44 - .claude/skills/agent-merge-pr/SKILL.md: sha256:52741aa96b7d995ba4b7c454de40b58d293e2c6ee5b82d2ecd304869851b1a36 + .claude/skills/agent-merge-pr/SKILL.md: sha256:942b7d08d0c9e705d057dfba6cce6e7da0fb725aea60cabd00c527c982b22060 .claude/skills/agent-merge-pr/scripts/lint-squash-msg.sh: sha256:3c5c3cf4694dbb291dada2279ffbf8312707e6025078ac0c38c2a829c60e5097 .claude/skills/agent-merge-pr/scripts/merge.sh: sha256:eed8c25ee35205e750192aa8ae6cd06cc5efd3ba8ff8adb3b8eb44fd07803142 .claude/skills/agent-merge-pr/scripts/preflight.sh: sha256:6556e7396f94e9f3241769f1f795145070a0ebdb3c411dac68decde5daff7e35 .claude/skills/agent-merge-pr/scripts/review.sh: sha256:6808c9db9267d93ff1a6eb3f205fbb80ed072ef5ccd97726efae9cc5b43e7956 .claude/skills/agent-merge-pr/scripts/squash-message.sh: sha256:5e114db1fc7354da7d5a1f94e2f124cdae950c1215763c0eb6ae6fff1eca278c - .claude/skills/agent-merge-pr/tokens.json: sha256:c3be348740f568119abb1c3972d65a489d044ebcb2142657dfe7b6ae2115a2b3 + .claude/skills/agent-merge-pr/tokens.json: sha256:5b748855a7f01d17243d61c2a1b54a3c27b4780941c8173e2ac93007e520b23c .claude/skills/agent-pr/SKILL.md: sha256:cd5015fff9c31c2211df4a845be74396db8096582ce1de43ddd4327164711426 .claude/skills/agent-pr/scripts/check.sh: sha256:d31964cbb5ae85554820983c3b6e5904790a152a17696f2cddb8b2f76d692cd3 .claude/skills/agent-pr/scripts/preflight.sh: sha256:fa23bac0cf361bd68eedd361bdf88c825f33dd512df3631fdf275069fa07dbb4 @@ -459,13 +459,13 @@ dependencies: .agents/skills/agent-commit/scripts/preflight.sh: sha256:150577c193b70fb169147eb619b8a715d37327fa7cd4b502b556cd5a87e8f0a8 .agents/skills/agent-commit/scripts/review.sh: sha256:561b69d7c0892c14deb1d1946a9ebefea817d64ff066a17dc02b8311b3711039 .agents/skills/agent-commit/tokens.json: sha256:65e686f8218b617c58725b9e7aecde4cae08a4f72a3570cba58e89bf66c173d0 - .agents/skills/agent-merge-pr/SKILL.md: sha256:6955a38d0ad61795774af14a85de05035f96ee23f83dc4a247008f7790ef97ce + .agents/skills/agent-merge-pr/SKILL.md: sha256:75c808fe46fc39b9310e90934564eb8a9a3b3262efa282baa9547aaf03681b07 .agents/skills/agent-merge-pr/scripts/lint-squash-msg.sh: sha256:3c5c3cf4694dbb291dada2279ffbf8312707e6025078ac0c38c2a829c60e5097 .agents/skills/agent-merge-pr/scripts/merge.sh: sha256:eed8c25ee35205e750192aa8ae6cd06cc5efd3ba8ff8adb3b8eb44fd07803142 .agents/skills/agent-merge-pr/scripts/preflight.sh: sha256:6556e7396f94e9f3241769f1f795145070a0ebdb3c411dac68decde5daff7e35 .agents/skills/agent-merge-pr/scripts/review.sh: sha256:6808c9db9267d93ff1a6eb3f205fbb80ed072ef5ccd97726efae9cc5b43e7956 .agents/skills/agent-merge-pr/scripts/squash-message.sh: sha256:5e114db1fc7354da7d5a1f94e2f124cdae950c1215763c0eb6ae6fff1eca278c - .agents/skills/agent-merge-pr/tokens.json: sha256:37b4895da1cacde098b0a1653a8bef75a30a8f98f710eeb179e1e5979282c0e0 + .agents/skills/agent-merge-pr/tokens.json: sha256:5a53126e35e1befd91ffbea3c414b9d18f34794a9eff64da0650f1bef88d9419 .agents/skills/agent-pr/SKILL.md: sha256:382b1590a90e3640978d57c1a740a160b62dc2c70972b7242d3aa3a7940970f1 .agents/skills/agent-pr/scripts/check.sh: sha256:d31964cbb5ae85554820983c3b6e5904790a152a17696f2cddb8b2f76d692cd3 .agents/skills/agent-pr/scripts/preflight.sh: sha256:fa23bac0cf361bd68eedd361bdf88c825f33dd512df3631fdf275069fa07dbb4 @@ -676,7 +676,7 @@ deployments: owners: - ./packages/agents/codex active_owner: ./packages/agents/codex - content_hash: sha256:6955a38d0ad61795774af14a85de05035f96ee23f83dc4a247008f7790ef97ce + content_hash: sha256:75c808fe46fc39b9310e90934564eb8a9a3b3262efa282baa9547aaf03681b07 - kind: project-relative target: antigravity value: .agents/skills/agent-merge-pr/scripts/lint-squash-msg.sh @@ -730,7 +730,7 @@ deployments: owners: - ./packages/agents/codex active_owner: ./packages/agents/codex - content_hash: sha256:37b4895da1cacde098b0a1653a8bef75a30a8f98f710eeb179e1e5979282c0e0 + content_hash: sha256:5a53126e35e1befd91ffbea3c414b9d18f34794a9eff64da0650f1bef88d9419 - kind: project-relative target: antigravity value: .agents/skills/agent-pr @@ -1972,7 +1972,7 @@ deployments: owners: - ./packages/agents/claude active_owner: ./packages/agents/claude - content_hash: sha256:52741aa96b7d995ba4b7c454de40b58d293e2c6ee5b82d2ecd304869851b1a36 + content_hash: sha256:942b7d08d0c9e705d057dfba6cce6e7da0fb725aea60cabd00c527c982b22060 - kind: project-relative target: claude value: .claude/skills/agent-merge-pr/scripts/lint-squash-msg.sh @@ -2026,7 +2026,7 @@ deployments: owners: - ./packages/agents/claude active_owner: ./packages/agents/claude - content_hash: sha256:c3be348740f568119abb1c3972d65a489d044ebcb2142657dfe7b6ae2115a2b3 + content_hash: sha256:5b748855a7f01d17243d61c2a1b54a3c27b4780941c8173e2ac93007e520b23c - kind: project-relative target: claude value: .claude/skills/agent-pr diff --git a/docs/agent-skills-migration.md b/docs/agent-skills-migration.md index b98cf003..b4a19b80 100644 --- a/docs/agent-skills-migration.md +++ b/docs/agent-skills-migration.md @@ -136,7 +136,7 @@ A successful publication records `pr.result.json` under the worktree's Git `agen Piece A of the merge split adds the optional `agentrepo config merge-machine KEY [DEFAULT]` reader. It rejects any set `DEVTOOLS_` variable for `merge_owner`, `required_reviewer`, `required_reviewer_type`, `delegation_check`, or `gh_login`. This includes empty variables and variables unrelated to the requested key. Values come from the machine's repository file, shared defaults, then the caller's fallback. Empty file values still win. No workflow calls the reader until piece B. Existing keys, environment overrides, defaults, and gates keep their behavior through the N-2 compatibility window. -Machine configuration must name `gh_login` and `required_reviewer`. The latest decisive review from that reviewer must approve the exact current PR head. A comment or pending review supplies no approval, and a later change request or dismissal blocks. Use the GraphQL login without the REST `[bot]` suffix. Optional machine `required_reviewer_type` accepts `Bot` or `User`. Leaving it unset keeps login-only matching. A wrong author type can't approve the head. A GitHub App installation needs Contents read and write permission for its approval to count. [#300](https://github.com/tbhb-dev/devtools/issues/300) documents the installation permission and down-scoped review tokens. The shared reviewer CLI remains selected only through machine review settings. +Machine configuration must name `gh_login` and `required_reviewer`. The latest decisive review from that reviewer must approve the exact current PR head, unless `merge_owner` approves that same head (see below). A comment or pending review supplies no approval, and a later change request or dismissal blocks. Use the GraphQL login without the REST `[bot]` suffix. Optional machine `required_reviewer_type` accepts `Bot` or `User`. Leaving it unset keeps login-only matching. A wrong author type can't approve the head. A GitHub App installation needs Contents read and write permission for its approval to count. [#300](https://github.com/tbhb-dev/devtools/issues/300) documents the installation permission and down-scoped review tokens. The shared reviewer CLI remains selected only through machine review settings. Golden-file changes and detected gate loosening require an APPROVED review from optional machine `merge_owner` on the same exact head. Configure it through `dt_conf`, such as `merge_owner` in machine configuration or `DEVTOOLS_MERGE_OWNER`. Use its GraphQL login without `[bot]` too. Keep `merge_owner` a person, since an App owner override can approve its own golden-file waivers. An exact-head APPROVED review from `merge_owner` also satisfies the `required_reviewer` gate when the required reviewer hasn't approved, and the merge report states the approving login. The reviewer type setting applies only to `required_reviewer`. Repository configuration can't name that login. A push invalidates approval. diff --git a/packages/agents/claude/.apm/skills/agent-merge-pr/SKILL.md b/packages/agents/claude/.apm/skills/agent-merge-pr/SKILL.md index 58f8f23d..302474fd 100644 --- a/packages/agents/claude/.apm/skills/agent-merge-pr/SKILL.md +++ b/packages/agents/claude/.apm/skills/agent-merge-pr/SKILL.md @@ -23,7 +23,7 @@ Install sibling `merge-pr` and `review-squash-message` from the same package. Th 1. Run `bash .claude/skills/agent-merge-pr/scripts/preflight.sh `. Stop for a dirty tree, interrupted Git operation, detached head, missing machine identity or required reviewer, missing required checks, or unreadable policy. No checks means no merge. 2. Read the published description and all commits. Run `bash .claude/skills/agent-merge-pr/scripts/squash-message.sh ` for the full briefing and skeleton. Write the squash body in `SQUASH_AGENTMSG`, explaining why the branch exists. Keep the PR reference and complete trailers. Add closing references only for work this delivery completes. 3. Run `bash .claude/skills/agent-merge-pr/scripts/lint-squash-msg.sh `, then `bash .claude/skills/agent-merge-pr/scripts/review.sh `. The lint wrapper reads cspell policy from the pinned pull request head. Under the policy and approval checks, the shared review script gathers pinned Git objects and independently reviews the message. Review stamps bind the message, head, base, and repository configuration. Findings return 2. The persistent bound returns 3. Failed execution, timeout, and input drift don't spend a finding round. -4. Run `bash .claude/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. +4. Run `bash .claude/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer` or, failing that, from `merge_owner`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. 5. Report the full merge commit, owner paths, any owner override and its login, and retained branches or worktrees. A failed or uncertain write requires inspection before retrying. A remote message mismatch preserves evidence and skips cleanup. Repository behavior comes from declarative `.agents/agent-skills.conf`. diff --git a/packages/agents/claude/.apm/skills/agent-merge-pr/tokens.json b/packages/agents/claude/.apm/skills/agent-merge-pr/tokens.json index c11dcb15..d13175eb 100644 --- a/packages/agents/claude/.apm/skills/agent-merge-pr/tokens.json +++ b/packages/agents/claude/.apm/skills/agent-merge-pr/tokens.json @@ -32,10 +32,10 @@ "bundled_files_tokens": 621, "generated_by": "tools/skill-tokens.sh", "manifest": { - "body_tokens": 1591, - "bytes": 5124, + "body_tokens": 1605, + "bytes": 5161, "frontmatter_tokens": 180, - "tokens": 1775 + "tokens": 1789 }, "model": "claude-opus-5", "skill": "agent-merge-pr" diff --git a/packages/agents/codex/.apm/skills/agent-merge-pr/SKILL.md b/packages/agents/codex/.apm/skills/agent-merge-pr/SKILL.md index 85a5eb1d..76a69b49 100644 --- a/packages/agents/codex/.apm/skills/agent-merge-pr/SKILL.md +++ b/packages/agents/codex/.apm/skills/agent-merge-pr/SKILL.md @@ -17,7 +17,7 @@ Install sibling `merge-pr` and `review-squash-message` from the same package. Th 1. Run `bash .agents/skills/agent-merge-pr/scripts/preflight.sh `. Stop for a dirty tree, interrupted Git operation, detached head, missing machine identity or required reviewer, missing required checks, or unreadable policy. No checks means no merge. 2. Read the published description and all commits. Run `bash .agents/skills/agent-merge-pr/scripts/squash-message.sh ` for the full briefing and skeleton. Write the squash body in `SQUASH_AGENTMSG`, explaining why the branch exists. Keep the PR reference and complete trailers. Add closing references only for work this delivery completes. 3. Run `bash .agents/skills/agent-merge-pr/scripts/lint-squash-msg.sh `, then `bash .agents/skills/agent-merge-pr/scripts/review.sh `. The lint wrapper reads cspell policy from the pinned pull request head. Under the policy and approval checks, the shared review script gathers pinned Git objects and independently reviews the message. Review stamps bind the message, head, base, and repository configuration. Findings return 2. The persistent bound returns 3. Failed execution, timeout, and input drift don't spend a finding round. -4. Run `bash .agents/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. +4. Run `bash .agents/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer` or, failing that, from `merge_owner`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. 5. Report the full merge commit, owner paths, any owner override and its login, and retained branches or worktrees. A failed or uncertain write requires inspection before retrying. A remote message mismatch preserves evidence and skips cleanup. Repository behavior comes from declarative `.agents/agent-skills.conf`. Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The same approval by `merge_owner` also satisfies the `required_reviewer` gate, and the report states the approving login. The owner-review label still holds the merge. diff --git a/packages/agents/codex/.apm/skills/agent-merge-pr/tokens.json b/packages/agents/codex/.apm/skills/agent-merge-pr/tokens.json index 4985f669..22599be3 100644 --- a/packages/agents/codex/.apm/skills/agent-merge-pr/tokens.json +++ b/packages/agents/codex/.apm/skills/agent-merge-pr/tokens.json @@ -32,10 +32,10 @@ "bundled_files_tokens": 331, "generated_by": "tools/skill-tokens.sh", "manifest": { - "body_tokens": 959, - "bytes": 4955, + "body_tokens": 969, + "bytes": 4992, "frontmatter_tokens": 58, - "tokens": 1018 + "tokens": 1028 }, "model": "gpt-5.6-sol", "skill": "agent-merge-pr", diff --git a/packages/agents/source/skills/agent-merge-pr/SKILL.claude.md b/packages/agents/source/skills/agent-merge-pr/SKILL.claude.md index 58f8f23d..302474fd 100644 --- a/packages/agents/source/skills/agent-merge-pr/SKILL.claude.md +++ b/packages/agents/source/skills/agent-merge-pr/SKILL.claude.md @@ -23,7 +23,7 @@ Install sibling `merge-pr` and `review-squash-message` from the same package. Th 1. Run `bash .claude/skills/agent-merge-pr/scripts/preflight.sh `. Stop for a dirty tree, interrupted Git operation, detached head, missing machine identity or required reviewer, missing required checks, or unreadable policy. No checks means no merge. 2. Read the published description and all commits. Run `bash .claude/skills/agent-merge-pr/scripts/squash-message.sh ` for the full briefing and skeleton. Write the squash body in `SQUASH_AGENTMSG`, explaining why the branch exists. Keep the PR reference and complete trailers. Add closing references only for work this delivery completes. 3. Run `bash .claude/skills/agent-merge-pr/scripts/lint-squash-msg.sh `, then `bash .claude/skills/agent-merge-pr/scripts/review.sh `. The lint wrapper reads cspell policy from the pinned pull request head. Under the policy and approval checks, the shared review script gathers pinned Git objects and independently reviews the message. Review stamps bind the message, head, base, and repository configuration. Findings return 2. The persistent bound returns 3. Failed execution, timeout, and input drift don't spend a finding round. -4. Run `bash .claude/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. +4. Run `bash .claude/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer` or, failing that, from `merge_owner`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. 5. Report the full merge commit, owner paths, any owner override and its login, and retained branches or worktrees. A failed or uncertain write requires inspection before retrying. A remote message mismatch preserves evidence and skips cleanup. Repository behavior comes from declarative `.agents/agent-skills.conf`. diff --git a/packages/agents/source/skills/agent-merge-pr/SKILL.codex.md b/packages/agents/source/skills/agent-merge-pr/SKILL.codex.md index 85a5eb1d..76a69b49 100644 --- a/packages/agents/source/skills/agent-merge-pr/SKILL.codex.md +++ b/packages/agents/source/skills/agent-merge-pr/SKILL.codex.md @@ -17,7 +17,7 @@ Install sibling `merge-pr` and `review-squash-message` from the same package. Th 1. Run `bash .agents/skills/agent-merge-pr/scripts/preflight.sh `. Stop for a dirty tree, interrupted Git operation, detached head, missing machine identity or required reviewer, missing required checks, or unreadable policy. No checks means no merge. 2. Read the published description and all commits. Run `bash .agents/skills/agent-merge-pr/scripts/squash-message.sh ` for the full briefing and skeleton. Write the squash body in `SQUASH_AGENTMSG`, explaining why the branch exists. Keep the PR reference and complete trailers. Add closing references only for work this delivery completes. 3. Run `bash .agents/skills/agent-merge-pr/scripts/lint-squash-msg.sh `, then `bash .agents/skills/agent-merge-pr/scripts/review.sh `. The lint wrapper reads cspell policy from the pinned pull request head. Under the policy and approval checks, the shared review script gathers pinned Git objects and independently reviews the message. Review stamps bind the message, head, base, and repository configuration. Findings return 2. The persistent bound returns 3. Failed execution, timeout, and input drift don't spend a finding round. -4. Run `bash .agents/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. +4. Run `bash .agents/skills/agent-merge-pr/scripts/merge.sh `. The shared merger requires the stamped inputs, a current exact-head approval from machine `required_reviewer` or, failing that, from `merge_owner`, successful required checks, readiness, and session pre-approval or machine delegation for `merge`. It checks live policy and authority again after delegation and changing the base branches of dependent pull requests. It never uses administrator or automatic merge. 5. Report the full merge commit, owner paths, any owner override and its login, and retained branches or worktrees. A failed or uncertain write requires inspection before retrying. A remote message mismatch preserves evidence and skips cleanup. Repository behavior comes from declarative `.agents/agent-skills.conf`. Authorities come only from machine configuration: `gh_login`, `required_reviewer`, optional `required_reviewer_type`, optional `merge_owner`, reviewer selection, and delegation. Golden files and detected gate loosening require an APPROVED review from `merge_owner` on the exact current head. A later push invalidates it. The same approval by `merge_owner` also satisfies the `required_reviewer` gate, and the report states the approving login. The owner-review label still holds the merge.