T-AWEM-sdlc-setup-seeds-hooks-gitignore-unignore
Status: closed/done · Impact: medium · Complexity: small
Auto-generated from a /sdlc:task-work post-mortem. Review and
promote to ready before picking up.
Project-local Claude Code hooks live under .claude/hooks/<script> and
are intentionally shareable (just like .claude/settings.json), but
the project’s .gitignore template ignores everything under .claude/
by default and only un-ignores a known short list (settings.json,
skills/, commands/, agents/, hooks.json). When a project adds
its first hook script the commit silently fails with “paths ignored by
.gitignore” until the author hand-edits .gitignore. Closing this gap
lets new projects ship hooks without tripping over the boilerplate. See
T-H9Q4-document-every-skill-with-mermaid-flowchart for the
originating friction.
/sdlc:setup (today plugin/skills/setup/SKILL.md, backed by
plugin/skills/setup/setup_planning.py — the script is co-located
with its skill per plugin/skills/CLAUDE.md’s co-locate-first
rule, NOT at plugin/scripts/setup_planning.py) only creates
docs/planning/<type>/ directories. It does not touch
.gitignore. The script also has no --dry-run flag yet; adding
one is part of this task’s scope per AC-3.
Note (2026-05-20): this project’s .gitignore already contains
!.claude/hooks/ and !.claude/hooks/** — they were added inline
during the originating task. The setup-skill gap remains for any
fresh project adopting the plugin.
The canonical “ignore .claude/ but un-ignore the shareable bits”
block reads:
# --- Claude Code project-local ---.claude/*!.claude/settings.json!.claude/skills/!.claude/commands/!.claude/agents/!.claude/hooks.json!.claude/hooks/!.claude/hooks/**Without the trailing two lines, freshly-checked-in hook scripts
under .claude/hooks/<name>.py are ignored and git add refuses
without -f.
Proposed
Section titled “Proposed”/sdlc:setup becomes responsible for ensuring .gitignore contains
the canonical un-ignore block for shareable .claude/ directories,
including .claude/hooks/. The skill is idempotent (no-op if the
block is already present) and surgical (no other lines edited). New
projects that adopt the SDLC plugin can drop a hook into
.claude/hooks/ and commit it without manual .gitignore surgery.
Approach
Section titled “Approach”- Define the canonical block as shown in the Today section
(keep the existing per-bit un-ignore shape and add
!.claude/hooks/plus!.claude/hooks/**). Hard-code it as a string constant insetup_planning.py. - Extend
plugin/skills/setup/setup_planning.py: read.gitignore, detect the canonical block by its fence comment (# --- Claude Code project-local ---). If absent, append the full block. If present but missing the hooks lines, patch in-place. If the file doesn’t exist, create it with just the block. Leave all other lines untouched. Keep this logic insetup_planning.pydirectly (no sibling helper) to match the rest of the setup flow. - Add a
--dry-runflag tosetup_planning.py. When set: report what would change in.gitignore(and any planning dirs) to stdout in a unified diff form, but do not write. Plumb the flag through any existing write paths so the flag’s semantics are “no writes anywhere,” not just gitignore. - Update
plugin/skills/setup/SKILL.mdto describe the new side-effect and how--dry-runworks. - Cover with a fixture-based test under
plugin/skills/setup/tests/(this directory is greenfield; create it). Fixtures: fresh project (no.gitignore), project with the block but missing hooks lines, project with the block fully populated (no-op).
Files to touch
Section titled “Files to touch”plugin/skills/setup/setup_planning.py— extend to ensure the canonical.gitignoreblock exists; add--dry-runflag.plugin/skills/setup/SKILL.md— mention the new behavior and--dry-runsemantics.docs/skills/setup.md— refresh the flowchart to include the.gitignorestep. (The skill-doc-drift warning hook from the parent task will flag this if forgotten.)plugin/skills/setup/tests/(new directory) — fixtures for the three cases above plus a--dry-runno-write assertion.
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
/sdlc:setupon a project with no.gitignorecreates one containing the canonical Claude Code block, including!.claude/hooks/and!.claude/hooks/**. - AC-2:
/sdlc:setupon a project whose.gitignorehas the block but lacks the hooks lines inserts them in the correct position and leaves the rest of the file byte-identical. - AC-3:
/sdlc:setup --dry-runreports what would change in.gitignorebut does not write. - AC-4: After the skill runs, dropping a script at
.claude/hooks/<name>.pyand runninggit addsucceeds without-f.
Out of scope
Section titled “Out of scope”- Migrating projects that already shipped a non-canonical
.claude/block (e.g. they un-ignored different bits). Manual reconciliation. - Auto-creating hook scripts.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:task-work post-mortem of
T-H9Q4-document-every-skill-with-mermaid-flowchart on
2026-05-19. The parent task ran into the missing un-ignore line while
adding .claude/hooks/warn-skill-doc-drift.py; worked around it by
editing .gitignore inline.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-05-21. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto —
plugin/skills/setup/tests/run_evals.pycaseac1-no-gitignore-creates-canonical-blockruns the script against a temp project with no.gitignore, then asserts both!.claude/hooks/and!.claude/hooks/**are in the resulting file. - AC-2: auto — case
ac2-block-missing-hooks-gets-patched-in-placestarts from a fixture whose.gitignorehas the canonical fence but lacks the hooks lines, runs the script, and asserts the hooks lines were inserted while the rest of the file remained intact. Theblock-fully-populated-is-no-opcase complements it by proving an already-canonical file is left alone. - AC-3: auto — cases
ac3-dry-run-on-fresh-project-writes-nothingandac3-dry-run-on-patch-case-writes-nothingassert the filesystem is unchanged after--dry-run: the fresh-project case checks no.gitignorewas created, and the patch-case checks the file is byte-identical to the fixture input. - AC-4: auto —
assert_git_add_hook_succeedsruns inside cases ac1, ac2, andno-fence-appends-block, initializing an ephemeral git repo at the fixture root, dropping.claude/hooks/demo.py, and verifyinggit add .claude/hooks/demo.pyreturns 0 and actually stages the file (without-f).
What worked
Section titled “What worked”- The existing fence comment
# --- Claude Code project-local ---made block detection unambiguous — no regex acrobatics needed, just locate the fence and walk to the next blank line or top-level fence boundary. - The eval pattern from
plugin/skills/entities-audit/tests/run_evals.pytransferred cleanly: a list of(name, fixture_builder, expectations)tuples with a small custom-check hook covered every AC including thegit addinvariant without needing a separate harness. --dry-runwas already plumbed through the existing write paths; extending it to the new gitignore step was a one-line check, no refactor required.
Friction and automation gaps
Section titled “Friction and automation gaps”- The Today section asserted “the script also has no
--dry-runflag yet” — but the script already had--dry-runplumbed when this task was picked up. The relevance check in /sdlc:task-work Step 2 didn’t surface that mismatch because the regex sweep only looks at file paths, not at claims-about-flags. A symbol-and-flag-claim cross-check (extract back-ticked CLI flags from Today, thengrepthe cited script for anadd_argument(..."--<flag>")` would catch this class of staleness automatically. → T-DV8J-task-work-relevance-verifies-cli-flag-claims - Step 5b’s rebase of the feat branch onto main hit a conflict on the
task file because ensure-ready had bumped
readiness_verified_at:to a newer timestamp on the feat branch while the start-commit on main carried the older stamp. Resolution was trivial (keep both changes), but a one-shot helper that combines “stamp ensure-ready”- “set status: in-progress + last_reviewed” into a single edit on one branch (instead of bumping the same frontmatter on two branches separately) would eliminate the conflict entirely. → T-H98A-last-reviewed-rebase-collision
Spawned follow-up tasks
Section titled “Spawned follow-up tasks”- T-DV8J-task-work-relevance-verifies-cli-flag-claims — created; adds CLI-flag-claim verification to Step 2’s relevance check.
- T-H98A-last-reviewed-rebase-collision — linked existing; collapses the Step 5a/5b same-file frontmatter edits into one branch to eliminate the rebase conflict.