Skip to content

feat(#268): skill-gated ticket-create hook (multi-tracker)#276

Merged
atlas-apex merged 2 commits into
devfrom
feature/GH-268-multi-tracker-gate
May 17, 2026
Merged

feat(#268): skill-gated ticket-create hook (multi-tracker)#276
atlas-apex merged 2 commits into
devfrom
feature/GH-268-multi-tracker-gate

Conversation

@atlas-apex

Copy link
Copy Markdown
Collaborator

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 + /investigation skills), 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

File Role
.claude/hooks/require-skill-for-issue-create.sh PreToolUse:Bash gate — reads ticket.create_command_patterns from project-config, matches at command-boundary (start-of-line or after ;/&&/`
.claude/hooks/clear-issue-skill-marker.sh SessionStart cleaner — mirror of clear-bootstrap-marker.sh. Sweeps stale active-issue-skill markers from killed sessions
.claude/project-config.defaults.json Adds ticket.create_command_patterns (6 defaults: gh, gh-api, linear, jira variants, asana). Adopters extend via .claude/project-config.json shallow merge
.claude/settings.json Wires both hooks into the right events
7 ticket SKILL.md files Step 0 marker-write block at entry + cleanup on exit. Covers /task, /feature, /bug, /spike, /migration, /investigation, /idea
.claude/rules/ticket-vocabulary.md Backstop-enforcement table row documents the new hook + multi-tracker matcher list
docs/agdr/AgDR-0030-skill-gated-ticket-create.md Design rationale, mirror-of-bootstrap-exemption pattern, tracker-agnostic decision
.claude/hooks/tests/test_require_skill_for_issue_create.sh 18-case test suite, all passing

Why 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

$ bash .claude/hooks/tests/test_require_skill_for_issue_create.sh
... PASS: 18   FAIL: 0

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)

  • Tightening gh api repos/ pattern — currently catches any gh api repos/... call, including read-only gh api repos/foo/bar/issues/123 (issue view). A regex-aware matcher could narrow to gh api repos/.*/issues\s*-X POST shape; deferred until a real false-positive surfaces.
  • Closing feat(#268): skill-gated ticket-create hook (multi-tracker) #274 — supersedes-by-PR; will close once this lands.

Testing

  • All 18 hook tests pass
  • Anchored matcher false-positive fix verified manually with quoted-prose command
  • True positive (actual gh issue create) still fires
  • Marker write + cleanup flow tested end-to-end (this PR's commit itself uses it)
  • Operator verification — run /feature after this lands, verify the gate doesn't block

Closes #268.

Glossary

Term Definition
Skill marker A file at .claude/session/active-<purpose> that a skill writes on entry and removes on exit, signalling to hooks that the skill is in progress
Bootstrap exemption The existing pattern (.claude/session/active-bootstrap) that lets /setup, /handover, /update, /split-portfolio run before the portfolio is configured
Tracker The system where issues / tickets live — GitHub, Linear, Jira, Asana, etc. The framework defaults to GitHub today but should not assume it
Anchored matcher A substring matcher that only fires at command-boundary positions (start-of-line, after ;/&&/`

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 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 #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-only gh 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' or printf '%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 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.

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

@atlas-apex atlas-apex merged commit 627c161 into dev May 17, 2026
4 checks passed
@atlas-apex atlas-apex deleted the feature/GH-268-multi-tracker-gate branch May 17, 2026 23:30
@atlas-apex atlas-apex mentioned this pull request May 18, 2026
8 tasks
me2resh added a commit that referenced this pull request Jun 5, 2026
* 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>
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.

2 participants