Skip to content

YAOSGit/patch-buddy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

patch-buddy

Universal volatile state manager — persistent, toggleable patch layers for any file

Node Version TypeScript Version React Version

Uses Ink Uses Zod Uses chokidar Uses Vitest Uses Biome


Table of Contents

Getting Started

Architecture

TUI

Development


Overview

patch-buddy is a local-first "volatile state" manager. It treats patches as first-class, toggleable layers that sit on top of your real files. Use it to persist hacky changes, temporary configs, or library patches that need to survive npm install or git pull.

Patches are stored as unified diffs in a .patches/ directory and re-applied automatically any time a watched file changes on disk.

What Makes This Project Unique

  • Patch Any File — not just npm dependencies, any file type on your filesystem
  • Toggleable Layers — stack multiple patches per file, enable/disable each one independently
  • Automatic Rehydration — the watcher re-applies patches whenever files change externally
  • No Config File — everything via CLI flags, patches stored in .patches/
  • IDE-Friendly Conflicts — conflicts are flagged with a status marker and optionally written as git-style conflict blocks for resolution in your preferred editor

Installation

# Install globally from npm
npm install -g @yaos-git/patch-buddy

# Or install as a dev dependency
npm install -D @yaos-git/patch-buddy

From Source

# Clone the repository
git clone https://github.com/YAOSGit/patch-buddy.git
cd patch-buddy

# Install dependencies
npm install

# Build the project
npm run build

# Link globally (optional)
npm link

Quick Start

# 1. Save current file content as baseline
patch-buddy init src/config/db.ts

# 2. Make your hacky changes in your editor...

# 3. Snapshot the diff
patch-buddy snap src/config/db.ts -n "fix-port"

# Start the watcher to auto-reapply after npm install / git pull
patch-buddy watch

Note: Patch files are plain unified diffs committed to .patches/. Add .patches/ to version control to share volatile state across machines, or add it to .gitignore to keep it local.


CLI Usage

patch-buddy init <file>                   Save current file content as baseline
patch-buddy snap <file> -n <name>         Create a patch from current changes
patch-buddy list [file]                   Show all patches, optionally filtered
patch-buddy toggle <id>                   Toggle a patch enabled/disabled
patch-buddy drop <id>                     Delete a patch
patch-buddy apply [file]                  Apply all enabled patches once
patch-buddy revert [file]                 Restore file(s) to unpatched state
patch-buddy watch                         Start foreground rehydration watcher
patch-buddy --help, -h                    Show help message
patch-buddy --version, -v                 Show version information
Command Description
init <file> Save current file content as baseline
snap <file> -n <name> Create a patch from current changes
snap <file> -n <name> --git Use git HEAD as baseline instead of stored baseline
snap <file> -n <name> --base <path> Use an arbitrary file as baseline
list [file] Show all patches, optionally filtered by target file
toggle <id> Toggle a patch between enabled and disabled
drop <id> Delete a patch and its diff file
apply [file] Apply all enabled patches once without watching
revert [file] Restore file(s) to their unpatched state
watch Start the foreground rehydration watcher
watch --conflict-markers Write git-style conflict markers on failed patches

Examples

# Baseline + snapshot workflow
patch-buddy init node_modules/some-lib/index.js
# edit the file...
patch-buddy snap node_modules/some-lib/index.js -n "fix-memory-leak"

# Use git HEAD as baseline (skip storing a separate baseline file)
patch-buddy snap src/app.ts -n "debug-logging" --git

# List all tracked patches
patch-buddy list

# List patches for a specific file
patch-buddy list src/config/db.ts

# Disable a patch temporarily
patch-buddy toggle 001

# Re-enable it
patch-buddy toggle 001

# Apply all enabled patches in one shot (no watcher)
patch-buddy apply

# Apply patches for one file only
patch-buddy apply src/config/db.ts

# Revert a file to its unpatched content
patch-buddy revert src/config/db.ts

# Start the watcher (Ctrl+C to stop)
patch-buddy watch

# Start the watcher and write conflict markers on failure
patch-buddy watch --conflict-markers

The Patch Store

patch-buddy stores everything inside a .patches/ directory at the project root.

Disk Layout

.patches/
  manifest.json                         # Tracks all patches and their metadata
  src-config-db.ts/                     # Slugified target file path
    001-fix-port.patch                  # Unified diff — prefix is stack position
    002-add-logging.patch
  node_modules-some-lib-index.js/
    001-fix-memory-leak.patch

The slug is derived by replacing path separators and dots with hyphens, making the directory name safe on all platforms.

manifest.json

The manifest is the single source of truth for patch metadata. It is validated against a Zod schema on every read.

{
	"files": {
		"src-config-db.ts": {
			"targetPath": "src/config/db.ts",
			"patches": [
				{
					"id": "001",
					"name": "fix-port",
					"enabled": true,
					"status": "active",
					"originalHash": "a1b2c3d4...",
					"createdAt": "2026-03-28T10:00:00.000Z"
				}
			]
		}
	}
}
Field Type Description
id string Zero-padded numeric ID matching the patch filename prefix
name string Human-readable name supplied via -n
enabled boolean Whether the patch participates in application
status "active" | "disabled" | "conflicted" Current patch state
originalHash string SHA-256 of the file content at snapshot time
createdAt string ISO 8601 timestamp

The Rehydration Loop

The watch command monitors all files tracked in manifest.json using chokidar. When a file changes on disk the following sequence runs:

  1. Detect external change — compute the file's hash and compare it against the last hash written by patch-buddy. If patch-buddy itself wrote the change, skip it to avoid re-applying in a loop.
  2. Forward-apply the stack — read every enabled patch in stack order and attempt to apply each unified diff to the new file content.
  3. Success — write the patched result back to disk and log the number of patches applied.
  4. Conflict — if any patch fails to apply, mark it as conflicted in the manifest and log the failing patch ID. With --conflict-markers, git-style <<<<<<< / ======= / >>>>>>> blocks are written to the file for manual resolution.

The loop is designed to be idempotent: running apply or restarting watch always produces the same output given the same manifest state.


Dashboard

Launch the TUI with the patch-buddy-tui binary.

patch-buddy-tui

The dashboard is a full-terminal Ink application that embeds the rehydration watcher automatically. It provides:

  • Header — displays the project name and count of tracked files
  • Patch List — all patches grouped by target file; each row shows the patch ID, name, and status indicator
  • Diff Viewer — press Enter on any patch to expand the unified diff inline
  • Watcher Log — a live feed of rehydration events at the bottom of the screen
  • Snap Overlay — press n to open an inline form for creating a new snapshot without leaving the TUI

The embedded watcher starts when the TUI opens and stops when you quit. There is no need to run patch-buddy watch separately while the TUI is open.


Keyboard Shortcuts

Key Action
q Quit
h Help
Up / Down Navigate patch list
Enter Expand / collapse patch diff
t Toggle selected patch on/off
d Drop selected patch (with confirmation)
n New snapshot — opens inline snap form

Available Scripts

Development Scripts

Script Description
npm run dev Run TypeScript checking + test watcher concurrently
npm run dev:typescript Run TypeScript type checking in watch mode
npm run dev:test Run Vitest in watch mode

Build Scripts

Script Description
npm run build Bundle the CLI and TUI with esbuild

Lint Scripts

Script Description
npm run lint Run type checking, linting, formatting, and audit
npm run lint:check Check code for linting issues with Biome
npm run lint:fix Check and fix linting issues with Biome
npm run lint:format Format all files with Biome
npm run lint:types Run TypeScript type checking only
npm run lint:audit Run npm audit

Testing Scripts

Script Description
npm test Run all tests (unit, react, types, e2e)
npm run test:unit Run unit tests
npm run test:react Run React component tests
npm run test:types Run type-level tests
npm run test:e2e Run end-to-end tests

Tech Stack

Core

  • TypeScript 5 — Type-safe JavaScript
  • Chalk — Terminal string styling
  • Commander — CLI argument parsing
  • chokidar — Cross-platform file system watcher
  • Zod — Runtime schema validation for the manifest

Build & Development

  • esbuild — Fast bundler
  • Vitest — Unit testing framework
  • Biome — Linter and formatter

UI


Project Structure

patch-buddy/
├── src/
│   ├── app/                        # Application entry points
│   │   ├── cli.ts                  # CLI entry point (patch-buddy)
│   │   ├── tui.tsx                 # TUI entry point (patch-buddy-tui)
│   │   ├── app.tsx                 # Main TUI application shell
│   │   ├── index.tsx               # React app root
│   │   └── providers.tsx           # Provider wrapper
│   ├── commands/                   # CLI command implementations
│   │   ├── apply.ts                # Apply all enabled patches once
│   │   ├── drop.ts                 # Delete a patch
│   │   ├── init.ts                 # Save file baseline
│   │   ├── list.ts                 # List patches
│   │   ├── revert.ts               # Restore unpatched state
│   │   ├── snap.ts                 # Create a patch from current changes
│   │   ├── toggle.ts               # Toggle patch enabled/disabled
│   │   └── watch.ts                # Foreground rehydration watcher
│   ├── core/                       # Core domain modules
│   │   ├── Hash/                   # File content hashing
│   │   ├── Manifest/               # manifest.json read/write/mutate
│   │   ├── Patcher/                # Unified diff apply stack
│   │   └── Watcher/                # chokidar wrapper
│   ├── tui/                        # TUI-specific code
│   │   ├── commands/               # Keyboard command definitions
│   │   ├── components/             # React (Ink) components
│   │   │   ├── PatchDetail/        # Expanded diff viewer
│   │   │   ├── PatchList/          # Patch list with status indicators
│   │   │   ├── SnapOverlay/        # Inline snapshot form
│   │   │   └── WatcherLog/         # Live rehydration event log
│   │   ├── hooks/                  # React hooks
│   │   │   ├── useManifest/        # Manifest state and mutations
│   │   │   ├── useUIState/         # Selected/expanded patch IDs
│   │   │   └── useWatcher/         # Embedded watcher events
│   │   └── providers/              # React context providers
│   │       ├── ManifestProvider/   # Manifest context
│   │       ├── UIStateProvider/    # UI state context
│   │       └── WatcherProvider/    # Watcher context
│   ├── types/                      # Shared TypeScript type definitions
│   │   └── Manifest/               # Manifest, FileEntry, PatchEntry types
│   └── utils/                      # Pure utility functions
│       ├── diff/                   # createPatch / applyPatch wrappers
│       └── slug/                   # File path → directory slug
├── e2e/                            # End-to-end tests
├── biome.json                      # Biome configuration
├── tsconfig.json                   # TypeScript configuration
├── vitest.unit.config.ts           # Unit test configuration
├── vitest.react.config.ts          # React test configuration
├── vitest.type.config.ts           # Type test configuration
├── vitest.e2e.config.ts            # E2E test configuration
├── esbuild.config.js               # esbuild bundler configuration
└── package.json

Versioning

This project uses a custom versioning scheme: MAJORYY.MINOR.PATCH

Part Description Example
MAJOR Major version number 1
YY Year (last 2 digits) 26 for 2026
MINOR Minor version 0
PATCH Patch version 0

Example: 126.0.0 = Major version 1, released in 2026, minor 0, patch 0


Style Guide

Conventions for contributing to this project. All rules are enforced by code review; Biome handles formatting and lint.

Exports

  • Named exports only — no export default. Every module uses export function, export const, or export type.
  • import type — always use import type for type-only imports.
  • .js extensions — all relative imports use explicit .js extensions (ESM requirement).

File Structure

src/
├── app/              # Entry points and root component
├── commands/         # CLI command implementations (camelCase files)
├── core/             # Domain primitives (PascalCase directories)
│   └── MyModule/
│       ├── index.ts
│       └── index.test.ts
├── tui/
│   ├── commands/     # Keyboard bindings and command registry
│   ├── components/   # React components (PascalCase directories)
│   │   └── MyComponent/
│   │       ├── index.tsx
│   │       └── index.test.tsx
│   ├── hooks/        # React hooks (camelCase directories)
│   └── providers/    # React context providers (PascalCase directories)
├── types/            # Shared type definitions (PascalCase directories)
│   └── MyType/
│       ├── index.ts
│       └── index.test-d.ts
└── utils/            # Pure utility functions (camelCase directories)
    └── myUtil/
        ├── index.ts
        └── index.test.ts

Components & Providers

  • Components use function declarations: export function MyComponent(props: MyComponentProps) {}
  • Providers use React.FC arrow syntax: export const MyProvider: React.FC<Props> = ({ children }) => {}
  • Props are defined in a co-located .types.ts file using the type keyword.
  • Components receive data via props — never read process.stdout or global state directly.

Types

  • Use type for all type definitions — never interface.
  • Shared types live in src/types/TypeName/index.ts with a co-located TypeName.test-d.ts.
  • Local types live in co-located .types.ts files — never inline in implementation files.
  • No duplicate type definitions — import from the canonical source.

Constants

  • Named constants go in .consts.ts files.
  • No magic numbers in implementation files — extract to named constants.

Testing

  • Every module has a co-located test file.
  • Components: index.test.tsx
  • Hooks: index.test.tsx
  • Utils: index.test.ts
  • Types: index.test-d.ts (type-level tests using expectTypeOf / assertType)

License

ISC

About

Universal volatile state manager — persistent, toggleable patch layers for any file

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors