Skip to content

init --here destroys user .gitignore on brownfield projects #6

@kevinkod

Description

@kevinkod

Why

Brownfield adoption is Specflow's highest-value use case: developers already have a working project (Vite, Next, Astro, etc.) and want to layer Specflow on top with `specflow init --here`. Every JS frontend scaffolder creates a `.gitignore`. Today Specflow treats that file as a managed conflict, blocking `init --here` at exit code 3, and silently destroying the user's ignore rules when they push through with `--force`. The architect's design call (2026-05-01) resolved the implementation strategy — a new `mergeable-project-root` bundle category with marker-fenced append-blocks — so this item is ready for implementation.

Acceptance criteria

  • `specflow init --here` (no flags) on a Vite-style project whose `.gitignore` was NOT written by Specflow exits 0 without requiring `--force`.
  • The resulting `.gitignore` contains both the user's original lines (e.g. `node_modules/`, `dist/`, `.DS_Store`) AND Specflow's entries, with Specflow's block fenced by `# --- Specflow: gitignore ---` / `# --- End Specflow: gitignore ---` markers.
  • Re-running `specflow init --here` (with or without `--force`) on a project that already has the fenced Specflow block does not duplicate it — the block is replaced in-place (idempotent).
  • When Specflow's block content changes between versions, `specflow upgrade` replaces only the fenced block; user-authored lines above and below the block are left untouched.
  • Greenfield `init` (empty dir, no pre-existing `.gitignore`) produces a `.gitignore` with the same marker-fenced shape — the output format is identical whether the file pre-existed or not.
  • `detectConflicts` does not return `.gitignore` when `mergeBlock` is set on its `TemplateFile` — it is never treated as a conflict candidate.
  • `--force` continues to apply normally to all non-mergeable managed files; mergeable files ignore the flag entirely because merge is non-destructive by design.
  • The lock file records the SHA of the Specflow block content alone (not the full merged file), so `upgrade` can distinguish spec changes from user-added lines.
  • All 8 harnesses in `src/infrastructure/harness/` handle the new `mergeable-project-root` category consistently — no harness-specific divergence in `.gitignore` merge behavior.
  • A new integration test (`tests/integration/init_brownfield_gitignore_test.ts`) covers: greenfield shape, brownfield merge, idempotent re-run, and upgrade block-replace.

Out of scope

  • Extending merge semantics to any other file (`AGENTS.md`, `tasks/backlog.md`, `.editorconfig`, `README.md`, etc.) — those keep their current replace or preserve-if-customized logic unchanged.
  • A user-configurable list of mergeable files.
  • Backward-compatible migration of existing brownfield projects where a prior `--force` already destroyed the `.gitignore` (those users have a `.gitignore.specflow.bak` and must merge manually — not in scope here).
  • Detecting which scaffolder authored the pre-existing `.gitignore`.

Notes

Architecture spec: `docs/superpowers/specs/2026-05-01-gitignore-merge-fix.md` — full design rationale, data-flow diagram, and edge-case decisions live there.

Files the implementation must touch (per architect's 2026-05-01 design call):

  • `src/domain/core_bundle.ts` — extend `CoreCategory` union with `"mergeable-project-root"`
  • `src/domain/template.ts` — add `mergeBlock?: string` to `TemplateFile`
  • `templates/manifest.json` — change `.gitignore` category to `mergeable-project-root`
  • `templates/core/root/.gitignore` — body-only content (markers are adapter-injected, not stored in the template)
  • All 8 harness files in `src/infrastructure/harness/` — add `case "mergeable-project-root"`
  • `src/infrastructure/deno_fs_writer.ts` — skip-in-detect logic + marker-aware merge-write
  • `src/application/init_project.ts` — fix lock SHA computation to use block content for `mergeBlock` entries
  • `tests/integration/init_brownfield_gitignore_test.ts` — new integration test (all four scenarios above)

Reproduction case: `test/vite-demo/` (gitignored locally) — `npm create vite@latest vite-demo -- --template react-ts` then `specflow init --here --no-git` reproduces exit-code-3 block; `--force` reproduces destructive overwrite.

Brownfield breakage shipped in v0.6.x. v0.7.0 (`dcaae33`) added a friendlier error message but did not fix detection or merge semantics. The `.gitignore.specflow.bak` safety net is not a fix — it is a workaround that still loses the conflict-detection skip.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions