Skip to content

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.json for 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

  1. 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,
}
  1. Create your manifest (if starting fresh):
cd ~/.config/nvim
# Edit manifest.json to add your configs
  1. Run initial status:
:GravityStatus

Usage

Commands

gravity.nvim provides three main commands:

:GravitySync

Main entry point with an interactive sync workflow:

  1. Shows full status of all configs
  2. Interactive menu with options to:
  3. View diffs for any file (numbered options)
  4. Sync all changes (y)
  5. Quit (q)
  6. Warns before overwriting local changes
  7. Performs sync with progress
  8. 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:

  1. Edit configs/.bashrc in your editor
  2. Run :GravitySync
  3. Review diff showing your changes
  4. Confirm sync to apply to system

Handling local changes you want to keep:

  1. :GravitySync shows ← system changed .tmux.conf
  2. Copy to override: cp configs/.tmux.conf configs.overrides/.tmux.conf
  3. Edit override with your local changes
  4. Next sync will use your override

On a new machine:

  1. Clone your kickstart.nvim fork (which includes gravity.nvim)
  2. Open Neovim (lazy.nvim installs plugins automatically)
  3. Run :GravitySync
  4. Review and confirm - all configs sync to system
  5. 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 output
  • sync.lua: Core sync logic, status detection, state tracking
  • manifest.lua: Manifest loading, validation, deep merge
  • utils.lua: File operations, hashing, diffing, backups

Key Concepts

Three-Way Detection:

  1. Compute hashes: source_hash, system_hash
  2. Load previous state: prev_source_hash, prev_system_hash
  3. 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

  1. Write tests first (tests/gravity/)
  2. Implement in appropriate module
  3. Update integration test if affects workflow
  4. Run test suite: ./tests/run_tests.sh
  5. 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.json to 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.


Status: v1.0 Complete - 14 tests passing, production ready