Skip to content

feat: content-hash staleness, state.json tracked + durable across clones - #1

Merged
decider merged 1 commit into
mainfrom
feat/content-hash-staleness
May 23, 2026
Merged

feat: content-hash staleness, state.json tracked + durable across clones#1
decider merged 1 commit into
mainfrom
feat/content-hash-staleness

Conversation

@decider

@decider decider commented May 23, 2026

Copy link
Copy Markdown
Owner

Why

Previous mtime-based staleness broke across fresh clones: git rewrites every file's mtime to the moment of checkout, so a freshly-cloned repo with committed state.json would treat every dir as file-modified and re-bootstrap. ~$0.40+ wasted per cold checkout — a real adoption blocker.

Fix

state.directories[rel].files now stores sha256 content hashes instead of mtime ints. Hashes survive clones, file copies, line-ending normalization, and unrelated mtime touches. Only actual content changes trigger re-analysis.

state schema bumped to version: 2. Old v1 states drop silently on load — one-time bootstrap on upgrade, then incremental refresh resumes.

.gitignore lets state.json through so it travels with the repo. Committed the bootstrapped state for this repo (3 dirs).

Tests (60 passing, +1 net)

  • New: needsAnalysis returns null when mtime changes but content is identical — explicitly proves the fresh-clone scenario doesn't re-bootstrap.
  • Renamed: file-modified when mtime changesfile-modified when CONTENT changes; rewritten to mutate via writeFileSync.
  • All utimesSync-as-staleness-trigger tests rewritten to mutate content.
  • All other tests unchanged.

Public API

Unchanged. selectFiles internally returns { name, full, hash, size } instead of { name, full, mtimeMs, size } — only the test suite touches that shape.

The previous mtime-based staleness check broke across fresh clones:
git rewrites every file's mtime to the moment of checkout, so a freshly-
cloned repo with a committed state.json would treat every dir as
'file-modified' and re-bootstrap (~$0.40+ per cold checkout).

Switches state.directories[rel].files from mtime ints to sha256 content
hashes. Hashes survive any number of clones, file copies, line-ending
normalizations, or unrelated mtime touches. Only an actual content
change triggers re-analysis.

State schema bumped to version: 2. Old v1 states are silently dropped
on load — anyone upgrading runs one bootstrap to populate v2, then
incremental refreshes resume normally.

Also removed .docgen/state.json from .gitignore so it travels with
the repo. Committed the freshly-bootstrapped state for this repo
(three dirs: .github, .github/workflows, hooks).

Tests:
  - new test: 'mtime changes but content identical → null' explicitly
    asserts the fresh-clone scenario doesn't re-bootstrap
  - existing 'file-modified when mtime changes' renamed to ' … when
    content changes' and rewritten to mutate content via writeFileSync
  - all utimesSync-as-staleness-trigger tests rewritten to mutate
    content instead
  - 60 tests passing (was 59; the new mtime-doesn't-matter test added)

CLI/API surface unchanged. selectFiles now returns { name, full, hash,
size } instead of { name, full, mtimeMs, size }, which is internal to
the module (tests update accordingly).
@decider
decider merged commit fe6e3ee into main May 23, 2026
1 check passed
@decider
decider deleted the feat/content-hash-staleness branch May 23, 2026 06:07
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