Skip to content

Routing guard refusal quotes the words it matched - #489

Open
EdbertChan wants to merge 5 commits into
mainfrom
plan/routing-guard-refusal-quotes-the-words-it-matched
Open

Routing guard refusal quotes the words it matched#489
EdbertChan wants to merge 5 commits into
mainfrom
plan/routing-guard-refusal-quotes-the-words-it-matched

Conversation

@EdbertChan

@EdbertChan EdbertChan commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Summary

A checker blocks local workers from publishing code when a managed workflow should run the work instead.

Its refusal showed only broad action labels. Authors could not see which words caused the refusal.

The checker discarded the matched text after it classified each action.

It now includes each match and nearby text in the refusal. Tests preserve the current decisions for past-work and non-code wording.

Review Claim

Approve refusals that show each matched word with nearby text without changing which requests the checker blocks.

Review Lane

behavior

Review Unit

engine-runtime

Safety Invariant

The checker uses the same matching rules and block-or-allow decision. Only the refusal text changes.

Assumptions: This safety invariant is unconfirmed. The final-only output requirement prevents a confirmation question.

Slice Rationale

This slice changes one refusal message and its data shape. A separate change can check the command that runs instead of request wording.

Non-goals

  • No change to which requests are blocked or allowed.
  • No new action words or changes to noun, negation, or hyphen handling.
  • No change to the workflow advice in the refusal.

Architecture

Before

flowchart LR
    A["Worker request"] --> B["Classify publication actions"]
    B --> C["Action labels"]
    C --> D["Format refusal"]
Loading

After

flowchart LR
    A["Worker request"] --> B["Classify publication actions"]
    B --> C["Action labels, matched text, and context"]
    C --> D["Format refusal"]
Loading

Test Plan

Test Plan
  • python3 engine/skills/make-pr/scripts/preflight.py --base origin/main — preflight passed for the engine runtime unit.
  • python3 -m unittest discover -s engine/hooks/agent-routing-guard/tests -v && python3 scripts/check_hook_test_coverage.py engine/hooks/agent-routing-guard — 31 tests passed, and hook coverage passed.
  • python3 scripts/check_no_new_comments.py --base origin/main — no new comments found.
  • bash scripts/scrub-handoff-artifacts.sh — no handoff artifacts found.
  • node scripts/validate-pr-body-local.mjs --body-file /tmp/routing-guard-refusal-pr.md --base main — PR body validation passed.

Revert Plan

Revert Plan
  • Safe to revert? Yes.
  • Revert command: git revert <merge-commit-sha>
  • Post-revert steps: None.
  • Data migration? No.

Note

Low Risk
Hook decision logic is untouched; only refusal formatting and the internal hit type change, with tests asserting unchanged classifications.

Overview
When the agent-routing-guard blocks an Agent spawn for publication wording, refusals now quote what matched, not only verb labels like commit, push.

publication_verbs returns PublicationVerbHit records (label, matched substring, ±40 char context). block_message and unchecked_message insert {hits} lines such as commit: "…" before the existing routing guidance. Block vs allow logic is unchanged—same regex and negation/noun rules; only stderr copy is richer.

Tests add publication_labels for label-only assertions, lock in a few edge-case verdicts, and assert each hit’s matched_text and window appear in the entrypoint refusal.

Reviewed by Cursor Bugbot for commit 58ebfaa. Bugbot is set up for automated code reviews on this repo. Configure here.

Invoker and others added 5 commits September 12, 2026 07:03
…laim: the routing guard's refusal quotes each matched verb with its surrounding words, and tests pin its verdict for narrated past work and for the non-git sense of "merge".

Review lane: behavior
Safety invariant: Refuse-or-allow behavior is unchanged; only the message gains detail. Tests assert the current verdicts rather than changing them.
Effectiveness measurement: A test asserts each matched span appears verbatim in the refusal; removing the span from the message fails it.
Slice rationale: One claim about one file's output. Replacing prompt matching with a check on the command actually run is a separate, already-open change.
Architectural effect: The detector's return value carries the matched text alongside the label, and the message formatter prints it.
Goal: Make a refusal say what it matched.
Motivation: The author guessed twice about why a spawn was refused, and both guesses were wrong, because the refusal named only verb labels.
Alternative considerations: Landing the open change that gates the publishing act instead of the prompt text is the structural fix and is preferred; this slice is the smaller one that helps whatever remains. Widening the negation or noun classes was rejected: the verdicts here are arguably correct, and the real defect is that the author could not see them.
Implementation details: In engine/hooks/agent-routing-guard/detect.py, have the verb-collecting function return each hit as a label plus the matched text plus a short window of surrounding characters, and have the refusal formatter list them one per line; keep the existing decision logic, exit codes, and the summary wording that names the skill to follow. Update every caller and test that consumes the old shape. Add fixtures in engine/hooks/agent-routing-guard/tests/test_hooks.py for a narrated past-tense sentence about a force-push, for "merge" used about findings, and for a sentence with no publication verb, each asserting the verdict and, where refused, that the matched words appear in the message.
Non-goals: No change to which prompts are refused. No new verb classes, no negation or noun-context changes, and no change to the routing advice the message gives.
Layer: domain
Feature state: active
Files:
- engine/hooks/agent-routing-guard/detect.py
- engine/hooks/agent-routing-guard/tests/test_hooks.py
Change types:
- engine/hooks/agent-routing-guard/detect.py: modify
- engine/hooks/agent-routing-guard/tests/test_hooks.py: modify
Acceptance criteria:
- `python3 -m unittest discover -s engine/hooks/agent-routing-guard/tests -v` exits 0.
- `python3 scripts/check_hook_test_coverage.py engine/hooks/agent-routing-guard` exits 0.
- `python3 scripts/check_no_new_comments.py --base origin/main` exits 0.

Exit code: 0
Invoker-Finalize-Id: e1386d6d-295a-4de7-8c40-bb51229bb8e0
…m: the guard's tests and its hook-coverage gate pass.

Review lane: proof
Safety invariant: Verification is read-only and changes no file.
Effectiveness measurement: The commands are the measurement.
Slice rationale: One proof for one claim.
Architectural effect: None; verification only.
Goal: Prove the guard's suite passes with the new message and fixtures.
Motivation: A message change that breaks a caller must be caught here.
Alternative considerations: The whole suite was rejected as slower without adding evidence for this claim.
Implementation details: Run the guard's tests and its coverage gate.
Non-goals: No edits.
Layer: app_regression
Feature state: active
Acceptance criteria:
- Exits 0 only when the tests and the gate pass.

Exit code: 0
Invoker-Finalize-Id: a582d341-dd65-404f-ad0a-dc236a2d3420
…no ephemeral handoff files remain in the worktree.

Review lane: cleanup
Safety invariant: The scrub script only checks for known handoff artifact names and never touches source or tests.
Effectiveness measurement: The script exits non-zero if any handoff artifact remains.
Slice rationale: Required terminal scrub for every implementation workflow.
Architectural effect: None; hygiene only.
Goal: Leave the branch free of handoff artifacts.
Motivation: Handoff files must not reach the PR.
Alternative considerations: Manual cleanup was rejected as non-deterministic.
Implementation details: Run scripts/scrub-handoff-artifacts.sh.
Non-goals: No product edits.
Layer: app_regression
Feature state: active
Acceptance criteria:
- `bash scripts/scrub-handoff-artifacts.sh` exits 0.

Exit code: 0
Invoker-Finalize-Id: a87dbb7c-e3ed-410f-989d-01ae6209b001
…aec4f354e-471f44e0 — Review claim: no ephemeral handoff files remain in the worktree.

Review lane: cleanup
Safety invariant: The scrub script only checks for known handoff artifact names and never touches source or tests.
Effectiveness measurement: The script exits non-zero if any handoff artifact remains.
Slice rationale: Required terminal scrub for every implementation workflow.
Architectural effect: None; hygiene only.
Goal: Leave the branch free of handoff artifacts.
Motivation: Handoff files must not reach the PR.
Alternative considerations: Manual cleanup was rejected as non-deterministic.
Implementation details: Run scripts/scrub-handoff-artifacts.sh.
Non-goals: No product edits.
Layer: app_regression
Feature state: active
Acceptance criteria:
- `bash scripts/scrub-handoff-artifacts.sh` exits 0.
@cursor

cursor Bot commented Sep 12, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_5319b103-66a8-4009-8932-df2985c1429b)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant