applydir
Overview
applydir is a Python command-line tool that automates the application of LLM-generated code changes to a codebase. It processes changes specified in a JSON format, supporting actions like line replacements, file creation, and deletion. Changes are applied directly to the codebase - use Git or other version control tools (e.g., via planned vibedir integration) for tracking and reverting. Designed to integrate with prepdir (for logging and configuration) and eventually vibedir (for LLM communication, Git integration, and linting), applydir validates and applies changes with robust error handling, fuzzy matching for reliability, and resolvable file paths.
Features
- Supported Actions:
replace_lines: Replace blocks of lines in existing files (unified approach for modifications, additions, deletions).create_file: Create new files with specified content.delete_file: Delete files (configurable via--no-allow-file-deletion).
- Reliability: Uses fuzzy matching (Levenshtein or sequence matcher, configurable) to locate lines in existing files, with options for whitespace handling and case sensitivity.
- JSON Input: Processes a simple JSON structure with
file_entries, each containingfile,action, andchanges(withoriginal_linesandchanged_linesfor replacements). - Resolvable Paths: Accepts relative or absolute file paths, resolved relative to
--base-dir(default: current directory). - Validation: Validates JSON structure, file paths, and non-ASCII characters (configurable via
config.yamlor--non-ascii-action). - Configuration: Loads defaults from
.applydir/config.yamlor bundledsrc/applydir/config.yamlusingprepdir'sload_config. Overrides via CLI or programmaticconfig_override. - Error Handling: Returns structured
ApplydirErrorobjects with type, severity, message, and details; logged viaprepdir's logging system. - CLI Utility: Run
applydir <input_file>with options to customize behavior. - Direct Application: Changes are applied directly to files for simplicity; track/revert via Git in workflows like
vibedir. - Modular Design: Separates JSON parsing (
ApplydirChanges), change validation (ApplydirFileChange), matching (ApplydirMatcher), and application (ApplydirApplicator). - Commit Message Support: Optional top-level message field in JSON for Git commit messages (multi-line via \n).
JSON Format
Changes are provided in a JSON object with a file_entries array and optional message describing the changes provided:
{
"message": "Optional Git commit message (may include \n for multi-line)",
"file_entries": [
{
"file": "<relative_or_absolute_file_path>",
"action": "<replace_lines|create_file|delete_file>",
"changes": [
{
"original_lines": ["<lines to match in existing file (empty for create_file)>"],
"changed_lines": ["<new lines to insert (full content for create_file)>"]
}
]
}
]
}
Top-level fields
message(optional string) – Git commit message.
May contain\\nfor a multi-line message.
Only emitted when the LLM is instructed to include it.
Prompting to use applydir
To facilitate integration with, the applydir_format_description function provides a detailed prompt for LLMs to generate JSON changes compatible with applydir. The function includes:
- Structure and Requirements: Describes the JSON format, including
file_entries, supported actions (replace_lines,create_file,delete_file), and validation rules (e.g., non-emptyoriginal_linesforreplace_lines, resolvable file paths). - File Output: Instructs the LLM to return the JSON in a file (default:
applydir_changes.json), separate from explanatory text, aligning with LLM API practices. - Example JSON: Includes a sample with multiple
replace_lineschanges, acreate_fileaction, and adelete_fileaction. - Customization: Accepts a
filenameparameter to specify the output file name.
This function ensures LLMs generate valid JSON for applydir, supporting LLM communication. See the example JSON for reference.
Example Cases
- Modification (replace_lines): Replace a block in
src/main.py:{ "file_entries": [ { "file": "src/main.py", "action": "replace_lines", "changes": [ {"original_lines": ["print('Hello')"], "changed_lines": ["print('Hello World')"]} ] } ] }
- Addition: Match a block and replace with additional lines.
- Deletion: Match a block and replace with fewer lines, or use
delete_filefor entire files. - Creation (create_file): Create
src/new.pywith content:{ "file_entries": [ { "file": "src/new.py", "action": "create_file", "changes": [ {"original_lines": [], "changed_lines": ["def new_func():", " pass"]} ] } ] }
- Deletion (delete_file): Delete
src/old.py:{ "file_entries": [ { "file": "src/old.py", "action": "delete_file", "changes": [] } ] }
Installation
pip install applydir
Usage
CLI
Run the applydir utility to apply changes from a JSON file:
applydir changes.json [--base-dir <path>] [--no-allow-file-deletion] [--non-ascii-action {error,warning,ignore}] [--log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}]
changes.json: Path to the JSON file containing changes.--base-dir: Base directory for file paths (default:.).--no-allow-file-deletion: Disable file deletions (overrides config).--non-ascii-action: Handle non-ASCII characters (error,warning, orignore; overrides config).--log-level: Set logging level (DEBUG,INFO,WARNING,ERROR,CRITICAL).
Example
applydir changes.json --base-dir /path/to/project --no-allow-file-deletion --non-ascii-action=error --log-level=DEBUG
Programmatic
from applydir import ApplydirApplicator, ApplydirChanges, ApplydirMatcher
from applydir.applydir_error import ApplydirError, ErrorSeverity
import logging
from prepdir import configure_logging
logger = logging.getLogger("applydir")
configure_logging(logger, level="INFO")
changes_json = {
"file_entries": [
{
"file": "src/main.py",
"action": "replace_lines",
"changes": [{"original_lines": ["print('Hello')"], "changed_lines": ["print('Hello World')"]}]
}
]
}
changes = ApplydirChanges(file_entries=changes_json["file_entries"])
matcher = ApplydirMatcher(similarity_threshold=0.95)
applicator = ApplydirApplicator(
base_dir="/path/to/project",
changes=changes,
matcher=matcher,
logger=logger,
config_override={"allow_file_deletion": False, "validation": {"non_ascii": {"default": "error"}}}
)
errors = applicator.apply_changes()
has_errors = False
for error in errors:
log_level = (
logging.INFO if error.severity == ErrorSeverity.INFO
else logging.WARNING if error.severity == ErrorSeverity.WARNING
else logging.ERROR
)
logger.log(log_level, f"{error.message}: {error.details}")
if error.severity in [ErrorSeverity.ERROR, ErrorSeverity.WARNING]:
has_errors = True
if not has_errors:
logger.info("Changes applied successfully")
Configuration
applydir loads defaults from .applydir/config.yaml or bundled src/applydir/config.yaml using prepdir's load_config. CLI options or programmatic config_override can override settings.
Example config.yaml:
validation:
non_ascii:
default: warning
rules:
- extensions: [".py", ".js"]
action: error
- extensions: [".md", ".markdown"]
action: ignore
- extensions: [".json", ".yaml"]
action: warning
allow_file_deletion: true
matching:
whitespace:
default: collapse
similarity:
default: 0.95
similarity_metric:
default: levenshtein
use_fuzzy:
default: true
validation.non_ascii: Controls non-ASCII handling (default, rules by extension).allow_file_deletion: Enables/disables deletions (default: true).matching: Settings forApplydirMatcher(whitespace, similarity threshold/metric, fuzzy matching).
Logging level is set via CLI --log-level or programmatically.
Error Format
Errors and warnings are returned as a list of ApplydirError objects and logged:
[
{
"change": null,
"error_type": "json_structure",
"severity": "error",
"message": "Invalid JSON structure",
"details": {}
},
{
"change": null,
"error_type": "file_not_found",
"severity": "error",
"message": "File does not exist for deletion",
"details": {"file": "/path/to/missing.py"}
},
{
"change": null,
"error_type": "file_changes_successful",
"severity": "info",
"message": "All changes to file applied successfully",
"details": {"file": "/path/to/main.py", "actions": ["replace_lines"], "change_count": 1}
}
]
Error Types
json_structure: Invalid JSON (e.g., missingfile_entries).invalid_change: Invalid change format or validation failure.file_not_found: File missing for modification/deletion.file_already_exists: File exists for creation.no_match: No matching lines found.multiple_matches: Multiple matches for lines.permission_denied: Deletion disabled.file_system: File operation failure.file_changes_successful: Successful application (info level).
Class Structure
-
ApplydirError:
- Represents errors/warnings.
- Attributes:
change: Optional[Any],error_type: ErrorType,severity: ErrorSeverity,message: str,details: Dict.
-
ApplydirChanges:
- Parses/validates JSON
file_entries. - Methods:
validate_changes(base_dir: str, config: Dict) -> List[ApplydirError].
- Parses/validates JSON
-
ApplydirFileChange:
- Represents a single change.
- Methods:
from_file_entry(file_path: Path, action: str, change_dict: Optional[Dict]) -> ApplydirFileChange,validate_change(config: Dict) -> List[ApplydirError].
-
ApplydirMatcher:
- Matches lines with fuzzy options.
- Methods:
match(file_content: List[str], change: ApplydirFileChange) -> Tuple[Optional[Dict], List[ApplydirError]].
-
ApplydirApplicator:
- Applies changes.
- Methods:
apply_changes() -> List[ApplydirError], supports create/replace/delete.
-
ApplydirResult:
- Returned by apply_changes().
- Attributes: errors: List[ApplydirError], commit_message: Optional[str], success: bool.
Workflow
- Input: Run
applydir <input_file>with JSON changes. - Parsing: Load JSON, check
file_entries, createApplydirChanges. - Validation: Validate structure, paths, non-ASCII via
validate_changes. - Application: Use
ApplydirApplicatorto match lines (ApplydirMatcher) and apply changes directly. - Output: Log errors/successes, return exit code (0 success, 1 failure). Commit message available in ApplydirResult.
Planned Features
- vibedir Integration: LLM prompting, Git commits/rollbacks, linting.
- Extended Actions: More action types if needed.
- Additional Validation: Syntax/linting checks.
Dependencies
- prepdir: For
load_config(configuration) andconfigure_logging(logging). - Pydantic: For model validation (e.g.,
ApplydirError,ApplydirChanges). - dynaconf: For configuration merging.
- difflib (standard library): For sequence matcher in fuzzy matching.
Python Best Practices
- PEP 8 compliant naming and structure.
- Type hints and Pydantic for safety.
- Modular classes for testability.
- Logging via
prepdir.
Edge Cases
- Invalid JSON/missing
file_entries: Logged and exit 1. - Non-existent files for replace/delete:
file_not_founderror. - Existing files for create:
file_already_existserror. - Non-ASCII: Handled per config/CLI.
- Multiple/no matches:
multiple_matchesorno_matcherrors. - Empty/whitespace-only message: Validation error.
Testing
- Tests in
tests/test_main.pycover CLI execution, validation, errors, and options. - Run:
pdm run pytest tests/test_main.py
Next Steps
- Implement
vibedirfor full workflow.
Metadata
Release files for applydir 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| applydir-0.5.0.tar.gz | 37.5 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| applydir-0.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 60.6 kB
Release files / applydir-0.5.0.tar.gz
| Download URL | applydir-0.5.0.tar.gz |
|---|---|
| Size | 37.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
671532af0a1c5682cb8ca3c2289b2dd0c9b5b1643de2766e434c998cab250c46
|
|
BLAKE2b-256 checksum How to use checksums |
d08b3566f5a1ad3d2da92361c504fb3fb0707398249e298b5f5a0f0f87f577b0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
pdm/2.26.1 CPython/3.10.16 Linux/5.15.167.4-microsoft-standard-WSL2
|
Release files / applydir-0.5.0-py3-none-any.whl
| Download URL | applydir-0.5.0-py3-none-any.whl |
|---|---|
| Size | 23.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1c1859e1be66255b5ba970bb9597ddbe6aef16007bb2def0048de92aa25dd017
|
|
BLAKE2b-256 checksum How to use checksums |
135ab29cfeb0fd17454327ec802c82df0d43811da6e5513528cfaab22efdb087
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
pdm/2.26.1 CPython/3.10.16 Linux/5.15.167.4-microsoft-standard-WSL2
|
Security for applydir 0.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.