Skip to content

feat: conditional .gitignore for two-layer/orphan state files (defense-in-depth) #1228

Closed

Description

@tamirdresher

Summary

When \stateBackend\ is \ wo-layer\ or \orphan, .squad/decisions.md\ and .squad/agents/*/history.md\ are owned by the \squad-state\ orphan branch. The pre-commit hook (\install-hooks.ts:110-127) refuses to commit them, but they can still be staged via \git add ., \git add -A, IDE \stage all\ gestures, or \git commit -am. The user then hits the pre-commit refusal and has to manually recover (see #1226 / PR #1227 for the documented recovery flow).

Proposal: add those paths to .gitignore\ automatically when the backend is \ wo-layer\ or \orphan, and remove them when switching back to \local. The pre-commit hook stays — defense in depth.

Behavior

From To .gitignore\ action
init or migrate → \local\ local no-op (current behavior preserved)
init or migrate → \orphan\ orphan add marker block
init or migrate → \ wo-layer\ two-layer add marker block
orphan ↔ two-layer (either) no-op
orphan/two-layer → \local\ local remove marker block

Entries added (delimited by marker comments so the block can be reliably removed):

\\

Squad: state owned by squad-state branch (two-layer/orphan backend)

.squad/decisions.md
.squad/agents/*/history.md

/Squad: state owned by squad-state branch

\\

Rationale

  1. Defense in depth. The pre-commit hook is a runtime guard; .gitignore\ is a configuration guard. Together they make the wrong thing actually hard to do.
  2. Removes the failure mode. Users won't stage these files via \git add .\ / IDE gestures → no pre-commit refusal → no recovery needed.
  3. Architecturally consistent. Under two-layer, on-disk copies are caches; caches belong in .gitignore\ (same logic as the existing .squad/.cache/\ entry).
  4. Atomic add/remove. The marker comments make the block trivially findable for the symmetric remove when switching backends.

Out of scope

  • The pre-commit hook STAYS (it still catches \git add --force\ and edge cases).
  • \local\ backend behavior is unchanged.

Acceptance criteria

  • \initSquad({stateBackend: 'two-layer'})\ writes the marker block to .gitignore\
  • \initSquad({stateBackend: 'orphan'})\ writes the marker block to .gitignore\
  • \initSquad({stateBackend: 'local'})\ does NOT write the marker block (current behavior preserved)
  • \squad upgrade --state-backend two-layer\ from local backend adds the block
  • \squad upgrade --state-backend orphan\ from local backend adds the block
  • \squad upgrade --state-backend local\ from two-layer/orphan removes the block
  • Idempotent — running upgrade twice doesn't duplicate entries
  • Round-trip (add then remove) leaves .gitignore\ byte-identical (modulo trailing newline)
  • Unit + integration tests cover all of the above
  • Documentation updated (\state-backends.md\ and \ roubleshooting.md\ — coordinate with PR docs(state-backends): document pre-commit hook, post-upgrade flow, and recovery #1227)
  • Changeset added

Related

Activity

  1. added a commit that references this issue on Jun 14, 2026
    ae50957
  2. added a commit that references this issue on Jun 14, 2026
    567f447
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions