Skip to content

feat: add adopt command to move existing files into the overlay - #7

Merged
lollipop-onl merged 1 commit into
mainfrom
feat/adopt
Jul 17, 2026
Merged

feat: add adopt command to move existing files into the overlay#7
lollipop-onl merged 1 commit into
mainfrom
feat/adopt

Conversation

@lollipop-onl

Copy link
Copy Markdown
Member

Summary

Setting up a new overlay file today is manual: mkdir the overlay entry
directory, cp/mv the file into it, then run ghostq apply. This friction
discourages moving existing personal files into the overlay after the fact,
even though the overlay is meant to be the durable source of truth for
gitignored, per-repo files.

ghostq adopt <file>... collapses this into one step per file:

  • Resolve the file's repo context via resolveContext (same identity
    resolution as apply/status), rejecting files outside a git repo or in a
    repo with no resolvable remote identity.
  • Move the real file into the mirrored path under the repo's overlay entry
    (fs.rename, with a copy+unlink fallback for EXDEV when the overlay lives
    on a different filesystem).
  • Symlink the original checkout path back at the moved file — the same shape
    of link apply produces, so a subsequent ghostq apply on that file is a
    no-op (alreadyLinked), verified directly in tests.

Safety guarantees

Per invariants in AGENTS.md:

  • Gitignore-gated: a target that fails git check-ignore is skipped with
    a warning — adopt never moves a tracked file into the overlay.
  • Never clobber: if the overlay already has a file at the target path, or
    the checkout path is already a symlink pointing somewhere else, the file is
    skipped and reported, not overwritten.
  • Already-adopted is a no-op: a file already symlinked into this repo's
    overlay entry is reported as already-adopted rather than re-processed.
  • symlink-each, never whole directories: passing a directory is rejected
    with a message to pass individual files instead, matching how apply links
    files individually so committed and overlay-linked files can sit side by
    side in the same directory.
  • All new logic lives in src/adopt.ts and returns a data structure per file
    (adopted / already-adopted / skipped-not-ignored / skipped-conflict
    / skipped-directory / error); only src/index.ts prints, consistent with
    the rest of the codebase.

Test plan

  • bun install
  • bun test — 42 pass (adds test/adopt.test.ts covering: move +
    symlink + content preservation, idempotency bridge with applyFiles,
    tracked-file skip, already-adopted no-op, overlay-conflict skip,
    foreign-symlink skip, directory rejection)
  • bun run typecheck
  • bun run build

🤖 Generated with Claude Code

https://claude.ai/code/session_013mAzjNvVWGBJT81cfTSJJN

Setting up a new overlay file today is manual: mkdir the entry, cp/mv
the file over, then run apply. That friction discourages moving files
into the overlay after the fact, even though the overlay is meant to
be the source of truth for gitignored personal files.

adopt collapses this into one step per file: verify the target is
gitignored (never touch tracked files) and not already a symlink
elsewhere or shadowing existing overlay content, move it into the
mirrored overlay path, and symlink it back — leaving the file in the
exact "linked" state apply would produce, so a subsequent apply is a
no-op.

Directories are rejected rather than recursed, matching symlink-each:
ghostq never links whole directories, and a directory can hold
committed files alongside overlay candidates that need individual
gitignore review.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013mAzjNvVWGBJT81cfTSJJN
@lollipop-onl
lollipop-onl merged commit cee815b into main Jul 17, 2026
1 check passed
@lollipop-onl
lollipop-onl deleted the feat/adopt branch July 17, 2026 14:58
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