Skip to content

chore(#107): validate-issue-structure.sh PreToolUse hook#122

Merged
atlas-apex merged 1 commit into
devfrom
chore/GH-107-validate-issue-structure
Apr 24, 2026
Merged

chore(#107): validate-issue-structure.sh PreToolUse hook#122
atlas-apex merged 1 commit into
devfrom
chore/GH-107-validate-issue-structure

Conversation

@atlas-apex

Copy link
Copy Markdown
Collaborator

Summary

Adds a new PreToolUse hook validate-issue-structure.sh that fires on gh issue create and enforces a body-shape schema per bracketed title prefix. Closes the gap where agents bypass the interactive /feature / /task / /bug skills and file raw gh issue create with bespoke body shapes.

The rule was already in .claude/rules/ticket-vocabulary.md and the individual skill docs, but until now it was self-discipline. This hook is the mechanical backstop: if the body doesn't have the required sections for its prefix, the issue-creation call exits 2 with a message naming the missing sections and pointing at the right skill.

  • New hook: .claude/hooks/validate-issue-structure.sh (~340 LOC)
  • Config integration: reads .ticket.required_sections + .ticket.skip_marker via the shared reader from [Chore] Make ticket-prefix whitelist + schema project-configurable (not hardcoded) #109. Extended .claude/project-config.defaults.json with the mapping (Feature → [User Story, Acceptance Criteria]; Chore/Refactor/Testing/CI → [Driver, Scope, Acceptance Criteria]; Docs → [Driver, Acceptance Criteria]; Bug → [Given / When / Then, Repro]).
  • Wired into .claude/settings.json — PreToolUse matcher on Bash(gh issue create *).
  • New section 11 in docs/rule-audit.md for the rule; mechanized count bumped.
  • 15 test cases covering every prefix's pass + fail + the empty-section path + the skip marker.

How it works

  1. Matches gh issue create only. Silent pass on everything else.
  2. Parses --title and --body / --body-file / -F <path> (lifted the arg-extractor style from block-private-refs-in-public-repos.sh, with awk-based multi-line body handling so bodies with \n survive intact — sed is line-oriented and would have truncated).
  3. Extracts the bracketed prefix ([Feature], [Chore], etc.), lowercases for the whitelist match, preserves canonical case for the required-sections lookup. Blocks if the prefix isn't whitelisted.
  4. For each required section, greps for ## <name> (case-insensitive, with tolerance for ## Given/When/Then vs ## Given / When / Then vs ## given/when/then).
  5. Empty-content check: after each heading, awk from the heading line to the next ## (or EOF) and strip whitespace — an empty range fails.
  6. Skip marker: <!-- validate-issue-structure: skip --> in the body bypasses with a visible stderr WARN. For epics, meta-threads, parking-lot issues that don't fit the schema.
  7. Silent pass conditions: no body (interactive gh issue create), prefix not in the schema, config completely absent.

Testing

$ bash .claude/hooks/tests/test_validate_issue_structure.sh
PASS: Feature with User Story + Acceptance Criteria → pass
PASS: Feature missing Acceptance Criteria → block
PASS: Chore with all three sections → pass
PASS: Chore missing Scope → block
PASS: Bug with Given / When / Then + Repro → pass
PASS: Bug with Given/When/Then compact heading → pass
PASS: Bug missing Repro → block
PASS: Feature with empty User Story section → block
PASS: skip marker bypasses validation
PASS: unknown title prefix → block
PASS: title without bracketed prefix → pass
PASS: non gh-issue-create command → no-op
PASS: gh issue create with no body → no-op
PASS: Docs with Driver + Acceptance Criteria → pass
PASS: --body-file path with valid Feature body → pass
Passed: 15  Failed: 0

Rebased cleanly onto the current dev HEAD after #111 landed — auto-merge of .claude/project-config.defaults.json (nested ticket subtree gained required_sections + skip_marker) and docs/rule-audit.md (new row at section 11). Both resolved without conflict.

Scope — what this does NOT do

Follow-ups

  • If short or generic prefixes emerge ([Infra], [Scaffold], [Design], [Security]) — teams extend by adding to .ticket.prefix_whitelist + .ticket.required_sections in their per-fork project-config.json. No framework edits required per apexyard#109's config-driven design.

Glossary

Term Definition
Required section A Markdown heading (## Name) that must appear in the body with non-empty content.
Prefix whitelist The set of bracketed title prefixes the hook recognises. Extended per-fork via .ticket.prefix_whitelist.
Empty-section check After finding a heading, scan forward to the next ## — if there's no non-whitespace content, the section is flagged as empty.
Skip marker <!-- validate-issue-structure: skip --> — bypasses validation for one invocation with a visible WARN.

Closes #107

Loading
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