Build Hazel

YESback to the verdict

0%0 of 20 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A background agent watching folders you choose, matching files against rules you can read, and moving, renaming, tagging or running a script on them, with a dry run that shows exactly what would happen and a log that lets you undo anything it did.

estimated effort weekendthe files for this build are in the project pack

LanguageSwift 6 and SwiftUI in XcodeApp typeA background agent with LSUIElement trueWatchingFSEvents with debounce and a settle checkStorageRules and a run log in SQLite under Application SupportUndoRecorded inverse operations, not a trash can

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree if you own one

    Why FSEvents, file coordination, tags and security scoped bookmarks are all platform APIs.

    Get it Apple menu, then About This Mac.

    Verify About This Mac reports macOS 14 or higher

  2. installfree

    Why Builds and signs the agent.

    Get it Mac App Store or developer.apple.com/xcode. open ↗

    Verify xcodebuild -version prints 16 or higher

  3. have readyfree

    Why Every phase acts on files, and testing against anything you care about is how a bug becomes a loss.

    Get it Generate them with a script so the folder can be recreated instantly after each destructive test.

    Verify A regeneration script recreates the folder from nothing

  4. decidefree with hardware you own

    Why This application moves and deletes files. Undo covers what it recorded; it does not cover a bug it did not record.

    Get it Confirm Time Machine or your backup tool has a recent snapshot, and that you have restored from it at least once.

    Verify A test file has been restored from backup successfully

  5. 99 USD per year

    Why Product phase only. Without it there is no notarization and a Gatekeeper warning elsewhere.

    Get it Enrol at developer.apple.com/programs. open ↗

    Verify A Developer ID Application certificate appears in Xcode settings

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- folders(id, path, recursive, enabled, bookmark_data) · bookmark_data is a security scoped bookmark so access survives a relaunch
- rules(id, folder_id, name, priority, enabled, conditions_json, actions_json, stop_on_match)
- runs(id, rule_id, file_path, matched_at, action, from_path, to_path, result, error) · the log and the undo record
- quarantine(path, reason, at) · files a rule refused to touch, with why
- A rule is conditions and actions as data, never code, so a dry run can evaluate it without doing anything.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
RULES_DB_PATHrequired~/Library/Application Support/Rules/rules.dbRules, folders and the run log.
SETTLE_CHECK_INTERVAL_MSrequired1000How long between size checks before a file counts as settled. Longer is safer for slow copies.
MAX_ACTIONS_PER_RUNrequired50Above this a run pauses for confirmation. The main protection against an over-broad rule.
SCRIPT_TIMEOUT_MSrequired10000How long a script action may run before it is killed.
LOG_RETENTION_DAYSrequired90How long run history is kept. Undo depends on it, so do not set this short.

The build, in order

  1. Watching a folder without hurting yourself

    You are told about new and changed files, once each, after they have finished being written.

    1. One stream per watched folder, with a latency that batches bursts. Persist the event identifier so a restart does not replay history.

      Files Watcher.swift

    2. Size and modification time stable across two checks. Test with a deliberately slow copy.

      Files Settle.swift

    3. Security scoped bookmarks so access to a user-chosen folder survives a relaunch without asking again.

      Files Bookmarks.swift

    done when · tick each as it passes
    watch out
    • A file being copied into a watched folder generates events while it is still incomplete, and acting on it moves or renames a partial file. Wait for the size to be stable across two checks a second or so apart before treating a file as ready, and be aware that a very slow copy can still fool a naive settle check.
  2. Rules as readable data

    A rule is something you can read, and evaluating it does nothing until you say so.

    1. Serialisable structures, not closures. This is what makes dry run and inspection possible.

      Files Rule.swift

    2. One function decides matches; another performs actions. The dry run calls only the first.

      Files Engine.swift

    3. Checksum every file in the folder before and after a dry run. This test is worth more than it looks.

      Files Tests/DryRun.swift

    done when · tick each as it passes
    watch out
    • A rule engine that executes as it evaluates cannot offer a dry run, and a file automation tool without a dry run will eventually move a thousand files somewhere you did not intend. Separate matching from acting from the beginning; retrofitting that separation means rewriting the engine.
  3. Acting safely, with undo

    Rules do their work, and anything they did can be reversed.

    1. NSFileCoordinator for moves and writes so applications reading the same files see consistency.

      Files Actions.swift

    2. From path and to path for every action. Undo replays them backwards.

      Files Undo.swift

    3. A destination that exists is a conflict, not a decision to make silently.

      Files Actions.swift

    done when · tick each as it passes
    watch out
    • Moving a file onto an existing path silently destroys the destination in most naive implementations. Refuse the move, record it as a conflict, and let the user decide. A file automation tool that overwrites will eventually overwrite something irreplaceable.
  4. Scripts, and the blast radius they bring

    A rule can run your own script, and a bad script cannot run away with your files.

    1. Off the main thread, process group killed on expiry, output captured into the log.

      Files ScriptAction.swift

    2. A threshold above which the run pauses for confirmation. This is the safety net that matters most.

      Files Engine.swift

    3. In the rule list and in the dry run. A script action deserves more attention than a move.

      Files RuleList.swift

    done when · tick each as it passes
    watch out
    • A rule that matches more broadly than you intended, combined with a script action, is how a folder gets reorganised beyond recognition in seconds. A per-run action cap that requires confirmation above a threshold turns a catastrophe into a question.
  5. Sharing and inspecting rules

    Rules can be exported, read and imported without importing a surprise.

    1. A schema version, refused if newer than the application understands.

      Files Export.swift

    2. Every action listed, scripts individually approved, and the import applied atomically or not at all.

      Files ImportPreview.swift

    3. By filename, rule and date. Answering what happened to a file is the log's main job.

      Files History.swift

    done when · tick each as it passes
    watch out
    • An imported rule set containing a script action is arbitrary code from whoever wrote it, running against your files on a trigger you may not fully understand. List every script prominently in the import preview and require explicit approval for each.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more productivity utilities.