Syncs the {base folder}/{scaffold_folder}/ rules/docs folders across multiple project directories safely and reproducibly. The utility compares modification times and copies the newest version of each file to older peers, with dry-run, timestamped backups, atomic writes, and live progress.
A small, focused app inside this repo to keep your {base folder}/{scaffold_folder}/ directory consistent across projects. Use the desktop GUI or the CLI:
- GUI:
python main_gui.py - CLI:
python cli_sync.py - Core engine:
utils_sync/sync_core.py
- Multi-folder sync — select any number of folders; the engine syncs all
{scaffold_folder}/subdirectories across them using mtime-based conflict resolution (newest file wins). - Scan → preview → execute — an overwrite preview panel shows every planned copy before anything is written; confirm or cancel before committing.
- Dry-run mode — preview changes without touching any files; toggle via the Settings window or
config.txt. - Safe writes — atomic copies (temp file + rename) prevent partial overwrites; optional timestamped
.bakbackups are created before every overwrite. - Backup cleanup — one-click button removes all
.bakfiles when they are no longer needed. - Favorite folder sets — save common project combinations via
folders_favesin config and reload them instantly. - Ignore patterns — exclude specific files or subdirectories from sync.
- Live progress — threaded worker streams progress events to the GUI so the interface stays responsive during long syncs.
- Simple configuration — all settings live in a human-editable
config.txt; no database, no registry. - Cross-platform — runs on Windows, macOS, and Linux.
The engine orchestrates a scan → plan → execute workflow:
- Scan:
SyncEngine.scan_folders()inutils_sync/sync_core.pybuilds an index of files under each folder's{base folder}/{scaffold_folder}/ - Plan:
SyncEngine.plan_actions()inutils_sync/sync_core.pydecides copy operations using newest mtime as source - Execute:
SyncEngine.execute_actions()inutils_sync/sync_core.pyperforms safe, atomic copies with optional backups
- Engine:
SyncEngine.__init__()inutils_sync/sync_core.pyholds config and an event queue and emits progress - Worker:
SyncWorker.run()inutils_sync/sync_worker.pycoordinates scan/plan/execute on a background thread for the GUI - Events:
EventType,ProgressEvent, andmake_event()inutils_sync/progress_events.pystandardize messages shown in the UI and CLI - Paths:
file_path_utils.has_roo_dir()andfile_path_utils.get_roo_relative_path()inutils_sync/file_path_utils.pyensure only real{base folder}/{scaffold_folder}/directories are synced and compute relative paths - Config I/O:
config_sync.save_config()inutils_sync/config_sync.pypersists settings atomically; defaults are loaded automatically - Logging:
logger.init_logger()andlogger.log_event()inutils_sync/logger.pywrite a rolling JSONL log and a human-readable plan log
- Ensure Python 3.9+ is installed
- Clone this repo
- Optional but recommended: create and activate a virtual environment
- Install dependencies:
pip install -r requirements.txt
or
py -m pip install -r requirements.txt GUI
- Launch:
python main_gui.py - In the window, click Add Folder and select two or more project roots that each contain a
{base folder}/{scaffold_folder}/directory - Open Settings to adjust dry-run, backup mode, and ignore patterns
- Click Start Sync to preview or apply changes
CLI
- Run headless sync:
python cli_sync.py <folder1> <folder2> [<folder3> ...] - Requirements:
- At least two folders; each must contain a
{base folder}/{scaffold_folder}/directory or the tool exits with code 1
- At least two folders; each must contain a
- Exit codes:
- 0: success
- 1: argument/validation error
- 2: runtime failure
By default, the app reads settings from config.txt in the project root. You can override this in three ways:
-
Environment variable via
.envfile:AgentAutoFlow_CONFIG=/path/to/custom-config.txt -
CLI flag:
python cli_sync.py --config /path/to/custom-config.txt folder1 folder2 -
Default fallback:
config.txtin project root
Priority order: CLI flag > environment variable > default
Example config.txt:
window_width=800
window_height=600
# Comma-separated names to ignore anywhere under .roo folder.
ignore_patterns=.roo\commands\run-sync.md, .roo\docs, .roo\rules\02-database.md
# Backup behavior ("none" or "timestamped").
backup_mode=timestamped
# if true, keep source modified times (copy2 already preserves on most platforms)
preserve_mtime=true
# if true, collect and display actions but do not modify files
dry_run=false
# Root-level allowlist for files above .roo folder.
root_allowlist=.roomodes
# For use with "Load Favorites" button.
folders_faves=D:\Dropbox\Projects\_MediaShare\app, D:\Dropbox\Projects\2ndFoundation\app, D:\Dropbox\Projects\AgentAutoFlow- Scope: By default, only the
{base folder}/{scaffold_folder}/subtree of each folder is scanned and synced, but.roomodeswill be included if.roomodesis present inroot_allowlistinconfig.txt. - Default (root-level files): No root-level scanning occurs unless opted-in via
root_allowlist. - Conflict resolution: The newest mtime wins; the latest copy becomes the source for all older peers.
- Backups: If
backup_mode=timestampedand a destination exists, it is renamed with an ISO timestamp suffix before copy. - Atomicity: Copies write to a temp file in the destination directory then rename into place to avoid partial writes.
- Safety rails: only includes regular files (no symlinks), requires a real
{base folder}/{scaffold_folder}/folder, and always respectignore_patterns. - Symlinks: A symlinked
{base folder}/{scaffold_folder}/is treated as absent byutils_sync/file_path_utils.has_roo_dir(). - Non-destructive: The current implementation copies newer files to older ones and does not delete files.
- Start with
dry_run=trueto verify actions. - Add private, noisy, or large folders to
ignore_patterns. - Keep one canonical project (AgentAutoFlow) as your "source of truth" and run sync from it first.
- Error: Folder does not contain
{base folder}/{scaffold_folder}/directory: Ensure each selected path has a{base folder}/{scaffold_folder}/folder at its top level - Permission denied: On Windows, run the terminal as Administrator or move the projects to a writable location
- Paths with spaces: Quotes are not required in the GUI; for CLI, wrap paths in quotes if your shell needs it
- Nothing happens in non-dry runs: Check antivirus or file locks; the app uses atomic replace operations that can be blocked
- Core logic and unit tests live under
utils_sync/andtests/ - Example:
tests/test_sync_core.pyverifies newest-wins planning - Contribute improvements by keeping modules small and functions documented
- Run tests with pytest tests/
A short opt-in mechanism to allow syncing a small set of files from a project root in addition to the default {base folder}/{scaffold_folder}/ subtree.
-
Summary: preserve the default behavior of scanning only
{base folder}/{scaffold_folder}/(the safe, opt-in default). When enabled, an optional root-level allowlist can include specific filenames (for example,.roomodes) which will be appended to the index after the{base folder}/{scaffold_folder}/scan and indexed under a synthetic relative key equal to the filename (example:.roomodes) so they do not collide with{base folder}/{scaffold_folder}/keys. -
Config key and example (see
config.txt):- root_allowlist=.roomodes
-
Default behavior:
- No scanning outside
{base folder}/{scaffold_folder}/occurs unless files are explicitly allowlisted.
- No scanning outside
-
Safety rails:
- Require a real, non-symlink
{base folder}/{scaffold_folder}/folder for a project to be eligible for syncing. - Only include files that exist, are regular files (not directories), and are not symlinks.
- Optional size cap recommended (e.g., 256 KB) to avoid very large files being pulled in.
- Respect existing ignore patterns by name (files matching
ignore_patternsare skipped).
- Require a real, non-symlink
-
Scan behavior summary:
- The engine first scans the
{base folder}/{scaffold_folder}/subtree as before. - After
{base folder}/{scaffold_folder}/is scanned, any root files present inroot_allowlistare appended to the index and assigned a synthetic relative key equal to the filename (for example:.roomodes) so they are indexed alongside{base folder}/{scaffold_folder}/entries without colliding with{base folder}/{scaffold_folder}/keys. - These allowlisted root entries participate in the normal plan → execute workflow.
- The engine first scans the
-
Code references:
- Implementation and config I/O:
utils_sync/config_sync.py - Scan/append behavior:
utils_sync/sync_core.py
- Implementation and config I/O:
-
Mermaid flow (validation → scan → append allowlisted → plan → execute):
flowchart LR
validation --> scan
scan --> append_allowlisted
append_allowlisted --> plan
plan --> execute
- Short acceptance criteria:
- This "Root-Level File Allowlist" section exists in
README-file-sync.md. - The example config snippet above matches the new
config.txtentry referenced atconfig.txt. - The README mentions the safety checks and the
root_allowlistconfiguration. - The README includes the short mermaid flow showing: validation → scan → append allowlisted → plan → execute.
- This "Root-Level File Allowlist" section exists in
- High-level project readme:
README.md - This module’s engine:
utils_sync/sync_core.py - CLI wrapper:
cli_sync.py - GUI app:
main_gui.py
Typical user path through main_gui.py: Load Favorites -> Scan -> Execute -> Confirmation dialog -> "Synchronization completed successfully" dialog -> List of files updated in green with checkmarks -> Delete .bak files -> Confirmation dialog -> Yes -> Deleted x .bak files dialog -> OK -> [Continue here with what you want to add or change about the file-sync app.] and feed the prompt to coder-sr mode. If it's a big change, feed it to planner-a or architect to get a detailed plan created.