Skip to content

Behavior Reference

This reference documents default behaviors, edge cases, error handling, and non-obvious design decisions across all commands.

Output Path Resolution

Order of precedence:

PriorityConditionResult
1-o <path> flag providedExact path specified
2.git/HEAD exists in current working directory<cwd>/reports/
3Otherwise~/Downloads

Detection uses Bun.file(join(cwd, ".git/HEAD")).exists() — it checks for the HEAD file specifically, not just a .git directory.

Timestamped Outputs

TypePatternExample
Collect report<hostname>-YYYYMMDDHHMMSS.dotfMacBook-Pro-20260407143022.dotf
Backup directorybackup-<hostname>-YYYYMMDDHHMMSSbackup-MacBook-Pro-20260407143022
Archivebackup-<hostname>-YYYYMMDDHHMMSS.tar.gzbackup-MacBook-Pro-20260407143022.tar.gz
Pre-restore snapshotpre-restore-YYYYMMDDHHMMSSpre-restore-20260407143500

Timestamps use YYYYMMDDHHMMSS format (no separators) for filesystem-safe, sortable names.


Command-Specific Behaviors

collect

BehaviorDetail
Collector parallelismAll collectors run via Promise.allSettled — one failing doesn't block others
Missing toolsCollectors for ollama, brew, apps return {} — no error output
Section orderingDetermined by collector return order and Object.assign merge
Redaction defaultOn — disable with --no-redact
Slim truncationContent sections cut to 10 lines, suffix ... (N more lines) added
Sensitivity reportPrinted after report file path — only shown when findings exist
Output directory creationmkdir -p is called on the output directory before writing

backup

BehaviorDetail
File scanEach file is scanned individually before writing
Skip actionFile with private key → not backed up (not even with redaction)
Custom redaction orderentry.redact() runs first, then applyRedactions() (pattern-based)
Directory copyBun.Glob('**/*') with { onlyFiles: true, dot: true } — includes dotfiles, files only
Empty backupIf no files were copied (all missing), prints "No files found to backup."
Archive creationtar czf runs on backup dir → dir is then rm -rf'd
Category filtering--only filters first, then --skip — both can be used together
Category countSummary shows per-category file counts: ai (7), shell (1)

restore

BehaviorDetail
File comparisonBun.hash() (xxHash64) for fast content comparison
Pre-restore snapshotOnly includes conflicting files — not new or same files
Redacted skipFiles containing [REDACTED] string are automatically skipped
.local mappingshell/.zshrc.local~/.zshrc.local (base entry path + .local suffix)
Directory entriesFiles under a dir backup entry are matched by prefix and restored to the correct subdirectory
Conflict persistencea (overwrite-all) and l (skip-all) persist for the rest of the session
Exit behaviorIf no files restored, prints "No files restored."
Category pickerShows categories with file counts, filters plan after selection

diff

BehaviorDetail
Auto-find backupSearches output dir for backup-* directories, sorted descending by name
TTY detectionprocess.stdout.isTTY ?? false — no colors in pipes
Color methodBun.color(name, "ansi-256") — not raw ANSI escape codes
GroupingOutput is grouped by category with category headers
Scope limitationOnly compares files in backup — does not discover new files on machine
Section filter--section matches exact category name (not fuzzy)

status

BehaviorDetail
Backup detectionSame as diff — finds latest backup-* directory
Age calculationDirectory mtime → human-readable (Nm ago, Nh ago, Nd ago)
Age fallbackIf stat fails, shows "unknown"
All-clear message"Everything up to date." when no modified or new files
Modified listOnly shown when modified files exist

compare

BehaviorDetail
Auto-detectFinds two newest .dotf files in <cwd>/reports/ by modification time
Search scopeOnly searches <cwd>/reports/not ~/Downloads
Minimum filesRequires at least 2 .dotf files when auto-detecting
Diff labelsDerived from filename without .dotf extension
ColorAlways enabled (color: true passed to formatter)
No diff outputPrints "No differences found." when reports are identical

list

BehaviorDetail
Fuzzy matchingSubstring match on full name + dot-segment match on parts
Search scopeLatest .dotf file in <cwd>/reports/ only
Multiple matchesAll matching sections are printed
No matchLists all available section names
Output formatRe-stringified via @dotformat/core (properly formatted .dotf output)

scan

BehaviorDetail
File modeSingle file scanned directly
Directory modeRecursive with Bun.Glob('**/*') and { dot: true }
Exclusionsnode_modules/ and .git/ paths skipped
Size limitFiles over 1 MiB skipped
Severity sortingFindings sorted HIGH → MEDIUM → LOW in detailed output
Match displayEach finding shows: L{line} [{severity}] {label}: {match}
Clean output"No sensitive data found." when no findings

Error Handling

Fatal Errors

ConditionBehavior
$HOME not setconsole.error + process.exit(1)
Bun runtime not availableError message + process.exit(1) (checked in bin/dotfiles.ts)
No backup path for restorePrints usage message, exits normally

Graceful Degradation

ConditionBehavior
Missing config fileRegistry collector skips entry — no error
Missing directory for dir kindTry/catch → skipped silently
ollama not installedcollectOllama catches error → returns {}
brew not installedcollectHomebrew catches error → returns {}
Non-macOS for apps/brewPlatform guard → returns {}
JSON parse errorRegistry collector catches → skips entry
stat() fails in age calculationReturns "unknown"
No backup found for diff/status"No backup found. Run 'dotfiles backup' first."
No reports for compare/listAppropriate message with usage hint

Bun API Usage

APIWhere UsedWhy
Bun.file()EverywhereFile existence checks, reading content
Bun.$Collectors, backupShell commands (uname, brew, ollama, tar, mkdir, rm, ls)
Bun.GlobBackup, scan, restore, compare, listFile pattern matching
Bun.hash()Restore planFast content comparison (xxHash64)
Bun.color()Diff commandTerminal color output
Bun.envPath resolutionEnvironment variable access
Bun.write()Backup, collectFile writing

Constants

ConstantValueLocationPurpose
REDACTION_MARKER"[REDACTED]"src/utils/constants.tsMarker used in redacted content
SLIM_MAX_LINES10src/commands/collect.tsMax lines in slim mode
MAX_FILE_SIZE1048576 (1 MiB)src/commands/scan.tsMax file size for scan

TTY / Color Behavior

  • diff command: checks process.stdout.isTTY ?? false
    • TTY present: uses Bun.color() for yellow/green/blue/gray output
    • Not a TTY (pipe, redirect): all color strings are empty, output is plain text
  • compare command: always passes color: true to formatDiff() (color is handled by @dotformat/core)
  • ANSI reset: \x1b[0m is used after each colored line (only when TTY)