## ADDED Requirements

### Requirement: State Lives In-Project Under .dochygiene/ at the Resolved Root

The state store SHALL resolve the project root deterministically (the nearest
ancestor `.git` directory, falling back to the current working directory when no
git root is found) and SHALL confine all state and report writes to
`<project_root>/.dochygiene/`. It SHALL write no path outside the resolved project
root and SHALL maintain no global cross-project index.

#### Scenario: Root resolves to the git root

- **WHEN** the store resolves the project root from a directory inside a git repository
- **THEN** it returns the repository's git root and writes under `<git-root>/.dochygiene/`

#### Scenario: Root falls back to cwd

- **WHEN** the store resolves the project root from a directory with no ancestor `.git`
- **THEN** it returns the current working directory and writes under `<cwd>/.dochygiene/`

#### Scenario: Writes are confined to the project root

- **WHEN** the store writes state or a report
- **THEN** every written path is under `<project_root>/.dochygiene/` and no path outside the resolved root is written

#### Scenario: No global index exists

- **WHEN** the store operates across multiple projects
- **THEN** no global or cross-project index is created (for example no `~/.dochygiene`); each project's state is independent

### Requirement: Store Never Silently Edits .gitignore

The state store SHALL create the `.dochygiene/` directory as needed but SHALL NEVER
open, append to, or otherwise edit the user's `.gitignore`. Creating an untracked,
not-yet-ignored `.dochygiene/` directory is permitted; editing `.gitignore` is not.

#### Scenario: Creating the state dir does not touch .gitignore

- **WHEN** the store creates `.dochygiene/` for the first time
- **THEN** it writes no change to `.gitignore`

#### Scenario: Ignore-entry offer is out of scope here

- **WHEN** `.dochygiene/` is not yet ignored by git
- **THEN** the state store takes no action on `.gitignore`; surfacing an offer to add the ignore entry is owned by the `check` change

### Requirement: Lifecycle Timestamps

The state store SHALL read and write the lifecycle timestamps `last_check`,
`last_clean`, and `last_reminded` in `state.json`. Reads of an absent timestamp
SHALL be reported as unset rather than as an error.

#### Scenario: Timestamps round-trip

- **WHEN** a timestamp is written and then read back
- **THEN** the read value equals the written value

#### Scenario: Absent timestamp reads as unset

- **WHEN** a timestamp has never been written
- **THEN** reading it reports it as unset (not an error)

### Requirement: Atomic Writes

Every write to `state.json` and to report files SHALL be atomic — written to a
temporary file in the same directory and then renamed onto the target — so that a
concurrent reader observes either the prior complete file or the new complete file,
never a partially written one.

#### Scenario: A write is never torn

- **WHEN** state or a report is written while another process may be reading it
- **THEN** the reader sees either the complete old file or the complete new file, never a partial write

### Requirement: Report Rollover Keeps Only the Latest Pair

The state store SHALL retain exactly one report pair (one human `.md` and one
machine `.json`). Writing a new report SHALL delete any prior report pair before
writing the new one, so a timestamped report history never accumulates.

#### Scenario: Second report replaces the first

- **WHEN** a report pair is written and then a second report pair is written
- **THEN** only the latest `.md` + `.json` pair remains on disk and the prior pair is deleted

#### Scenario: Exactly one pair after any number of writes

- **WHEN** any number of reports have been written in sequence
- **THEN** exactly one `.md` and one `.json` report file exist in `.dochygiene/`
