chore(#358): wave-2 cleanup bundle — drop allowed_tools_override + push-gate scope + multi-line drift#364
Conversation
PR #357 review caught that the agent-routing v1 schema advertised `allowed_tools_override` in three places — apply-agent-routing.sh's parser doc comment, agent-routing.yaml.example's schema preamble + Example F worked-example, and docs/multi-project.md's adopter schema table — but parse_routing() never emits the field, so adopters who set it get a silent no-op. Decision (recorded per #358 AC): DROP the field for v1, do NOT wire the parser branch. Rationale: - The full implementation needs parser emission (3 parsers) + a consumer-side `tools:` / `allowed-tools:` line rewrite + drift-guard awareness (tools-line differs from dev would need its own escape hatch). 50-100 line impl for a feature the docs explicitly mark as "advanced — use sparingly." - Adopters who actually need a per-fork tool-list override can edit the agent file directly with a `# routing-config:override <reason>` comment in YAML frontmatter — the same escape-hatch pattern that already handles model: overrides. Documented in the now-deprecation prose. - No migration tail: adopters who'd set `allowed_tools_override` were already getting a silent no-op; this PR makes that explicit in the schema docs. Files changed: - agent-routing.yaml.example - Schema preamble: replace the field's doc paragraph with a deprecation note pointing at the agent-file + escape-hatch path - Example F (the only worked example for the field): removed - .claude/hooks/apply-agent-routing.sh:115 - Parser doc comment: removed from the key-set list, added a one-line deprecation note pointing readers at the per-agent frontmatter path - docs/multi-project.md:549 - Schema table: removed the `allowed_tools_override` row - New post-table paragraph documenting the deprecation + the operator-facing escape-hatch alternative Closes #358 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ntation PR #357 review flagged a doc/impl mismatch: AgDR-0050 § Axis 4 said the pre-push guard "sweeps before any push to a public-class remote," but block-agent-routing-drift.sh fires on ANY push regardless of remote class. Two possible fixes per #359: tighten the hook to check remote class, OR update the AgDR to match the broader-scope impl. Decision: UPDATE the AgDR (option 2, recommended in the ticket). Rationale: - The broader scope is actually the right design. Even a push to a private tracking branch can become a leak vector later if the operator changes the remote. Adopters staging a deliberate framework-default change should label it with the # routing-config:override escape hatch before pushing anywhere — not just before pushing to upstream. - Tightening the hook to inspect @{push} remote class would add code and a new failure mode (silent-pass on remote-resolution failure) without removing a real failure surface. Files changed: - docs/agdr/AgDR-0050-agent-runtime-overhaul.md § Axis 4 - Prose changed: "sweeps before any push to a public-class remote" → "sweeps before ANY push (not just to public-class remotes)" - New blockquote note explaining the resolution + linking to #359 Closes #359 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nts.sh #342 § 2(b) flagged that test_site_counts.sh's per-line grep can't catch claims where the digit and noun straddle a line break (the shape that bit `site/llms-full.txt:131-133` before #360 cleaned it up by hand). This commit adds the missing pass per #342's prescription. Implementation: - New check_multiline_count() helper. Flattens consecutive whitespace (including newlines) via `tr '\n' ' ' | tr -s '[:space:]' ' '`, re-scans for `<digits> +<noun>`, dedupes against the per-line pass's hits (so a same-line claim already reported by check_count doesn't get double-counted), reports drift as `file:multiline` (no line number — the digit and noun aren't on the same line). - Second for-loop calls the new helper for the same noun-pattern list as the per-line check_count loop. - Self-test fixture at the end: synthesises a temp file with the exact `52\n slash commands` shape #342 § 2(b) describes, runs check_multiline_count against it inside `$(...)` (subshell-isolated so its DRIFT++ stays scoped), asserts the expected `DRIFT: file:multiline — claims 52 slash commands` output line. If absent, the detector is broken and the suite fails. Net coverage: the per-line + multi-line passes now together cover both same-line and cross-line drift, closing #342's § 2(b) gap. § 2(a)'s remaining synonym patterns (`enforcement scripts?` and `mechanical (enforcement )?(scripts?|hooks?)`) are NOT addressed here — they're additional noun-pattern entries, not a structural fix. Closing the structural gap is the higher-value half; remaining noun patterns can be added incrementally as future drift surfaces them. Files changed: - .claude/hooks/tests/test_site_counts.sh: - new check_multiline_count() function (~30 lines) - new for-loop applying it to the same 9 noun patterns as check_count - new self-test at end of file (~15 lines) that proves the detector fires on a known-bad cross-line fixture Test passes locally against current site/ files (which were swept clean post-#360). Sibling tests (test_agent_routing_sync_and_drift.sh 8/8, test_agent_wrap_shape.sh 13+5+19/37) unchanged + still pass. Closes #342 § 2(b). § 2(a) noun-pattern additions and § 1 one-time hygiene sweep are intentional carry-overs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
atlas-apex
left a comment
There was a problem hiding this comment.
Code Review: PR #364
Commit: 8f3638b5b7ebd3249c6f1851711983ebfa80ca7a
Summary
Wave-2 cleanup bundle — three P3 follow-ups surfaced by Rex non-blocking notes during the Wave 2 work, each on its own commit, each closing a single ticket:
440c19a— drops the advertised-but-not-wiredallowed_tools_overridefield fromagent-routing.yaml.example+apply-agent-routing.shparser doc +docs/multi-project.mdschema table. Decision: DROP for v1 (closes #358).9a02e94— reconciles AgDR-0050 § Axis 4's push-gate scope prose with the shipped impl (broader-scope is the design intent). Closes #359.8f3638b— addscheck_multiline_count()+ subshell-isolated self-test totest_site_counts.shto catch cross-line drift. Closes #342 § 2(b).
PR body carries <!-- multi-close: approved --> for the 3-ticket closure.
Checklist Results
- Architecture & Design: PASS (small surgical edits, no architectural shifts)
- Code Quality: PASS (shell idioms idiomatic; helper is small and focused)
- Testing: PASS (the test commit IS the test; self-test fires correctly — verified locally)
- Security: N/A (no auth/crypto/secrets surface touched)
- Performance: N/A (file scanner; flatten-then-scan is O(file size))
- PR Description & Glossary: PASS (glossary explains marker, DROP vs add-parser-branch, push-gate scope, multi-line lookback, subshell-isolated self-test)
- Summary Bullet Narrative: PASS (each bullet carries verb + >6 words + rationale; no label-only bullets)
- Technical Decisions (AgDR): PASS (no new technical decisions; #358's DROP rationale is articulated in the commit message and ticket; #359 is an AgDR reconciliation, not a new decision)
- Adopter Handbooks: PASS (
general/commit-message-quality.mdloaded — commit bodies are substantive and explain WHY; other handbooks not triggered by this diff)
Issues Found
None blocking.
Verification trace (per brief's focus list)
1. #358 — DROP completeness. grep -rn "allowed_tools_override" against the framework returns exactly two hits, both deliberate deprecation prose:
.claude/hooks/apply-agent-routing.sh:116— parser doc comment explaining the drop + pointing readers at.claude/agents/<name>.mdfrontmatter as the source of truth.docs/multi-project.md:552— schema-table follow-up paragraph naming the# routing-config:override <reason>escape-hatch alternative.
agent-routing.yaml.example schema preamble is rewritten with the same deprecation note + escape-hatch pointer, and Example F is fully removed. No live allowed_tools_override reference remains. The grep answer matches the brief's prediction.
2. #358 — DROP rationale. The 50-100 line estimate is plausible — the existing model: field is the proof of what a per-agent override entails: parser emission across the routing parser + a consumer-side frontmatter rewrite (the parser already emits name\tkey\tvalue tuples, but a tools: rewrite is a list, not a scalar, so the consumer needs list-merge semantics that the scalar model: rewriter doesn't have) + drift-guard awareness for tools: differences in block-agent-routing-drift.sh. The "advanced — use sparingly" docs framing pre-existed and the # routing-config:override escape hatch already exists for the genuinely-needed one-offs. DROP is the right call — an advertised-but-silently-no-op field is a worse trap than no field at all.
3. #359 — AgDR-0050 prose change. The new blockquote inside § Axis 4 reads as a coherent addendum to the table-line edit ("pre-push sweeps the same surface before ANY push (not just to public-class remotes)"). The blockquote correctly explains WHY the broader scope is the design intent: private-tracking-branch can still become a leak vector if the remote later changes, and the # routing-config:override escape-hatch labelling is needed before pushing anywhere. No contradiction with the table line. Reads as an annotation that closes the doc-vs-impl gap that #359 named.
4. #342 — check_multiline_count correctness.
- Flatten logic:
tr '\n' ' ' | tr -s '[:space:]' ' 'is the canonical cross-line fold. Firsttrswaps newlines for spaces, secondtr -scollapses runs of whitespace into single spaces. Correct. - Dedupe logic: empirically validated. With
seen_same_line="52|53|"(trailing|fromtr '\n' '|'), the case match*"|$num|"*against"|$seen_same_line"(so"|52|53|") correctly matches bothnum=52(position 0) andnum=53(position 3) and correctly rejectsnum=54/num=99. No off-by-one. The leading|in the case-input plus the trailing|fromtrtogether guarantee every reported number is sandwiched by literal|separators. - Small-number heuristic (
[ "$num" -lt 10 ]): byte-identical to the same pre-emption incheck_count(lines 100-103 of the original). Consistent.
5. #342 — self-test design. The assertion regex DRIFT:.*:multiline — claims 52 slash commands is tight enough to catch all three plausible regression shapes:
- Locus suffix rename (e.g.
:multiline→:multi_line) — the regex literal would not match. - Wrong number (detector reports
0or a stale count) — the52literal would not match. - Label drift (e.g. helper passing
slash_commandsinstead ofslash commands) — the literalslash commandswould not match.
A future operator debugging a failed self-test gets the helpful output was: <actual> echo, which surfaces both the case the detector produced AND its absence — enough to root-cause without re-reading the helper.
Subshell isolation via $(check_multiline_count ...) is correct — the helper's DRIFT=$((DRIFT + 1)) mutates the subshell's DRIFT only, and the outer suite's count stays clean. If the self-test itself fails to find the expected line, the outer DRIFT=$((DRIFT + 1)) increments and fails the suite. Good.
I ran bash .claude/hooks/tests/test_site_counts.sh locally on the head commit: PASS at 53 skills / 31 hooks / 19 roles, multiline self-test fires on the synthetic fixture, real site/* files are clean.
6. Multi-close marker. The hook at .claude/hooks/validate-pr-create.sh:319 defaults to MULTI_CLOSE_SKIP="<!-- multi-close: approved -->" — byte-identical to what the PR body carries. The marker check (line 372) uses grep -qF -- "$MULTI_CLOSE_SKIP" against the code-fence-stripped body. The PR's marker is at the very top of the body, outside any fence, so it survives the strip. All three target tickets verified OPEN via gh issue view:
- #358:
[Chore] allowed_tools_override field — schema/impl drift in agent-routing v1— OPEN - #359:
[Chore] AgDR-0050 push-gate scope — reconcile any-push impl vs public-class-remote spec— OPEN - #342:
[Chore] test_site_counts.sh — close synonym-coverage + multiline gaps flagged on PR #340— OPEN
7. Scope discipline. git log dev..HEAD --stat shows exactly the 5 files the brief listed: agent-routing.yaml.example, .claude/hooks/apply-agent-routing.sh, .claude/hooks/tests/test_site_counts.sh, docs/agdr/AgDR-0050-agent-runtime-overhaul.md, docs/multi-project.md. No drive-by edits to CLAUDE.md, site/, AGENTS.md, or unrelated areas. Each commit's diff stays inside the surface its ticket owns.
8. CI. All five checks green at HEAD: lychee (Link Check), markdownlint-cli2, Verify Ticket ID (PR Title Check), shellcheck .claude/hooks, site-counts drift detection.
Suggestions (non-blocking)
- The new
check_multiline_countnoun_patternlist mirrors thecheck_countlist. A future refactor could extract the noun-pattern tuples into a single array and iterate twice (once per-line, once flattened) to keep them DRY — but for #342's surgical close, the current shape is fine and the diff is easy to review. Not worth churning this PR.
Verdict
APPROVED
Three independent surgical follow-ups, each with sound rationale, each correctly implemented, all tests green, scope clean, prose addenda coherent. The <!-- multi-close: approved --> marker is the right tool for this bundle shape — these are demonstrably P3 residue-cleanup tickets that don't deserve three separate PRs.
Operator: per the sandbox workaround memory, please write the approval marker on my behalf at <ops_fork_root>/.claude/session/reviews/364-rex.approved containing exactly:
8f3638b5b7ebd3249c6f1851711983ebfa80ca7a
(bare 40-char SHA + newline, no labels/JSON/extra text).
🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: 8f3638b5b7ebd3249c6f1851711983ebfa80ca7a
…sh-gate scope + multi-line drift (#364) * chore(#358): drop advertised-but-not-wired allowed_tools_override field PR #357 review caught that the agent-routing v1 schema advertised `allowed_tools_override` in three places — apply-agent-routing.sh's parser doc comment, agent-routing.yaml.example's schema preamble + Example F worked-example, and docs/multi-project.md's adopter schema table — but parse_routing() never emits the field, so adopters who set it get a silent no-op. Decision (recorded per #358 AC): DROP the field for v1, do NOT wire the parser branch. Rationale: - The full implementation needs parser emission (3 parsers) + a consumer-side `tools:` / `allowed-tools:` line rewrite + drift-guard awareness (tools-line differs from dev would need its own escape hatch). 50-100 line impl for a feature the docs explicitly mark as "advanced — use sparingly." - Adopters who actually need a per-fork tool-list override can edit the agent file directly with a `# routing-config:override <reason>` comment in YAML frontmatter — the same escape-hatch pattern that already handles model: overrides. Documented in the now-deprecation prose. - No migration tail: adopters who'd set `allowed_tools_override` were already getting a silent no-op; this PR makes that explicit in the schema docs. Files changed: - agent-routing.yaml.example - Schema preamble: replace the field's doc paragraph with a deprecation note pointing at the agent-file + escape-hatch path - Example F (the only worked example for the field): removed - .claude/hooks/apply-agent-routing.sh:115 - Parser doc comment: removed from the key-set list, added a one-line deprecation note pointing readers at the per-agent frontmatter path - docs/multi-project.md:549 - Schema table: removed the `allowed_tools_override` row - New post-table paragraph documenting the deprecation + the operator-facing escape-hatch alternative Closes #358 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(#359): reconcile AgDR-0050 § Axis 4 push-gate scope with implementation PR #357 review flagged a doc/impl mismatch: AgDR-0050 § Axis 4 said the pre-push guard "sweeps before any push to a public-class remote," but block-agent-routing-drift.sh fires on ANY push regardless of remote class. Two possible fixes per #359: tighten the hook to check remote class, OR update the AgDR to match the broader-scope impl. Decision: UPDATE the AgDR (option 2, recommended in the ticket). Rationale: - The broader scope is actually the right design. Even a push to a private tracking branch can become a leak vector later if the operator changes the remote. Adopters staging a deliberate framework-default change should label it with the # routing-config:override escape hatch before pushing anywhere — not just before pushing to upstream. - Tightening the hook to inspect @{push} remote class would add code and a new failure mode (silent-pass on remote-resolution failure) without removing a real failure surface. Files changed: - docs/agdr/AgDR-0050-agent-runtime-overhaul.md § Axis 4 - Prose changed: "sweeps before any push to a public-class remote" → "sweeps before ANY push (not just to public-class remotes)" - New blockquote note explaining the resolution + linking to #359 Closes #359 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(#342): add multi-line lookback pass + self-test to test_site_counts.sh #342 § 2(b) flagged that test_site_counts.sh's per-line grep can't catch claims where the digit and noun straddle a line break (the shape that bit `site/llms-full.txt:131-133` before #360 cleaned it up by hand). This commit adds the missing pass per #342's prescription. Implementation: - New check_multiline_count() helper. Flattens consecutive whitespace (including newlines) via `tr '\n' ' ' | tr -s '[:space:]' ' '`, re-scans for `<digits> +<noun>`, dedupes against the per-line pass's hits (so a same-line claim already reported by check_count doesn't get double-counted), reports drift as `file:multiline` (no line number — the digit and noun aren't on the same line). - Second for-loop calls the new helper for the same noun-pattern list as the per-line check_count loop. - Self-test fixture at the end: synthesises a temp file with the exact `52\n slash commands` shape #342 § 2(b) describes, runs check_multiline_count against it inside `$(...)` (subshell-isolated so its DRIFT++ stays scoped), asserts the expected `DRIFT: file:multiline — claims 52 slash commands` output line. If absent, the detector is broken and the suite fails. Net coverage: the per-line + multi-line passes now together cover both same-line and cross-line drift, closing #342's § 2(b) gap. § 2(a)'s remaining synonym patterns (`enforcement scripts?` and `mechanical (enforcement )?(scripts?|hooks?)`) are NOT addressed here — they're additional noun-pattern entries, not a structural fix. Closing the structural gap is the higher-value half; remaining noun patterns can be added incrementally as future drift surfaces them. Files changed: - .claude/hooks/tests/test_site_counts.sh: - new check_multiline_count() function (~30 lines) - new for-loop applying it to the same 9 noun patterns as check_count - new self-test at end of file (~15 lines) that proves the detector fires on a known-bad cross-line fixture Test passes locally against current site/ files (which were swept clean post-#360). Sibling tests (test_agent_routing_sync_and_drift.sh 8/8, test_agent_wrap_shape.sh 13+5+19/37) unchanged + still pass. Closes #342 § 2(b). § 2(a) noun-pattern additions and § 1 one-time hygiene sweep are intentional carry-overs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Three small Wave-2 cleanup follow-ups — all P3, all surfaced by Rex non-blocking notes during the wave, all small enough to bundle into one PR with the
<!-- multi-close: approved -->marker. Each fix is a single commit and closes a single ticket:440c19a—chore(#358): drop advertised-but-not-wired allowed_tools_override field— the v1 agent-routing schema advertisedallowed_tools_overridein three places (apply-agent-routing.sh parser doc comment,agent-routing.yaml.exampleschema preamble + Example F,docs/multi-project.md:549adopter schema table), butparse_routing()never emitted the field. Adopters who set it got a silent no-op. Decision per [Chore] allowed_tools_override field — schema/impl drift in agent-routing v1 #358 AC: DROP for v1 rather than build the parser branch — the full impl needs parser emission across 3 parsers + a consumer-sidetools:line rewrite + drift-guard awareness fortools:differences, which is 50-100 lines for a feature the docs explicitly mark "advanced — use sparingly". Adopters with a real per-fork tool-list override need can edit the agent file directly with a# routing-config:override <reason>comment in YAML frontmatter — the same escape-hatch pattern that already handlesmodel:overrides. Closes [Chore] allowed_tools_override field — schema/impl drift in agent-routing v1 #358.9a02e94—docs(#359): reconcile AgDR-0050 § Axis 4 push-gate scope with implementation— AgDR-0050 § Axis 4 said the pre-push guard "sweeps before any push to a public-class remote," butblock-agent-routing-drift.shfires on ANY push regardless of remote class. Decision per [Chore] AgDR-0050 push-gate scope — reconcile any-push impl vs public-class-remote spec #359 AC: update the AgDR to match the impl (option 2, recommended in the ticket) rather than tighten the hook. The broader scope is the right design — even a push to a private tracking branch can leak later if the remote changes, and adopters staging a deliberate framework-default change should label it with the escape-hatch comment before pushing anywhere. Tightening the hook would add code + a new failure mode (silent-pass on remote-resolution failure) without removing a real failure surface. Closes [Chore] AgDR-0050 push-gate scope — reconcile any-push impl vs public-class-remote spec #359.8f3638b—test(#342): add multi-line lookback pass + self-test to test_site_counts.sh— [Chore] test_site_counts.sh — close synonym-coverage + multiline gaps flagged on PR #340 #342 § 2(b) flagged that the per-line grep can't catch claims where the digit and noun straddle a line break (the exact shape that bitsite/llms-full.txt:131-133before feat(#347)!: Hatim→Hakim consolidation + security + data sub-agents (Wave 2 PR 3) #360 cleaned it up by hand). Newcheck_multiline_count()helper flattens consecutive whitespace viatr '\n' ' ' | tr -s '[:space:]' ' ', re-scans for<digits> +<noun>, dedupes against the per-line pass to avoid double-counting, and reports drift asfile:multiline. Self-test fixture at end of file synthesises a known-bad52\n slash commandsshape (the exact shape [Chore] test_site_counts.sh — close synonym-coverage + multiline gaps flagged on PR #340 #342 names), runs the detector against it inside$(...)for subshell isolation, asserts the expected output line. If the detector breaks, the self-test fails the suite. Closes [Chore] test_site_counts.sh — close synonym-coverage + multiline gaps flagged on PR #340 #342 § 2(b). § 2(a) (other noun-pattern synonyms) and § 1 (one-time hygiene sweep) are intentional carry-overs.Testing
bash .claude/hooks/tests/test_site_counts.sh— PASS at 53 skills / 31 hooks / 19 roles, multiline self-test fires on the synthetic fixture, realsite/*files are clean.bash .claude/hooks/tests/test_agent_routing_sync_and_drift.sh— PASS at 8/8 (sanity check that the apply-agent-routing.sh comment-only change didn't regress anything).bash .claude/agents/tests/test_agent_wrap_shape.sh— PASS at 13 ROLE_AGENTS + 5 UTILITY_AGENTS + 19 ROLE_CLASSES.12 sub-agentsacross a line wrap), confirm the new multi-line pass surfaces it withDRIFT: site/llms-full.txt:multiline — claims 12 sub-agents (across newline), actual is 23. Not blocking — covered by the self-test.Glossary
<!-- multi-close: approved -->markerCloses #Nper PR (enforced byvalidate-pr-create.sh); the marker explicitly allows N>1 for legitimately bundled work. Used here because all 3 follow-ups are P3 cleanup chores from the same Wave 2 review residue.<digits>\n <noun>patterns the per-line pass misses. Reportsfile:multilineas the locus when triggered.$(function ...)runs the entire function in a subshell. Any state mutations inside (e.g.DRIFT++) stay in the subshell. Lets the self-test exercise the detector without polluting the outer DRIFT count.Closes #358
Closes #359
Closes #342
Refs PR #357 (the wave PR that surfaced #358 + #359 + #342's partial closure)