{{ message }}
feat(sleep): add skillopt-sleep revert to undo an adopt - #248
Open
pravit-amp wants to merge 1 commit into
Open
Conversation
Adoption had no undo. adopt() and adopt_skills() take an immutable backup and publish a receipt, but nothing read either one back, so a night that regressed a live skill left the user hand-copying files out of a timestamped staging directory. That matters most unattended: schedule forwards --auto-adopt into the installed cron entry. revert() and revert_skills() reverse the receipts microsoft#212 already writes, which pin the live path, the sha256 before adoption ("" when no file existed), the sha256 adoption wrote, and the immutable backup. A document adoption replaced is restored from its backup; one adoption created is removed, since that is the state being returned to. Selection mirrors adopt: --skill, --all-skills, --legacy, --staging. Reverting consumes the backup and drops the night's receipt rows, so the night can be adopted again — adoption refuses to run while an immutable backup is present. It refuses when the live file no longer matches what adoption wrote: it was edited since, and restoring the backup would discard that work. Directories adoption created are left alone, because the receipt records no created_dirs and removing a path whose ownership was not durably recorded is what the adoption recovery path already refuses.
pravit-amp
force-pushed
the
feat/sleep-revert
branch
from
August 22, 2026 22:24
4d427d6 to
a3e2cf4
Compare
Contributor
Author
Contributor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Fixes #247
Summary
adopt()backed up every live file it replaced, and nothing could restore that backup — there was norevert,rollback, or undo anywhere in the CLI. Adoption can also run unattended, sincecmd_scheduleforwards--auto-adoptstraight into the installed cron entry / Scheduled Task, so a night that regressed the live skill left the user hand-copying files out of a timestamped staging directory.skillopt-sleep revert, undoing the most recentadoptfor a projectadoptfrom destroying its own backup when the same directory is adopted twicestatus, and points at the undo afteradoptContract
revertrestores the pre-adopt state of every live path the target staging directory touched.A live document that
adoptreplaced is restored frombackup/. A live file thatadoptcreated — you had no such file — is removed instead, because that is the state being returned to; leaving the proposal in place would undo nothing.adoptnow records which case each live path was in, since after adoption the file exists either way and the distinction is no longer observable.Without
--staging, the target is the most recently adopted directory that has not already been reverted — deliberately notlatest_staging(), whose newest entry may never have been adopted, in which case restoring its backup would undo a change the live files never received.--staging DIRreverts a specific night, so a user can walk back more than one step.revertraises rather than reporting a successful no-op when the target was never adopted, or when a backup the manifest claims is missing from disk.Staging directories adopted before this change carry no record, only a
backup/directory. Those are still revertible: their backup files are matched to the manifest's live paths by basename. A file such an adopt created was never backed up and is indistinguishable from one it never touched, so those are left alone rather than deleted on a guess.The backup fix is a prerequisite, not scope creep
_backup()overwrote unconditionally. Adopting the same staging directory twice copied the already-applied proposal overbackup/, losing the only copy of the user's pre-adopt document:skillopt-sleep adoptwith no--stagingresolves tolatest_staging(project), so running it twice in a row is easy to do by accident. Built on the old behavior,revertwould restore the very regression it exists to undo._backup()now leaves an existing backup alone, andadoptreuses the recorded entry for a live path instead of recomputing it against the file adopt itself just wrote.This is listed separately under Fixed in the changelog and splits cleanly into its own PR if maintainers prefer — the cut is
_backup()plus therecorded/priorbranch inadopt().Validation
tests/test_sleep_revert.py— restore, remove-created, skill+memory together, idempotent revert, double-adopt backup safety, never-adopted and missing-backup refusals, legacy staging dirs,latest_adopted_stagingselection, and CLI round-triptest_parse_jsonl_ignores_excessively_nested_json, twoTestOverlayIntegrationcases) reproduce unchanged onmainat9c776fcand are untouched by this PR — the overlay pair is a macOS/var→/private/varsymlink issue insuperpowers.py, the parser case is a deep-nesting guard that no longer fires on Python 3.13+run_sleep_cyclewithauto_adopt=True: revert removed both the createdSKILL.mdandCLAUDE.md, and a second revert exited 1 with "nothing to revert"python -m mkdocs build --strictpassed__main__.py:554incmd_schedule, left untouched to keep the diff focusedNotes for review
statusgained two printed lines and alatest_adopted_stagingJSON key. That was edge case 4 in the issue — nothing previously distinguished an adopted proposal from a staged one — and it makes the default revert target visible before you run it. Happy to drop it if you would rather keep this PR to the command alone.