Skip to content

feat(init): scaffold managed .specify/.gitignore - #35

Closed
mnriem wants to merge 1 commit into
mainfrom
mnriem-feat-2304-scaffold-specify-gitignore
Closed

feat(init): scaffold managed .specify/.gitignore#35
mnriem wants to merge 1 commit into
mainfrom
mnriem-feat-2304-scaffold-specify-gitignore

Conversation

@mnriem

@mnriem mnriem commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Summary

Answers the recurring "what should I commit under .specify?" question (discussion #2304) by scaffolding a managed .specify/.gitignore at init time. It excludes machine-local state while leaving everything shareable tracked, so teams get sensible defaults out of the box instead of having to reverse-engineer which files are session-local.

Ignored (machine/session-local):

  • feature.json — the current-feature pointer, rewritten on every feature switch (per-checkout state, sharing it only causes churn/merge noise).
  • extensions/*/local-config.yml — per-machine extension config overrides (already documented as gitignored in ConfigManager).

Everything else under .specify/ (constitution, templates, scripts, extensions.yml, project-level extension config) stays tracked so a team shares a consistent setup.

Implementation

  • Writes the file in install_shared_infra (src/specify_cli/shared_infra.py), routed through the same overwrite/skip/preserve policy as shared templates:
    • manifest-tracked → specify integration uninstall removes it;
    • user edits preserved on plain re-init;
    • specify init --here --force restores the managed content.
  • Scoped to a .specify/.gitignore (patterns relative to that dir) rather than touching the project-root .gitignore, so the CLI never clobbers a file the user owns.

Why a nested .specify/.gitignore and not a root-level merge?

This was a deliberate design choice over merging entries into the project-root .gitignore:

  • Functionally complete. A .gitignore inside .specify/ is a first-class git mechanism — git check-ignore correctly ignores .specify/feature.json and .specify/extensions/*/local-config.yml. Root placement would only add discoverability, not correctness.
  • Keeps the manifest model clean. The shared-infra installer tracks whole-file SHA-256 hashes, which is what makes uninstall safe and "preserve on user edit" work. A nested file we fully own fits that contract exactly. Merging into a user-owned root .gitignore would require a delimited managed block plus custom idempotent-merge and surgical-removal logic, and any unrelated user edit would change the file hash and break the manifest's preserve/refresh behavior.
  • Consistent with existing CLI behavior. The CLI today only suggests the user add their agent folder to .gitignore (see init.py) — it never auto-edits the root file. The only marker-block-in-a-user-file pattern in Spec Kit is the agent-context extension, which is deliberately opt-in because it owns that complexity.

Note (independent of file location): a .gitignore does not untrack files that were already committed — a feature.json committed before upgrading still needs a manual git rm --cached.

Tests

New tests/test_shared_infra_gitignore.py covers: file creation + manifest tracking, real git check-ignore behaviour for the target paths (and that a shareable file like constitution.md is not ignored), user-edit preservation, and --force restore. Existing shared-infra / init / manifest suites pass (130 passed, 2 skipped locally).

Docs

Added a "Version control" note to docs/reference/core.md and a CHANGELOG entry.


Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous). Opened on behalf of @mnriem.

Write a manifest-tracked `.specify/.gitignore` during shared-infra install
so machine-local Spec Kit state stays out of version control while everything
else under `.specify/` remains shareable:

- `feature.json` — the current-feature pointer, rewritten on every feature
  switch (per-checkout state, not something to share).
- `extensions/*/local-config.yml` — per-machine extension config overrides.

The file is routed through the same overwrite/skip/preserve policy as shared
templates: `--force` refreshes it, user edits are preserved on re-init, and
uninstall removes it via the manifest. Addresses github#2304.

Assisted-by: GitHub Copilot (model: Claude Opus 4.8, autonomous)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 98faefd1-9fc8-48fc-bd25-d4f3ccbb2ab9
@mnriem

mnriem commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

Superseded by the upstream PR github#4000, which targets github/spec-kit:main. Closing this fork-targeted PR.

@mnriem mnriem closed this Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant