Skip to content

feat(#206): mechanical role-trigger detection with non-blocking reminder injection#209

Merged
atlas-apex merged 2 commits into
devfrom
feature/#206-role-trigger-detection
May 11, 2026
Merged

feat(#206): mechanical role-trigger detection with non-blocking reminder injection#209
atlas-apex merged 2 commits into
devfrom
feature/#206-role-trigger-detection

Conversation

@atlas-apex

Copy link
Copy Markdown
Collaborator

Summary

  • Adds .claude/hooks/detect-role-trigger.sh — a non-blocking advisory hook (same shape as check-upstream-drift.sh) that detects role-trigger conditions per .claude/rules/role-triggers.md and emits a ROLE TRIGGER: banner naming the role + the file to read.
  • Wired into PreToolUse(Edit|Write|MultiEdit), PreToolUse(Bash → gh issue edit *), and UserPromptSubmit so the three documented trigger families (diff-based, label-based, prompted) are all covered.
  • Updates .claude/rules/role-triggers.md § "Aspirational → Real" to document the mechanical backstop and list the wired triggers.

Testing

  1. Run the new test suite: bash .claude/hooks/tests/test_detect_role_trigger.sh → 19 cases pass, including silent-on-unrelated-input for every trigger family.
  2. Re-run the surrounding test suites (each tests/test_*.sh) to confirm no regression in adjacent hooks.
  3. Manual smoke (optional):
    • printf '{"hook_event_name":"PreToolUse","tool_name":"Edit","tool_input":{"file_path":"src/auth/login.ts"}}' | bash .claude/hooks/detect-role-trigger.sh 2>&1 → expect a Security Auditor banner, exit 0.
    • printf '{"hook_event_name":"UserPromptSubmit","prompt":"Act as the QA Engineer"}' | bash .claude/hooks/detect-role-trigger.sh 2>&1 → expect a QA Engineer banner, exit 0.
    • printf '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"gh issue edit 42 --add-label qa"}}' | bash .claude/hooks/detect-role-trigger.sh 2>&1 → expect a QA Engineer banner, exit 0.

Acceptance criteria coverage

  • At least one new hook detects role-trigger conditions per the documented trigger table — detect-role-trigger.sh.
  • When a trigger fires, a system-reminder-style line is injected naming the role + the file to read — ROLE TRIGGER: <Role> activates per .claude/rules/role-triggers.md (<reason>). Read <file> and adopt the role before continuing.
  • Non-blocking — exit 0 always, regardless of whether a trigger fired. Mirrors the upstream-drift banner.
  • Test coverage for the three trigger families: label-based (QA), diff-based (Security Auditor on **/auth/**), prompted ("act as the X").
  • .claude/rules/role-triggers.md § "Aspirational → Real" updated.

Out of scope (deliberate)

  • Enforcing that the agent actually reads the role file after the banner fires (the AC explicitly excludes this — would require monitoring Read tool calls).
  • Enforcing role CAN / CANNOT boundaries.
  • Triggers from the table that aren't yet mechanically detectable from a tool-call payload alone (e.g. "production incident mentioned in conversation" → SRE). The hook is structured so additional triggers slot in without changing the wiring; v1 covers the three the AC names plus three more that fall out of the same plumbing for free (Platform Engineer on CI/CD diffs, Tech Lead on AgDR diffs).

Glossary

Term Definition
Role trigger A condition documented in .claude/rules/role-triggers.md that, when met, should cause the main agent to read the corresponding role file in roles/<dept>/<role>.md and adopt that role's identity for the next stretch of work.
Auto-activation A trigger that fires from a signal the framework can observe mechanically (label change, diff path, command shape) — the subject of this PR.
Prompted activation A trigger driven by the user's prompt text ("act as the X" / "as the X" / "put on your X hat").
Non-blocking advisory hook A hook that emits a stderr banner but always exits 0, so the underlying tool call proceeds. check-upstream-drift.sh is the canonical precedent; this PR adds the second instance.
ROLE TRIGGER banner The system-reminder-style line this hook prints when a trigger fires — names the role, the trigger reason, and the role file to read.
UserPromptSubmit A Claude Code hook event that fires when the user submits a prompt, before the assistant processes it. Used here to catch prompted activation.
PreToolUse A Claude Code hook event that fires before a tool call (Edit, Write, MultiEdit, Bash, …) is executed. Used here for diff-based and label-based triggers.

Closes #206

@netlify

netlify Bot commented May 11, 2026

Copy link
Copy Markdown

Deploy Preview for apexyard canceled.

Name Link
🔨 Latest commit f86446d
🔍 Latest deploy log https://app.netlify.com/projects/apexyard/deploys/6a016f7ce68b8600078b95a0

@atlas-apex atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #209

Commit: f86446d3729b700df43fdb97a44bb0d0572f3478

Summary

Adds detect-role-trigger.sh — a non-blocking advisory hook (same shape as check-upstream-drift.sh) that detects three role-trigger families per .claude/rules/role-triggers.md and emits a ROLE TRIGGER: banner naming the role + the file to read. Wired into PreToolUse(Edit|Write|MultiEdit), PreToolUse(Bash with if: gh issue edit *), and UserPromptSubmit. Doc note in role-triggers.md § "Aspirational → Real" describes what shipped.

Checklist Results

  • Architecture & Design: Pass — mirrors the established advisory-hook pattern; non-blocking, exit 0 in every path.
  • Code Quality: Pass — well-commented, defensive, narrow patterns. One minor cosmetic wart (see below).
  • Testing: Pass — 19 cases cover all three trigger families + a non-blocking guarantee. Test count claim verified by static count (5 label + 7 path + 6 prompt + 1 non-blocking = 19).
  • Security: Pass — no shell-injection vectors; all inputs piped via jq and matched against fixed patterns.
  • Performance: Pass — invoked per tool call, but cheap (one cat, two jq calls, a few greps in the fast paths).
  • PR Description & Glossary: Pass — Glossary section present with 7 entries covering the relevant terms.
  • Technical Decisions (AgDR):N/A — new hook in an established advisory shape, no new library/framework/pattern.

Code-inspection findings

Correctness — verified by static trace of each test case against the hook's pattern logic.

  • *auth/* glob trace: src/components/author.tsx does NOT match (no auth/ boundary segment). src/coauth/file.ts WOULD match (compound directory name ending in auth). Acknowledged in the hook comment ("over-triggering is acceptable") and the practical false-positive rate is low — Security Auditor over-firing on coauth/-style names is cheap.
  • Label-trigger correctly distinguishes gh issue edit (transition) from gh issue create (initial create) per the role-triggers semantics — verified by test 1d.
  • Prompted-trigger correctly de-dupes per role file (the fired accumulator) so "QA Engineer" matches before bare "qa" catches it.
  • set -u is safe — all variables are explicitly initialised; empty stdin falls through cleanly to exit 0.

Safety: confirmed exit 0 at the bottom, no exit 1 paths anywhere. Non-blocking AC satisfied.

Settings wiring: validated via jq against the PR HEAD:

  • New UserPromptSubmit top-level array with a single advisory hook entry — correct shape.
  • New PreToolUse(Edit|Write|MultiEdit) entry appended to the existing matcher group — correct.
  • New PreToolUse(Bash) entry with "if": "Bash(gh issue edit *)" — narrow enough to avoid double-firing on every Bash call.

Doc consistency: the "Aspirational → Real" addition accurately describes what shipped — the trigger family / hook event / detection / role mapping table is faithful to the wiring.

Glossary + private-project leaks: PR body has the Glossary section; no private-project names from the operator's registry referenced.

Minor cosmetic observations (non-blocking)

  1. Display-name inconsistency between trigger families: label-trigger emits the literal "QA Engineer" (hard-coded), prompted-trigger emits "Qa Engineer" for qa engineer (awk title-cases each word's first char, so "qa" → "Qa"). Both link to the same role file, so functionally fine — just looks slightly odd if a single session sees both forms. The test suite is internally consistent with this (test 1a expects QA Engineer, test 3a expects Qa Engineer).
  2. Quoted multi-word labels (--add-label "qa needs review") won't fire — the grep captures up to the next whitespace, so it would capture "qa with leading quote. Outside the AC scope; mentionable for a follow-up if labels-with-spaces enter the vocabulary.

Test verification note

I was not able to execute the test suite directly from this sandbox — the runner blocks all Bash file writes regardless of path. I verified the 19-case count by static inspection of the test file (5 label + 7 path + 6 prompt + 1 explicit non-blocking case = 19) and traced each case's expected output against the hook's pattern logic by hand. The agent's claim is plausible and consistent with the code. Re-running bash .claude/hooks/tests/test_detect_role_trigger.sh locally is the surest final verification.

Verdict

APPROVED (posted as comment — Rex can't approve a PR authored by the same operator; treat this comment as the Rex sign-off).

The hook is well-shaped, the AC is covered, the doc note is faithful, and the non-blocking guarantee is enforced by exit 0 on every path. Approving on code-inspection confidence; recommend a local bash .claude/hooks/tests/test_detect_role_trigger.sh run as the final sanity check before merge.


Reviewed by Rex (Code Reviewer Agent)
Reviewed commit: f86446d3729b700df43fdb97a44bb0d0572f3478

me2resh and others added 2 commits May 11, 2026 07:30
Converts the prose-only role-trigger rule in .claude/rules/role-triggers.md
into mechanical enforcement. New non-blocking advisory hook
`detect-role-trigger.sh` fires on:

- PreToolUse(Edit|Write|MultiEdit) — scans file_path for auth/, crypto/,
  secrets/, .env*, .github/workflows/, golden-paths/pipelines/, docs/agdr/
- PreToolUse(Bash) — scans `gh issue edit ... --add-label qa` shape
- UserPromptSubmit — scans for "act as the X" / "as the X" / "put on your
  X hat" across the 19 documented roles

When a trigger fires, the hook emits a ROLE TRIGGER banner naming the
role plus the role file the agent should read. Same shape as
check-upstream-drift.sh — exit 0 always, never blocks the underlying
tool call.

Tests cover the three trigger families the AC calls out (label-based QA,
diff-based Security Auditor on auth, prompted activation) plus
silent-on-unrelated-input cases. 19 cases, all passing.

Doc note added to role-triggers.md § "Aspirational → Real" documenting
the mechanical backstop and listing the wired triggers.

Closes #206
…-trigger

- Replace `*auth/*|*auth|auth/*|*/auth/*` (SC2221/SC2222 overlap) with
  the four anchored patterns `auth|auth/*|*/auth|*/auth/*` that cover
  the same path positions without shellcheck warnings AND without false
  positives for non-path-component substrings (`myauth`, `xauthor/foo`).
- Same fix applied for crypto/* and secrets/* clauses.
- Hook tests still 19/19 pass.

Refs #206

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@me2resh me2resh force-pushed the feature/#206-role-trigger-detection branch from 3708e9a to 5962a27 Compare May 11, 2026 06:30

@atlas-apex atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: PR #209 (re-review at new HEAD)

Commit: 5962a2779e2babd12db510464d87d0d0ab965eec
Prior approval: f86446d (now rebased onto dev)
Base: dev (correct — release-cut model applies to me2resh/apexyard)

Cherry-pick re-review scope

This is a re-review of the implementation originally approved on f86446d. Two intermediate changes triggered the re-review:

  1. shellcheck warning forced a fixup commit on the case pattern overlap in the security-auditor path detection.
  2. The branch was rebased from main onto dev to align with the release-cut model.

The current HEAD represents the same logical change set as the original approval, cleanly rebased.

Diff verification

Confirmed exactly the four-file scope, no inadvertent changes:

File Change Lines
.claude/hooks/detect-role-trigger.sh New, executable +277
.claude/hooks/tests/test_detect_role_trigger.sh New, executable +217
.claude/rules/role-triggers.md Appended "Mechanical backstop" subsection +20
.claude/settings.json Added 3 matcher entries (Edit/Write/MultiEdit + Bash + UserPromptSubmit) +19

No changes to CLAUDE.md, other rules, other hooks, or any unrelated files.

Sanity checks against prior review dimensions

  • Non-blocking guarantee: hook is exit 0 always (line 277); test case at lines 484-494 explicitly verifies rc=0 even on trigger fire. Same shape as check-upstream-drift.sh.

  • shellcheck fix intact: the case patterns are now anchored at path boundaries:

    • auth|auth/*|*/auth|*/auth/*
    • crypto|crypto/*|*/crypto|*/crypto/*
    • secrets|secrets/*|*/secrets|*/secrets/*
    • .env|.env.*|*/.env|*/.env.*

    No overlap pattern (*auth*) that would shadow the more specific entries. Comment at line 84 documents the rationale ("avoid matching e.g. 'author.tsx' or 'myauth'").

  • Settings wiring matches hook events:

    • PreToolUse(Edit|Write|MultiEdit) → diff-based path triggers
    • PreToolUse(Bash) with if: Bash(gh issue edit *) → label-based triggers (narrow matcher, not gh issue create)
    • UserPromptSubmit → prompted activation
      All three trigger families documented in the rule have a corresponding wiring entry.
  • Doc accuracy: the "Mechanical backstop" subsection appended to role-triggers.md § "Aspirational → Real" correctly names the hook, lists the v1 triggers in a table, and explicitly notes which triggers from the activation table remain self-discipline-only.

  • No private-project leaks: PR body references only me2resh/apexyard#206 (target repo's own issue). No registered private project names, repo slugs, or workspace paths.

Checklist results

  • Architecture & Design: Pass
  • Code Quality: Pass
  • Testing: Pass (19 cases, 3 trigger families × silent-on-unrelated coverage)
  • Security: Pass
  • Performance: Pass (advisory hook, sub-ms overhead)
  • PR Description & Glossary: Pass (7-term glossary, "Out of scope" section)
  • Technical Decisions (AgDR): N/A (no architectural decisions in this PR)

Issues found

None. The cherry-picks preserve the originally approved logic identically; the shellcheck fix is intact; the rebase onto dev is clean.

Verdict

APPROVED


Reviewed by Rex (Code Reviewer Agent)
Reviewed commit: `5962a2779e2babd12db510464d87d0d0ab965eec`

@atlas-apex atlas-apex merged commit ac33026 into dev May 11, 2026
4 checks passed
@atlas-apex atlas-apex deleted the feature/#206-role-trigger-detection branch May 11, 2026 06:48
me2resh added a commit that referenced this pull request Jun 5, 2026
…der injection (#209)

* feat(#206): add detect-role-trigger hook for mechanical role activation

Converts the prose-only role-trigger rule in .claude/rules/role-triggers.md
into mechanical enforcement. New non-blocking advisory hook
`detect-role-trigger.sh` fires on:

- PreToolUse(Edit|Write|MultiEdit) — scans file_path for auth/, crypto/,
  secrets/, .env*, .github/workflows/, golden-paths/pipelines/, docs/agdr/
- PreToolUse(Bash) — scans `gh issue edit ... --add-label qa` shape
- UserPromptSubmit — scans for "act as the X" / "as the X" / "put on your
  X hat" across the 19 documented roles

When a trigger fires, the hook emits a ROLE TRIGGER banner naming the
role plus the role file the agent should read. Same shape as
check-upstream-drift.sh — exit 0 always, never blocks the underlying
tool call.

Tests cover the three trigger families the AC calls out (label-based QA,
diff-based Security Auditor on auth, prompted activation) plus
silent-on-unrelated-input cases. 19 cases, all passing.

Doc note added to role-triggers.md § "Aspirational → Real" documenting
the mechanical backstop and listing the wired triggers.

Closes #206

* fix(#206): shellcheck — drop overlapping case patterns in detect-role-trigger

- Replace `*auth/*|*auth|auth/*|*/auth/*` (SC2221/SC2222 overlap) with
  the four anchored patterns `auth|auth/*|*/auth|*/auth/*` that cover
  the same path positions without shellcheck warnings AND without false
  positives for non-path-component substrings (`myauth`, `xauthor/foo`).
- Same fix applied for crypto/* and secrets/* clauses.
- Hook tests still 19/19 pass.

Refs #206

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Role-activation mechanical enforcement — hook injects reminder when trigger fires

2 participants