gravity.nvim
Intelligent dotfile sync plugin for Neovim with three-way conflict detection and machine-specific overrides.
Overview
gravity.nvim manages configuration files (dotfiles, agent prompts, scripts) by syncing them from your Neovim config repository to system locations. It tracks changes on both sides, detects conflicts, and provides interactive tools to review and apply updates safely.
Key Philosophy: Version control your configs in one place, sync them everywhere, never lose local changes.
Goals
- Single Source of Truth: All configs live in
~/.config/nvim/configs/ - Safe Syncing: Three-way conflict detection prevents accidental overwrites
- Machine-Specific Configs: Override system for work machines, different setups
- State Tracking: Know exactly what changed and when
- Interactive Workflow: Review diffs before syncing
- Beyond Dotfiles: Sync any file type (agent prompts, scripts, docs)
Features
Core Functionality
- Three-Way Conflict Detection: Compares repo, system, and last-sync state using SHA256 hashes
- Dual Override System:
manifest.overrides.jsonfor dependency metadata (deep merge)configs.overrides/for wholesale config file replacement
- Status Detection: 7 states tracked (unchanged, source_changed, system_changed, conflict, missing_system, missing_source, out_of_sync)
- Interactive Menus: Review changes, view diffs, selective syncing
- Automatic Backups: All overwritten files backed up with timestamps
- Directory Creation: Creates parent directories automatically
- Color-Coded Output: Visual feedback using Neovim highlight groups
What Can You Sync?
- Traditional dotfiles:
.bashrc,.gitconfig,.tmux.conf - Config files: Any structured config (JSON, YAML, TOML)
- Agent prompts: Claude Code agent definitions to
~/.claude/agents/ - Scripts: Bash, Python, or any executable
- Documentation: Markdown files, notes
- Anything text-based: If it's a file, gravity can sync it
Installation
Prerequisites
- Neovim 0.9.0+
- Git (for cloning)
- Unix-like system (Linux, macOS)
Setup
- Already installed if you're using this kickstart.nvim fork:
-- lua/custom/plugins/init.lua already includes:
{
name = 'gravity.nvim',
dir = vim.fn.stdpath 'config' .. '/lua/custom/gravity',
config = function()
require('custom.gravity').setup()
end,
lazy = false,
priority = 100,
}
- Create your manifest (if starting fresh):
cd ~/.config/nvim
# Edit manifest.json to add your configs
- Run initial status:
:GravityStatus
Usage
Commands
gravity.nvim provides three main commands:
:GravitySync
Main entry point with an interactive sync workflow:
- Shows full status of all configs
- Interactive menu with options to:
- View diffs for any file (numbered options)
- Sync all changes (
y) - Quit (
q) - Warns before overwriting local changes
- Performs sync with progress
- Shows summary of results
Example:
=== Gravity Sync ===
Config Files:
→ source changed .gitconfig
← system changed .tmux.conf
Options:
1. Diff .gitconfig
2. Diff .tmux.conf
y. Yes, sync all changes
q. Quit
Choice: 1
=== Diff: .gitconfig (system vs base) ===
-[user]
- name = Old Name
+[user]
+ name = New Name
:GravityStatus
Quick status check showing which configs differ from repo:
=== Gravity Status ===
Config Files:
✓ unchanged .bashrc
→ source changed .gitconfig
← system changed .tmux.conf
⚠ CONFLICT settings.json
○ not on system init.lua
✓ All config files in sync
Status Symbols:
✓unchanged - Files identical→source changed - Repo updated (safe to sync)←system changed - Local edits (warns before overwrite)⚠conflict - Both changed (manual resolution needed)○missing system - File not on system yet◌out of sync - File exists but differs (no history)✗missing source - Source file missing (skipped)
:GravityDiff <file>
View diff for specific config file with navigation:
:GravityDiff .bashrc
Shows colored diff with options to navigate to other files or trigger sync.
Typical Workflow
Daily usage:
" Review what changed and sync changes
:GravitySync
After editing repo configs:
- Edit
configs/.bashrcin your editor - Run
:GravitySync - Review diff showing your changes
- Confirm sync to apply to system
Handling local changes you want to keep:
:GravitySyncshows← system changed .tmux.conf- Copy to override:
cp configs/.tmux.conf configs.overrides/.tmux.conf - Edit override with your local changes
- Next sync will use your override
On a new machine:
- Clone your kickstart.nvim fork (which includes gravity.nvim)
- Open Neovim (lazy.nvim installs plugins automatically)
- Run
:GravitySync - Review and confirm - all configs sync to system
- Optionally run bootstrap agent:
claude-code --agent bootstrap "provision environment"
Configuration
Manifest Structure
The manifest defines what to sync and where:
{
"version": "1.0.0",
"dependencies": {
"system_packages": ["git", "tmux"],
"languages": { "go": "1.25.3", "node": "18" },
"tools": ["docker"]
},
"configs": {
".bashrc": {
"source": "configs/.bashrc",
"target": "~/.bashrc"
},
"bootstrap-agent": {
"source": "configs/bootstrap-agent-prompt.md",
"target": "~/.claude/agents/bootstrap-agent-prompt.md"
}
}
}
Override System
Manifest Overrides (manifest.overrides.json):
For granular changes to dependencies (gitignored):
{
"version": "1.0.0",
"dependencies": {
"languages": { "node": "16" }
}
}
Config Overrides (configs.overrides/):
For machine-specific config files (gitignored):
# Use different .gitconfig on work machine
cp configs/.gitconfig configs.overrides/.gitconfig
# Edit configs.overrides/.gitconfig with work email
Next sync uses override automatically. Status shows [override] indicator.
Directory Structure
~/.config/nvim/
├── manifest.json # Main config mapping
├── manifest.overrides.json # Machine-specific overrides (gitignored)
├── configs/ # Base configs (tracked)
│ ├── .bashrc
│ ├── .gitconfig
│ └── bootstrap-agent-prompt.md
├── configs.overrides/ # Machine overrides (gitignored)
│ ├── README.md # Only this tracked
│ └── .gitconfig # Override (gitignored)
├── .sync_state.json # Sync state (gitignored)
├── backups/ # Backup files (gitignored)
└── lua/custom/gravity/ # Plugin code
├── init.lua # Commands & UI
├── sync.lua # Sync logic
├── manifest.lua # Manifest handling
└── utils.lua # Utilities
Test Coverage
gravity.nvim has comprehensive test coverage with 14 automated tests:
| Category | Tests | Coverage |
|---|---|---|
| Integration Test | 1 | Complete workflow with 9 configs in all status states |
| Status Detection | 7 | All 7 status states (missing_system, unchanged, conflict, etc.) |
| Override Precedence | 3 | Base vs override selection, content syncing, flag tracking |
| State Tracking | 3 | State file creation, hash tracking, override recording |
What's Tested:
- ✅ All status detection logic
- ✅ Three-way conflict detection
- ✅ Override system (both types)
- ✅ Directory creation
- ✅ Backup file generation
- ✅ State file persistence
- ✅ Hash-based change tracking
Running Tests:
cd ~/.config/nvim
./tests/run_tests.sh
Full Testing Documentation: See testing.md for detailed coverage, test architecture, and patterns.
Development
Architecture
gravity.nvim is organized into focused modules:
init.lua: User-facing commands, interactive menus, colored outputsync.lua: Core sync logic, status detection, state trackingmanifest.lua: Manifest loading, validation, deep mergeutils.lua: File operations, hashing, diffing, backups
Key Concepts
Three-Way Detection:
- Compute hashes:
source_hash,system_hash - Load previous state:
prev_source_hash,prev_system_hash - Compare all four to detect what changed
State File (.sync_state.json):
{
"manifest_hash": "",
"dotfiles": {
".bashrc": {
"source_hash": "abc123...",
"system_hash": "abc123...",
"used_override": false,
"last_sync": "2024-11-05T10:30:00Z"
}
}
}
Status Detection Logic:
No source? → missing_source
No system? → missing_system
No state? → unchanged if match, else out_of_sync
Has state? → Compare hashes to detect changes
Adding New Features
- Write tests first (
tests/gravity/) - Implement in appropriate module
- Update integration test if affects workflow
- Run test suite:
./tests/run_tests.sh - Update documentation
File Conventions
- All test files use
test-prefix for safety - Status symbols centralized in
init.lua - Error messages use color coding
- Functions return structured results
Companion: Bootstrap Agent
gravity.nvim includes an AI bootstrap agent (configs/bootstrap-agent-prompt.md) that:
- Reads
manifest.jsonto provision development environments - Installs system packages, languages, tools
- Syncs configs via gravity.nvim
- Sets up Neovim plugins and LSPs
- Syncs to
~/.claude/agents/bootstrap-agent-prompt.md
Usage:
claude-code --agent bootstrap "Please provision my development environment"
The agent uses gravity's manifest as the single source of truth for environment setup.
Roadmap
v1.0 ✅ (Current and only planned version)
- Core sync functionality
- Three-way conflict detection
- Dual override system
- Interactive menus
- Comprehensive test coverage
- Bootstrap agent integration
Contributing
This is part of a personal Neovim configuration, probably best to fork and configure yourself from here.
License
Part of personal Neovim configuration. Use freely.
Related
- MkDocs Documentation: Full project documentation at sao.bros.ninja
- Testing Guide: testing.md - Comprehensive test documentation
- Bootstrap Agent: bootstrap-agent-prompt.md - AI provisioning agent
Status: v1.0 Complete - 14 tests passing, production ready