feat(#268): skill-gated ticket-create hook (multi-tracker)#276
Conversation
Mechanical enforcement of "tickets file through structured skills, not raw CLIs". Blocks raw ticket-create on gh, linear, jira, asana (and any adopter-extension via project-config) unless one of the 7 structured ticket skills is in flight. Components: - .claude/hooks/require-skill-for-issue-create.sh — PreToolUse:Bash matcher reading the configured pattern list and the marker at .claude/session/active-issue-skill. Patterns match at command-boundary only (start-of-line or after a shell separator) to avoid false-positives on prose-in-quotes. Bootstrap exemption preserved (active-bootstrap marker still allows). Operator escape hatch via env var. - .claude/hooks/clear-issue-skill-marker.sh — SessionStart cleaner mirroring clear-bootstrap-marker.sh; sweeps stale markers from killed sessions. - .claude/project-config.defaults.json — ticket.create_command_patterns ships with 6 default matchers covering gh + linear + jira + asana. Adopters extend via .claude/project-config.json shallow merge. - .claude/settings.json — wires the new hook into PreToolUse:Bash and the cleaner into SessionStart. - 7 ticket SKILL.md files (task, feature, bug, spike, migration, investigation, idea) — added Step 0 marker-write block at skill entry + cleanup instruction. - .claude/rules/ticket-vocabulary.md — backstop-enforcement table row documenting the new hook + multi-tracker matcher list. - docs/agdr/AgDR-0030-skill-gated-ticket-create.md — design rationale, mirror-of-bootstrap-exemption pattern, multi-tracker decision. - 18-case test suite (.claude/hooks/tests/test_require_skill_for_issue_create.sh): all 5 default matchers in blocked-without-marker and allowed-with-marker shape, bootstrap exemption (listed + non-listed), env-var escape hatch, custom-matcher via config override, empty-marker, edit/non-matching no-ops. Refs #268 Supersedes #274 (parallel agent implementation off stale dev; this PR is the cleaner reimplementation on current dev with anchored matcher). 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 #276
Commit: f2f68ce66c3161fe1e9e8e7e897589be42b958c9
Summary
Mechanical gate that blocks raw ticket-create CLIs (gh issue create, gh api repos/.../issues, linear|jira|asana shapes) unless a structured ticket skill is in flight via the .claude/session/active-issue-skill marker. Mirrors the bootstrap-skill exemption pattern (AgDR-0011). Tracker-agnostic via a config-driven matcher list. Anchored matcher avoids false-positives on prose-in-quoted commit messages.
Checklist Results
- Architecture & Design: Pass — mirrors AgDR-0011 pattern, consistent shape
- Code Quality: Pass — clean shell,
set -u, graceful no-ops on missing jq/defaults - Testing: Pass — 18-case suite, all green; covers blocked/allowed/bootstrap/env-var/custom-matcher/empty-marker/non-Bash
- Security: Pass — no secrets, env-var bypass is auditable (stderr warning)
- Performance: Pass — single pass over a small pattern list
- PR Description & Glossary: Pass — 4-term glossary, Closes #268
- Technical Decisions (AgDR):Pass — AgDR-0030 with 5 options considered, lineage to AgDR-0011
- Adopter Handbooks: N/A — no handbooks loaded for this framework-internal change
Issues Found
None blocking. CI is green except lychee (link-check), which is an unrelated network-class failure.
Suggestions (non-blocking)
gh api repos/over-matching is acknowledged in the PR body's "out of scope" — currently catches read-onlygh api repos/foo/bar/issues/123(issue view). Worth a follow-up issue when a real false-positive surfaces.- Heredoc pattern loop uses an unquoted heredoc — parameter expansion fires on
$inside patterns. Current defaults are safe, but<<'EOF'orprintf '%s\n' "$PATTERNS" | while …would be a touch more defensive. nit. - Step 0 block duplication across 7 SKILL.md files is consistent; future marker-lifecycle changes need lockstep edits to all 7. Acceptable for v1.
Verdict
APPROVED (submitted as comment — author cannot self-approve via the GitHub UI; treat as Rex sign-off.)
Reviewed by Rex (Code Reviewer Agent)
Reviewed commit: f2f68ce66c3161fe1e9e8e7e897589be42b958c9
The link target 'MEMORY' was a reference to an auto-memory entry that doesn't exist as a file in the repo — lychee correctly flagged it as broken. Replaced with prose so the phrase still reads. Refs #268 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
atlas-apex
left a comment
There was a problem hiding this comment.
Re-review at 5d68fc6 — APPROVED (self-PR, comment-form)
Follow-up CI-fix commit since prior APPROVE on f2f68ce. Confirmed scope via gh pr diff: single one-line edit in docs/agdr/AgDR-0030-skill-gated-ticket-create.md replacing broken relative link [Invoke matching skills](MEMORY) with prose "Invoke matching skills" operator-feedback memory. No code or behaviour change; unblocks lychee link-check.
Checklist Results
- Architecture & Design: N/A (docs-only)
- Code Quality: N/A (docs-only)
- Testing: N/A
- Security: Pass
- Performance: N/A
- PR Description & Glossary: Pass (carries over from prior review)
- Technical Decisions (AgDR):N/A
- Adopter Handbooks: N/A
Verdict
APPROVED
Approval marker updated to 5d68fc625681b7a4954dbd4be24f9ecd1ca491af.
🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: 5d68fc625681b7a4954dbd4be24f9ecd1ca491af
* feat(#268): skill-gated ticket-create hook (multi-tracker) Mechanical enforcement of "tickets file through structured skills, not raw CLIs". Blocks raw ticket-create on gh, linear, jira, asana (and any adopter-extension via project-config) unless one of the 7 structured ticket skills is in flight. Components: - .claude/hooks/require-skill-for-issue-create.sh — PreToolUse:Bash matcher reading the configured pattern list and the marker at .claude/session/active-issue-skill. Patterns match at command-boundary only (start-of-line or after a shell separator) to avoid false-positives on prose-in-quotes. Bootstrap exemption preserved (active-bootstrap marker still allows). Operator escape hatch via env var. - .claude/hooks/clear-issue-skill-marker.sh — SessionStart cleaner mirroring clear-bootstrap-marker.sh; sweeps stale markers from killed sessions. - .claude/project-config.defaults.json — ticket.create_command_patterns ships with 6 default matchers covering gh + linear + jira + asana. Adopters extend via .claude/project-config.json shallow merge. - .claude/settings.json — wires the new hook into PreToolUse:Bash and the cleaner into SessionStart. - 7 ticket SKILL.md files (task, feature, bug, spike, migration, investigation, idea) — added Step 0 marker-write block at skill entry + cleanup instruction. - .claude/rules/ticket-vocabulary.md — backstop-enforcement table row documenting the new hook + multi-tracker matcher list. - docs/agdr/AgDR-0030-skill-gated-ticket-create.md — design rationale, mirror-of-bootstrap-exemption pattern, multi-tracker decision. - 18-case test suite (.claude/hooks/tests/test_require_skill_for_issue_create.sh): all 5 default matchers in blocked-without-marker and allowed-with-marker shape, bootstrap exemption (listed + non-listed), env-var escape hatch, custom-matcher via config override, empty-marker, edit/non-matching no-ops. Refs #268 Supersedes #274 (parallel agent implementation off stale dev; this PR is the cleaner reimplementation on current dev with anchored matcher). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(#268): drop broken relative link in AgDR-0030 (CI lychee was red) The link target 'MEMORY' was a reference to an auto-memory entry that doesn't exist as a file in the repo — lychee correctly flagged it as broken. Replaced with prose so the phrase still reads. Refs #268 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
Mechanical enforcement of the "tickets file through structured skills, not raw CLIs" contract. Blocks raw ticket-create commands on
gh,linear,jira,asana(and any adopter-extension via project-config) unless one of the 7 structured ticket skills is in flight via a session marker.This supersedes the parallel agent implementation in #274 — that PR was filed from a stale dev base (predating
/spike+/investigationskills), so its diff included ~70 unintended "reverts". This PR ships the same design on current dev, with the matcher tightened to anchor at command boundaries (was false-positiving on prose-in-quotes inside commit messages).Components
.claude/hooks/require-skill-for-issue-create.shticket.create_command_patternsfrom project-config, matches at command-boundary (start-of-line or after;/&&/`.claude/hooks/clear-issue-skill-marker.shclear-bootstrap-marker.sh. Sweeps staleactive-issue-skillmarkers from killed sessions.claude/project-config.defaults.jsonticket.create_command_patterns(6 defaults: gh, gh-api, linear, jira variants, asana). Adopters extend via.claude/project-config.jsonshallow merge.claude/settings.json/task,/feature,/bug,/spike,/migration,/investigation,/idea.claude/rules/ticket-vocabulary.mddocs/agdr/AgDR-0030-skill-gated-ticket-create.md.claude/hooks/tests/test_require_skill_for_issue_create.shWhy anchored matching (not substring)
The earlier substring matcher false-positived on
git commit -m "feat: see gh issue create"— the literal phrase "gh issue create" appears in the quoted text but is not a command invocation. After tightening, the pattern must appear at the start of the line or right after a shell separator. This still catches:gh issue create --repo foo/bar← actual command ✓cd /tmp && gh issue create --repo foo/bar← after&&✓gh pr view 1; gh issue create --repo foo/bar← after;✓…and correctly ignores:
git commit -m "see also: gh issue create"← inside a quoted string ✓echo "tip: run gh issue create"← inside a quoted echo ✓Tests
Covers all 5 default matchers in blocked-without-marker and allowed-with-marker shape, bootstrap exemption (listed + non-listed bootstrap skills), env-var escape hatch (
APEXYARD_ALLOW_RAW_TICKET_CREATE=1), custom matcher via project-config override, empty-marker handling, Edit/non-matching no-ops.Out of scope (follow-ups)
gh api repos/pattern — currently catches anygh api repos/...call, including read-onlygh api repos/foo/bar/issues/123(issue view). A regex-aware matcher could narrow togh api repos/.*/issues\s*-X POSTshape; deferred until a real false-positive surfaces.Testing
gh issue create) still fires/featureafter this lands, verify the gate doesn't blockCloses #268.
Glossary
.claude/session/active-<purpose>that a skill writes on entry and removes on exit, signalling to hooks that the skill is in progress.claude/session/active-bootstrap) that lets/setup,/handover,/update,/split-portfoliorun before the portfolio is configured;/&&/`