Skip to content

refactor: scoped state writes preserve untouched entries#22

Merged
dhruva-reddy merged 1 commit intomainfrom
dhruva-reddy/refactor/scoped-state-writes
May 5, 2026
Merged

refactor: scoped state writes preserve untouched entries#22
dhruva-reddy merged 1 commit intomainfrom
dhruva-reddy/refactor/scoped-state-writes

Conversation

@dhruva-reddy
Copy link
Copy Markdown
Contributor

ELI5

Problem. Even when you ran a scoped push — say
npm run push -- <env> assistants/foo.md to update one assistant —
the engine rewrote the entire state file. Any pre-existing drift
in unrelated state entries (UUIDs from earlier sessions, untracked
local files, etc.) swept into the focused commit. Reviewers couldn't
tell from the state-file diff "what did this push actually change?"
and the state file became a pile of side effects accumulated across
sessions instead of a precise record of intent.

What this fix does. During a push, the engine tracks which
resourceIds it actually mutated (a per-section Set<string>). At
end-of-run, for scoped pushes only, it loads the on-disk state
fresh, replaces only the touched entries with the in-memory version,
and leaves everything else alone. Full pushes (no scope) still write
wholesale (existing behavior). Credentials are always replaced
because bootstrap pull populates them every push regardless.

This depends on Stack F's ResourceState because we need per-entry
metadata to distinguish "stale" from "just-not-touched."

Outcome you'll notice. A one-file npm run push produces a
one-file diff in the state file — same scope as the resource change.
Reviewers can read the state diff and tell "this push updated
assistant foo, here's its new hash" cleanly. Pre-existing drift
elsewhere in state stays where it is until you explicitly address it.


When push is scoped to specific paths, only update state entries for
the resources actually touched. A surgical push of two files used to
rewrite the entire state file, sweeping in pre-existing drift from
earlier pushes (improvements.md #15) and producing noisy diffs that
hide the actual scope of the change.

Files:

  • src/state-merge.ts (NEW): mergeScoped(disk, inMemory, touched).
    For each section, replace only touched.X resourceIds with the in-memory
    version; leave the rest of disk's section as-is. Credentials are
    always replaced wholesale (bootstrap pull populates them on every
    push). Pure data, no I/O — safe to test directly.
  • src/push.ts: TouchedSets tracker. Each upsertState call site
    records the resourceId. End-of-run, partial pushes call
    mergeScoped(loadState(), state, touched) before saveState; full
    pushes save wholesale (existing behavior).
  • tests/state-merge.test.ts: replace-only-touched, leave-untouched,
    drift in untouched stays, credentials always replaced.

Closes improvements.md #15.

🤖 Generated with Claude Code

@dhruva-reddy dhruva-reddy force-pushed the dhruva-reddy/feat/snapshot-rollback branch from fdf7bfb to c916a21 Compare May 1, 2026 22:56
@dhruva-reddy dhruva-reddy force-pushed the dhruva-reddy/refactor/scoped-state-writes branch from 6ebaebb to 90d7cd0 Compare May 1, 2026 22:56
@dhruva-reddy dhruva-reddy force-pushed the dhruva-reddy/feat/snapshot-rollback branch from c916a21 to 34ae2cb Compare May 2, 2026 01:24
@dhruva-reddy dhruva-reddy force-pushed the dhruva-reddy/refactor/scoped-state-writes branch from 90d7cd0 to 1b79bc3 Compare May 2, 2026 01:24
@dhruva-reddy dhruva-reddy force-pushed the dhruva-reddy/feat/snapshot-rollback branch from 34ae2cb to 9667011 Compare May 2, 2026 01:29
@dhruva-reddy dhruva-reddy force-pushed the dhruva-reddy/refactor/scoped-state-writes branch from 1b79bc3 to 0aa2c59 Compare May 2, 2026 01:29
@dhruva-reddy dhruva-reddy force-pushed the dhruva-reddy/feat/snapshot-rollback branch from 9667011 to 866b910 Compare May 2, 2026 01:33
@dhruva-reddy dhruva-reddy force-pushed the dhruva-reddy/refactor/scoped-state-writes branch from 0aa2c59 to 259746d Compare May 2, 2026 01:34
Copy link
Copy Markdown
Contributor Author

dhruva-reddy commented May 5, 2026

Merge activity

@dhruva-reddy dhruva-reddy force-pushed the dhruva-reddy/refactor/scoped-state-writes branch from 259746d to 9d8933a Compare May 5, 2026 02:14
@dhruva-reddy dhruva-reddy force-pushed the dhruva-reddy/feat/snapshot-rollback branch from 866b910 to 83242f3 Compare May 5, 2026 02:14
@dhruva-reddy dhruva-reddy changed the base branch from dhruva-reddy/feat/snapshot-rollback to graphite-base/22 May 5, 2026 02:20
@dhruva-reddy dhruva-reddy changed the base branch from graphite-base/22 to main May 5, 2026 02:21
## ELI5

**Problem.** Even when you ran a *scoped* push — say
`npm run push -- <env> assistants/foo.md` to update one assistant —
the engine rewrote the **entire** state file. Any pre-existing drift
in unrelated state entries (UUIDs from earlier sessions, untracked
local files, etc.) swept into the focused commit. Reviewers couldn't
tell from the state-file diff "what did this push actually change?"
and the state file became a pile of side effects accumulated across
sessions instead of a precise record of intent.

**What this fix does.** During a push, the engine tracks which
`resourceId`s it actually mutated (a per-section `Set<string>`). At
end-of-run, for **scoped pushes only**, it loads the on-disk state
fresh, replaces only the touched entries with the in-memory version,
and leaves everything else alone. Full pushes (no scope) still write
wholesale (existing behavior). Credentials are always replaced
because bootstrap pull populates them every push regardless.

This depends on Stack F's `ResourceState` because we need per-entry
metadata to distinguish "stale" from "just-not-touched."

**Outcome you'll notice.** A one-file `npm run push` produces a
one-file diff in the state file — same scope as the resource change.
Reviewers can read the state diff and tell "this push updated
assistant `foo`, here's its new hash" cleanly. Pre-existing drift
elsewhere in state stays where it is until you explicitly address it.

---

When push is scoped to specific paths, only update state entries for
the resources actually touched. A surgical push of two files used to
rewrite the entire state file, sweeping in pre-existing drift from
earlier pushes (improvements.md #15) and producing noisy diffs that
hide the actual scope of the change.

Files:
- src/state-merge.ts (NEW): mergeScoped(disk, inMemory, touched).
  For each section, replace only touched.X resourceIds with the in-memory
  version; leave the rest of disk's section as-is. Credentials are
  always replaced wholesale (bootstrap pull populates them on every
  push). Pure data, no I/O — safe to test directly.
- src/push.ts: TouchedSets tracker. Each upsertState call site
  records the resourceId. End-of-run, partial pushes call
  mergeScoped(loadState(), state, touched) before saveState; full
  pushes save wholesale (existing behavior).
- tests/state-merge.test.ts: replace-only-touched, leave-untouched,
  drift in untouched stays, credentials always replaced.

Closes improvements.md #15.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@dhruva-reddy dhruva-reddy force-pushed the dhruva-reddy/refactor/scoped-state-writes branch from 9d8933a to 9696d10 Compare May 5, 2026 02:22
@dhruva-reddy dhruva-reddy merged commit d908e9f into main May 5, 2026
1 check was pending
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