Instructions for AI agents working on this codebase.
Core mechanism: git worktree (vendoring) + rsync (syncing). Three parallel implementations exist with command parity.
| Layer | Location | Language | CLI Framework | Git Interface | Status |
|---|---|---|---|---|---|
| Go (primary) | src-go/main.go |
Go | Cobra | gogs/git-module + os/exec |
Production |
| Just + Fish | Justfile.cross |
Fish/Bash | Just | git CLI + jq |
Reference |
| Rust (experimental) | src-rust/src/main.rs |
Rust | Clap | git2 (minimal) + duct |
WIP |
Both Go and Rust are single-file implementations (~1500 lines each). Justfile.cross is ~940 lines.
Justfile # Root wrapper: delegates `just cross <cmd>` to Justfile.cross
Justfile.cross # Shell/Fish reference implementation (all commands)
Crossfile # User-facing state: records use/patch commands for replay
src-go/main.go # Go CLI (single file, Cobra-based)
src-go/go.mod # Go module (go 1.23)
src-rust/src/main.rs # Rust CLI (single file, Clap-based)
src-rust/Cargo.toml # Rust dependencies
.git/cross/ # Internal state directory
metadata.json # Patch registry: {patches: [{remote, remote_path, local_path, worktree, branch}]}
worktrees/ # Hidden git worktrees for each patch
test/common.sh # Shared test helpers (setup_sandbox, create_upstream, assertions)
test/run-all.sh # Test runner: discovers and executes test/NNN_*.sh files
test/001-017_*.sh # Individual test files (see coverage matrix below)
| Command | Args | Purpose |
|---|---|---|
use |
<name> <url> |
Register remote, detect default branch |
patch |
<remote[:branch]:path> [local_path] |
Sparse-checkout worktree, rsync to local |
sync |
[path] |
Pull upstream, rebase, rsync back to local |
diff |
[path] |
git diff --no-index between worktree and local (context-aware: auto-detects patch from CWD) |
list |
Table of remotes and patches | |
status |
Health: local diffs, upstream divergence, conflicts | |
push |
[path] |
Rsync local to worktree, commit, push upstream |
replay |
Re-execute Crossfile to reconstruct environment | |
remove |
<path> |
Delete patch, clean Crossfile and metadata |
prune |
[remote] |
Remove remote and all its patches, prune stale worktrees |
exec |
<cmd> |
Run arbitrary command (for Crossfile hooks) |
init |
Create empty Crossfile | |
cd |
[path] |
Open shell in local_path (or fzf select + clipboard) |
wt |
[path] |
Open shell in worktree (or fzf select + clipboard) |
| Aspect | Just/Fish | Go | Rust |
|---|---|---|---|
| Readability | Best. Concise, intent-clear | Good. Cobra structure helps | Good. Match arms are clean |
| Error handling | Weak. Fish has no set -e; many unchecked returns |
Weak. 14 loadMetadata errors silently discarded |
Mixed. anyhow/? is good, but 12 let _ = suppressions |
| Testability | Tested via Just invocation | Self-contained binary | Self-contained binary |
| Distribution | Requires just + fish + jq + rsync |
Single static binary + rsync |
Single binary + rsync |
| Maintainability | Simple to modify, fast iteration | Single 1509-line file, needs decomposition | Single 1520-line file, needs decomposition |
| Topic | Just/Fish | Go | Rust |
|---|---|---|---|
| Worktree hash | md5sum(local_path) — branch NOT included |
SHA256(remote+path+branch)[:8] — no field separator |
DefaultHasher(canonical+branch)[:8] — non-deterministic across Rust versions |
| Git interface | Direct git CLI everywhere |
Mixed: gogs/git-module + os/exec + git.Open() — 3 styles interleaved |
git2 for 2 commands only, duct/run_cmd for rest — git2 is dead weight |
Metadata id field |
Written via jq | Not in struct — silently dropped on save | Present with #[serde(default)] |
| Crossfile removal | grep -v "patch" — removes ALL lines with "patch" |
strings.Contains(line, "patch") && strings.Contains(line, localPath) — fragile |
Same as Go — fragile substring match |
init path |
CWD (no pushd REPO_DIR) |
CWD (hardcodes "Crossfile") |
CWD (hardcodes "Crossfile") |
push no-arg |
Requires explicit path or CWD in patch | Silently selects first patch | Silently selects first patch |
- Justfile.cross: Clearest intent.
_resolve_context2is elegant.update_crossfileis a 2-line recipe. The CWD-based auto-detection viaUSER_CWD+jqis the most correct approach. - Go:
updateCrossfile()has the best deduplication (3-way prefix matching).resolvePathToRepoRelative()handles macOS/tmp->/private/tmpsymlinks.detectDefaultBranch()has the most robust fallback chain. - Rust:
parse_patch_spec()handles the most edge cases.anyhowerror context is better than Go's rawfmt.Errorf.select_patch_interactive()has the cleanest fzf integration.
cd/wtcode duplication: Near-identical blocks in Go (80 lines) and Rust (56 lines). Should be a single parameterized function.remove/prunecode duplication: Prune copy-pastes remove logic. Should call a sharedremovePatch()helper.syncis a god-method: 159 lines (Go), 192 lines (Rust), 166 lines (Just). Needs decomposition into: stash, sync-to-worktree, pull-rebase, detect-deletions, sync-from-worktree, unstash.- Crossfile removal is fragile: All three use substring matching that can corrupt unrelated lines.
loadMetadataerrors universally ignored: Corrupted metadata.json silently treated as empty.
Tests 001-007 form a chain for Shell/Just (001 is sourced by 002, etc.). Tests 008 (Rust) and 009 (Go) are self-contained monolithic tests. Tests 010-017 are focused feature tests.
| Command | Shell (tests) | Go (tests) | Rust (tests) |
|---|---|---|---|
use |
001 | 009 | 008 |
patch |
002 | 009 | 008 |
sync (basic) |
004 | 009 | 008 |
sync (edge cases: conflicts, stash, deletion) |
004 | -- | -- |
diff (basic) |
003 | 009 | 008 |
diff (context-aware CWD) |
003, 016 | 016 | -- |
diff (relative paths) |
003 | -- | -- |
list |
(017) | 009, 014 | 008, 014 |
status (all states) |
007 | 009* | 008* |
push (basic) |
006 | 009 | 008, 011 |
push (custom msg/branch/force) |
006 | -- | 011 |
replay |
005 | 009 | 008 |
remove |
014 | 014 | 014 |
prune |
015 | 009 | 008 |
cd/wt |
017 | 017 | 017 |
exec |
005* | -- | -- |
init |
-- | 009 | 008 |
sparse checkout |
012 | 012 | 012 |
sbx/sandbox workflow |
-- | 018 | -- |
* = partial/basic assertions only. -- = not tested.
- Go/Rust sync edge cases: conflicts, uncommitted changes, file deletion — only tested in Shell.
- Go push with custom message/branch/force: only Shell and Rust test these.
- Rust context-aware diff: not tested (Go and Shell are).
execcommand: only tested indirectly through replay.listfor Shell: no dedicated test.
- Read this file and
TODO.mdto understand priorities and constraints. - Run existing tests to establish a baseline:
just cross-testorbash test/run-all.sh. - Understand the three implementations — changes must land in all three unless the scope is explicitly limited (e.g., "fix Go only").
- Check the coverage matrix above to know what's tested where.
Order of implementation:
- Justfile.cross first — it's the simplest to iterate on, and serves as the reference for behavior.
- Go second — primary production implementation. Ensure it matches the Just behavior.
- Rust third — experimental. Port from Go since the structure is closer.
- Write/update tests — at minimum, add assertions to 008 (Rust) and 009 (Go). For Shell, add to the relevant 00X test or create a new one.
- Update documentation —
TODO.md,CHANGELOG.md, this file if the change affects architecture.
Order of verification:
bash test/NNN_specific.sh— run the specific test for the feature.bash test/003_diff.shandbash test/004_sync.sh— most likely to regress.bash test/run-all.sh— full regression.
- Fix in all three implementations unless the bug is implementation-specific.
- If fixing a shared pattern (e.g., Crossfile removal), fix the pattern once and port to all three.
- Add a regression test.
Do:
- Propagate errors explicitly. Use
return err(Go),?(Rust),or exit 1(Fish). - Use the established helpers:
getRepoRoot()/get_repo_root(),loadMetadata()/load_metadata(),updateCrossfile()/update_crossfile(). - Use constants for paths:
.git/cross/worktrees/,Crossfile,.git/cross/metadata.json. - Match the existing code style of each implementation (Cobra commands in Go, Clap derive in Rust, Fish recipes in Just).
Don't:
- Add new dependencies to Rust (git2 is already dead weight — prefer
ductCLI calls). - Use
gogs/git-modulefor new Go code (preferos/execfor consistency — the mixed approach is a known problem). - Make Justfile.cross more complex to accommodate programmatic concerns — keep it readable and concise.
- Silently discard errors with
_ =/let _ =/ ignoring return values. Log them at minimum. - Duplicate logic between commands (especially
remove/pruneandcd/wt).
- Tests use
setup_sandboxandcreate_upstreamfromtest/common.sh. - Shell tests may chain-source earlier tests (e.g., 003 sources 002 which sources 001).
- Tests 008/009 are self-contained and build their own binaries.
- To run a specific test:
bash test/NNN_name.sh. - To run all:
bash test/run-all.shorjust cross-test. - CI runs on
ubuntu-latestand does NOT install Go/Rust toolchains — tests 008/009 must handle this gracefully (skip with message).
- No partial implementations — all three must be updated in the same commit series.
- Test coverage required for new features.
- Update
CHANGELOG.mdfor user-facing changes. - Update
TODO.mdwhen completing or adding items.
- Hidden worktrees:
.git/cross/worktrees/<remote>_<hash>. - Sparse checkout:
git sparse-checkout set <path>— only specified paths checked out. - Rsync:
rsync -av --delete --exclude .gitfor worktree-to-local sync.--deleteremoves files locally that were deleted upstream. - Crossfile format: Lines like
cross use <name> <url>orcross patch <remote>:<branch>:<path> <local>. Parsed as bash duringreplay. - Metadata format: JSON at
.git/cross/metadata.json. Schema:{"patches": [{"id", "remote", "remote_path", "local_path", "worktree", "branch"}]}.
When time allows, these structural improvements would most benefit the codebase:
- Extract
removePatch()helper in Go and Rust — eliminates duplication betweenremoveandprune. - Unify
cd/wtinto a single parameterized function in Go and Rust. - Decompose
syncinto sub-functions (stash, sync-to-wt, pull, detect-deletions, sync-from-wt, unstash). - Fix Crossfile removal — use structured parsing (split line, match local_path field) instead of substring matching.
- Stabilize Rust worktree hash — replace
DefaultHasherwith SHA256 to match Go's deterministic behavior. - Remove
git2dependency from Rust — it's used for 2 trivial operations that already have CLI fallbacks. - Consolidate Go's git interface — pick either
gogs/git-moduleoros/execand use it consistently.