Skip to content

Commit 40037b1

Browse files
mnriemCopilot
andauthored
feat(init): scaffold managed .specify/.gitignore (#4000)
* feat(init): scaffold managed .specify/.gitignore 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 #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 * docs: correct .specify/.gitignore uninstall claim The file is tracked in the shared-infra manifest (speckit.manifest.json), not the per-integration manifest that `specify integration uninstall` loads. Shared infrastructure is deliberately preserved on uninstall (see test_uninstall_preserves_shared_infra), so `.specify/.gitignore` is left in place rather than removed. Reword the code comment and core.md note to state the actual behavior; keep the true benefits (force-refresh and preserve-on-edit). 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 * revert: drop manual CHANGELOG.md edit CHANGELOG.md is auto-generated; do not hand-edit it. 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 * test: add .specify/.gitignore to integration file inventories The complete-file-inventory tests assert an exact match of every file produced by `specify init`. Now that shared infra scaffolds a managed `.specify/.gitignore`, add it to the expected inventories so the exact-match assertions pass on both sh and ps script types. 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
1 parent 3dff6f1 commit 40037b1

10 files changed

Lines changed: 161 additions & 0 deletions

docs/reference/core.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ specify init my-project --integration copilot --preset compliance
5757

5858
> **Two resolution axes.** `SPECIFY_INIT_DIR` selects the **project** (which directory contains `.specify/`); `SPECIFY_FEATURE_DIRECTORY` / `.specify/feature.json` select the **feature** within that project. They are independent — project first, then feature.
5959
60+
> **Version control.** `specify init` scaffolds a managed `.specify/.gitignore` that excludes machine-local state — `feature.json` (the current-feature pointer, rewritten on every feature switch) and per-machine extension `extensions/*/local-config.yml` overrides — while leaving everything else under `.specify/` (constitution, templates, scripts, extension config) shareable so teams stay aligned. Like the rest of `.specify/`'s shared scripts and templates, the file is tracked in the shared-infrastructure manifest: your edits are preserved on re-init and `specify init --here --force` restores the managed content. It is intentionally left in place by `specify integration uninstall`, which only removes the uninstalled agent's own files.
61+
6062
> **Symlinked project roots.** `SPECIFY_INIT_DIR` relocates *where* the project is, not *how* a command treats symlinks: each command keeps its existing cwd-path stance. Commands that traverse and write project files through broad input paths (`bundle`, `workflow run <file>`) refuse a symlinked `.specify/` to preserve write confinement. Other project-scoped commands keep their existing behavior when `SPECIFY_INIT_DIR` points at a project root, which may include following a symlinked `.specify/`.
6163
6264
## Check Installed Tools

src/specify_cli/shared_infra.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@
1616

1717
logger = logging.getLogger(__name__)
1818

19+
# Managed ``.specify/.gitignore``. Keeps machine-local Spec Kit state out of
20+
# version control while leaving shareable project files (specs, constitution,
21+
# templates, scripts, extension config) tracked. Patterns are relative to the
22+
# ``.specify/`` directory the file lives in.
23+
SPECIFY_GITIGNORE_CONTENT = """\
24+
# Machine-local Spec Kit state — not meant to be shared.