From ea54be6535325e40581395ecd2b0e5bfe19df430 Mon Sep 17 00:00:00 2001 From: Brummel Date: Thu, 2 Jul 2026 11:53:59 +0200 Subject: [PATCH] refactor(pipeline): ignore specs/plans via project-local docs/.gitignore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the global ~/.config/git/ignore mechanism with a committed, per-project docs/.gitignore whose entries `specs/` and `plans/` are relative to docs/. The ignore now travels with each repo instead of living in the user's global excludes. specify (Step 3) and planner (intro) create docs/.gitignore on demand if a project has not set it up yet, so a spec/plan can never become committable. conventions.md § File layout is the single source; the onboarding fragment tells a new project to set it up. The global ~/.config/git/ignore block is removed separately (it is outside this repo). --- docs/conventions.md | 18 +++++++++++------- planner/SKILL.md | 7 +++++-- specify/SKILL.md | 7 +++++-- templates/CLAUDE.md.fragment | 20 +++++++++++--------- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/docs/conventions.md b/docs/conventions.md index 619cbc3..2d83294 100644 --- a/docs/conventions.md +++ b/docs/conventions.md @@ -28,13 +28,17 @@ elsewhere is the rare exception and states the override in its `CLAUDE.md` project facts. Both directories are **git-ignored** — their contents are transient -working files, never committed (see § Lifecycle). The plugin ignores -them **globally**: `docs/specs/` and `docs/plans/` live in the user's -global git excludes (`~/.config/git/ignore`, the XDG default that -applies to every repo), next to the other plugin-wide ignores. One -entry set covers every project — no per-project `.gitignore` line is -needed, though a project MAY add its own if it wants the ignore to -travel with the repo. +working files, never committed (see § Lifecycle). The ignore is +**project-local**: each project commits a `docs/.gitignore` whose two +entries, `specs/` and `plans/`, are relative to `docs/` and so match +`docs/specs/` and `docs/plans/`. That `docs/.gitignore` is the one +committed thing — it travels with the repo; the spec and plan files it +covers never are. It is created once (whichever skill first writes a +working file — `specify`, or `planner` on a direct tidy dispatch — +creates it if absent) and committed like any other repo file, after +which both directories are invisible to git for every future cycle. A +new project sets this up as part of adopting the plugin +(`../templates/CLAUDE.md.fragment`). ## Lifecycle diff --git a/planner/SKILL.md b/planner/SKILL.md index c5e35d8..8dc7c8f 100644 --- a/planner/SKILL.md +++ b/planner/SKILL.md @@ -17,8 +17,11 @@ into bites small enough that a subagent can execute one in Plans live under `docs/plans` with the fixed name shape `slug.md` — a short kebab-case topic slug, no numeric prefix (see -`docs/conventions.md` § Naming). `docs/plans` is git-ignored; the plan -is a working file, never committed. The header references the parent spec. The plan +`docs/conventions.md` § Naming). `docs/plans` is git-ignored via the +project's committed `docs/.gitignore`; on a direct tidy dispatch that +skipped `specify`, create that file first if it does not yet ignore +`plans/` (`docs/conventions.md` § File layout). The plan is a working +file, never committed. The header references the parent spec. The plan decomposes work into tasks; each task is the unit of review (spec-compliance and quality gates inside `implement`), not the unit of commit. Commits are an orchestrator-only decision diff --git a/specify/SKILL.md b/specify/SKILL.md index 4551efe..9df73b3 100644 --- a/specify/SKILL.md +++ b/specify/SKILL.md @@ -270,8 +270,11 @@ it as a target, not verified fact. Path: under `docs/specs`, named `slug.md` per the fixed naming convention (see `docs/conventions.md` § Naming) — a short kebab-case -topic slug, no numeric prefix. The directory is git-ignored; the spec is -created there and never `git add`'d. +topic slug, no numeric prefix. The directory is git-ignored via the +project's committed `docs/.gitignore`; if that file does not yet ignore +`specs/`, create it first (`docs/conventions.md` § File layout) so the +spec can never be committable. The spec is created under `docs/specs` and +never `git add`'d. Structure: diff --git a/templates/CLAUDE.md.fragment b/templates/CLAUDE.md.fragment index 826114d..294514e 100644 --- a/templates/CLAUDE.md.fragment +++ b/templates/CLAUDE.md.fragment @@ -68,15 +68,17 @@ Two rules govern who touches git history and how: ## Spec/plan artefacts are ephemeral `docs/specs/` and `docs/plans/` hold only the active cycle's spec and -plan, and they are **git-ignored** — never committed (the plugin ignores -them globally via `~/.config/git/ignore`; a project may add its own -`.gitignore` entry instead). Each is a working file: present on disk for -its own cycle, read across sessions from the working tree, and -shell-`rm`'d at cycle close by `audit` — never `git rm`'d, since it was -never tracked, so no git-history copy survives. The durable record of a -past cycle is the design ledger only, and no durable artefact cites a -spec/plan by number or path (see `~/dev/skills/docs/conventions.md` -§ Lifecycle). +plan, and they are **git-ignored** — never committed. This repo carries +a committed `docs/.gitignore` with the two entries `specs/` and `plans/` +(relative to `docs/`); that ignore file is the only committed part — the +spec and plan files it covers never are. If `docs/.gitignore` is missing +when the first working file is written, the writing skill creates it. +Each spec / plan is a working file: present on disk for its own cycle, +read across sessions from the working tree, and shell-`rm`'d at cycle +close by `audit` — never `git rm`'d, since it was never tracked, so no +git-history copy survives. The durable record of a past cycle is the +design ledger only, and no durable artefact cites a spec/plan by number +or path (see `~/dev/skills/docs/conventions.md` § Lifecycle). ## Design rationale ≠ implementation effort