Repository navigation
fix(project): record receipts on project init so files and rules refresh - #193
Conversation
A files: artifact created through one path is user-authored to the other. Refs: sideshow#91
arcavenai
left a comment
There was a problem hiding this comment.
Approving. With this change, a repo set up with project init picks up a pack's corrected files and rules. A user's edit is kept and the reason is printed, and neither order of the two paths overwrote an edit in my runs. Before this, a pack fix never reached such a repo. On main, the issue repro still reproduces: v1 then v2 leaves a.cfg and the rule at v1, the registry stays empty, and the run prints "0 wrote, 2 already present" with no reason.
What I ran. I built both binaries (main 3cd4e2d, head 0f10ae4) and used a scratch HOME and SIDESHOW_HOME with the network denied. A test pack ck ships one files: artifact (a.cfg) and one rule (.claude/rules/r1.md) at v1, v2 and v3.
| scenario (single-repo path) | head |
|---|---|
| v1, then v2 | file and rule updated, receipts move to v2; a third run reports "already current" for both |
| edit both, then v2, then v3 | both kept with "modified since sideshow wrote it (user edit preserved)"; receipts stay at v1 |
edit the file, then v2, --dry-run |
the edit is kept with its reason printed; the registry is byte-identical before and after; the next real run updates the rule only |
| user-authored file before the first run | kept with "exists and sideshow has no record of writing it (user-authored)" at v1 and v2 |
| file hand-set to the v2 bytes, then v2 | "already current" and the receipt moves; v3 then updates it |
| file deleted after v1 | recreated; v2 updates it |
v1 through the real path, v2 through a symlinked cwd (PWD set to the link) |
updated; one installation, root is the resolved path, repo name from it |
No run wrote .sideshow/project.yaml inside the repo. The only one is at the orchestrator root, written by the init --scope project path, which this PR does not touch.
The files: two-path case, both orders.
- cwd path first: the orchestrator path reads the file as user-authored at every version and records no file receipt. A later edit is kept by both paths.
- orchestrator path first: the cwd path reads it as user-authored with no receipt. The orchestrator path refreshes it at v2, and a later edit is kept by both.
- Created by the cwd path, deleted, then recreated by the orchestrator path: both paths hold a receipt. A later edit is still kept by both, because each path compares the bytes on disk to its own receipt, and an edit matches neither.
In no order was a user edit overwritten or lost, and the body's limit sentence matches what I saw.
Key stability (SingleRepoKey). These forms all give the same id and root:
- the absolute path, with one or two trailing slashes
- a symlink to the repo, with a trailing slash
- a symlink to that symlink
..,../and./..from a subdirectorya/sub/..
Two repos with the same basename under different parents get different ids. A repo with an identity file keeps its id after a move, with a new root, which is the accepted limit. The key derivation writes nothing into the repo.
Red and mutants.
- Red at e1bc1f8: all 8 new tests fail on assertions, 40 of 40 across 5 runs. Head passes
./..., and the new tests pass with-count=20. - Mutants: each of the 9 listed is killed, by the tests the body names. So are two I added: skipping
reg.Save(), and keying on the unresolved cwd. On 0f10ae4, the adopt-equal-bytes mutant (case !known && onDisk != sourceSum) fails the new two-path test.
Unchanged against main. 0f10ae4 adds only the test; no production code differs from 58437fa. The orchestrator path's output is byte-identical between the main and head binaries in my eight files: scenarios and eight rules: scenarios. CI on 0f10ae4 passes.
Non-blocking: the reason printed after a two-key rule lag is wrong. The case: the cwd path records a v1 rule receipt, the orchestrator path moves the rule to v2, and the pack is at v3 before the cwd path runs again. That run then prints "modified since sideshow wrote it (user edit preserved)" for a rule nobody edited. Nothing is lost: the next orchestrator run writes v3, and the cwd path then reports "already current" and moves its receipt. Only the reason is misleading, and only for that window. The body's "already current" sentence holds when the cwd path runs at the version the orchestrator path wrote.
Seat: reviewer / reviewer
A repo onboarded with
sideshow project init <pack>kept the first version of everyfiles:artifact forever, and since #189 it no longer refreshes rules either, so a pack's corrected.editorconfig, workflow file or rule never reached it. The single-repo path now reads and records receipts in the registry, so an unedited file or rule updates on a pack bump, an edit is kept, and the reason is printed.Closes #91
Rules regression from #189, named: before it, a marker rule was overwritten by
project init, so rules refreshed on this path. #189 made a marker file with no receipt and different bytes "left as is", and this path never had a receipt, so it sat in that state permanently. This change gives it receipts.Shape, as ruled: the same registry the
init --scope projectpath uses, under a key derived from cwd alone. The id is the repo's own identity file when it has one, elsepath:plus the sha256 of the symlink-resolved absolute path; the root is that resolved path; the manifest is one fixed name (project.SingleRepoManifest).project initnever writes.sideshow/project.yaml. The repo name in the key is taken from the resolved root, so a symlinked cwd finds the same receipt.Also in this PR:
project initprinted nothing for a skipped action, so a user saw "0 wrote, 2 already present" with no reason. It now prints each skip as<type>: skipped <path>: <detail>, in the wording the conflict lines already use.Accepted limits, from the ruling:
files:artifact then reads as user-authored. A rule is adopted only when its bytes equal the current content, and otherwise skipped with the no-receipt message.TestProjectInit_TwoKeysForOneRepoAgree). Afiles:artifact is different: afiles:artifact created through one path (subrepo or cwd) is user-authored to the other; only the creating path refreshes it. Equal bytes with no receipt prove equality, not authorship, so they are never adopted. This is a named limit, ruled by the architect (valid until 2026-10-22), pinned byTestProjectInit_AFilesArtifactCreatedByOnePathIsUserAuthoredToTheOther.Migration note for the release notes: repos that ran
project initbefore this have no receipts. Theirfiles:artifacts stay user-authored, and their rules are adopted only when already current; otherwise the user deletes the file once and the next run writes it and records a receipt.Acceptance:
.sideshow; the derived key is the same from the repo path and from a symlink to it, and differs between reposCommits: (1) tests with a stub key, red; (2) the key, the wiring and the printed reasons.
Blast radius:
runProjectInitForPack, one new function and constant ininternal/project; the orchestrator path is unchanged.Mutants, each compiles and fails a test:
TestSingleRepoKey_UsesAnIdentityFileWhenThereIsOneTestSingleRepoKey_DerivedFromTheResolvedPathAndWritesNothingand the symlinked-cwd testRecordResultsis not called: killed by the pack-bump testRecordResultsruns under--dry-run: killed by the dry-run test