Description
Add a guard:exempt skill plus an exempt CLI subcommand so the user manages the
exempt_skills list (introduced in #172) interactively, instead of hand-editing
.claude/guard.local.json.
Flow: the skill lists the skills available in the current session, uses
AskUserQuestion for the user to choose which to exempt, and — once the user confirms
— records the selection through the exempt CLI (guard_hook.py exempt set …, run via
Bash). If the user already named skills in their message, it skips the question and
records directly.
Changes (plugins/guard/):
- New skill
skills/exempt/SKILL.md (model-invocable) — drives list →
AskUserQuestion → record → relay.
- New
exempt CLI subcommand (cmd_exempt): list / set / add / remove /
clear. Edits ONLY the exempt_skills key — never enabled / mode / state — so
it can narrow the judge's coverage but cannot disable guard or touch the approval
gate. Preserves every other config key; normalizes names (leading / stripped,
lowercased, plugin namespace kept). Project dir from CLAUDE_PROJECT_DIR, else cwd.
_CONTROL_CMD_RE now includes exempt, so a typed /guard:exempt turn is skipped
by the approval classifier (UserPromptSubmit) and by the Stop judge.
- Version 0.2.0 → 0.3.0; docs updated (
AGENTS.md, dev/design.md, README.md).
Context
Follow-up to #172, which added the exempt_skills config but left it to be
hand-edited. The committer is a script (not the Write/Edit tool) on purpose:
guard.local.json stays blocked from the file-editing tools (_is_guard_owned), so
the exempt CLI invoked via Bash is the only supported writer for exempt_skills.
The model can therefore manage that one key, but still cannot self-arm approval or
disable the judge.
Acceptance Criteria
- CLI verified:
list / add (normalizes leading / + case) / dedupe / remove /
set (replace) / clear behave correctly, and the result is read back by
_exempt_skills.
- Other config keys preserved — verified: with a config of
model/effort/enabled/
mode, exempt add kept all four and appended exempt_skills.
- Live e2e (Claude Code v2.1.197,
acceptEdits): /guard:exempt deep-research → the
skill ran exempt list then exempt set deep-research; .claude/guard.local.json
updated to exempt_skills: ["deep-research"]. Traces: exempt list, then
exempt set (n=1, changed=true).
python3 -c "import ast; ast.parse(...)" passes.
Description
Add a
guard:exemptskill plus anexemptCLI subcommand so the user manages theexempt_skillslist (introduced in #172) interactively, instead of hand-editing.claude/guard.local.json.Flow: the skill lists the skills available in the current session, uses
AskUserQuestionfor the user to choose which to exempt, and — once the user confirms— records the selection through the
exemptCLI (guard_hook.py exempt set …, run viaBash). If the user already named skills in their message, it skips the question and
records directly.
Changes (
plugins/guard/):skills/exempt/SKILL.md(model-invocable) — drives list →AskUserQuestion → record → relay.
exemptCLI subcommand (cmd_exempt):list/set/add/remove/clear. Edits ONLY theexempt_skillskey — neverenabled/mode/ state — soit can narrow the judge's coverage but cannot disable guard or touch the approval
gate. Preserves every other config key; normalizes names (leading
/stripped,lowercased, plugin namespace kept). Project dir from
CLAUDE_PROJECT_DIR, else cwd._CONTROL_CMD_REnow includesexempt, so a typed/guard:exemptturn is skippedby the approval classifier (UserPromptSubmit) and by the Stop judge.
AGENTS.md,dev/design.md,README.md).Context
Follow-up to #172, which added the
exempt_skillsconfig but left it to behand-edited. The committer is a script (not the Write/Edit tool) on purpose:
guard.local.jsonstays blocked from the file-editing tools (_is_guard_owned), sothe
exemptCLI invoked via Bash is the only supported writer forexempt_skills.The model can therefore manage that one key, but still cannot self-arm approval or
disable the judge.
Acceptance Criteria
list/add(normalizes leading/+ case) / dedupe /remove/set(replace) /clearbehave correctly, and the result is read back by_exempt_skills.model/effort/enabled/mode,exempt addkept all four and appendedexempt_skills.acceptEdits):/guard:exempt deep-research→ theskill ran
exempt listthenexempt set deep-research;.claude/guard.local.jsonupdated to
exempt_skills: ["deep-research"]. Traces:exempt list, thenexempt set(n=1, changed=true).python3 -c "import ast; ast.parse(...)"passes.