aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-06-16 18:17:07 +0200
committerDanilo M. <danix@danix.xyz>2026-06-16 18:17:07 +0200
commit4881bead18438be095b66a52cbf688c7a796f868 (patch)
tree9595be1736ee92334a9b3dc767b0530c9f542689 /docs
parent7818d1b50fccf02b65df2634c6a35cdc5d9b6e80 (diff)
downloaddots-backup-4881bead18438be095b66a52cbf688c7a796f868.tar.gz
dots-backup-4881bead18438be095b66a52cbf688c7a796f868.zip
docs: spec for --suggest flag
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'docs')
-rw-r--r--docs/superpowers/specs/2026-06-16-suggest-flag-design.md180
1 files changed, 180 insertions, 0 deletions
diff --git a/docs/superpowers/specs/2026-06-16-suggest-flag-design.md b/docs/superpowers/specs/2026-06-16-suggest-flag-design.md
new file mode 100644
index 0000000..5bcd5d6
--- /dev/null
+++ b/docs/superpowers/specs/2026-06-16-suggest-flag-design.md
@@ -0,0 +1,180 @@
+# Design: `--suggest` flag
+
+**Date:** 2026-06-16
+**Status:** Approved (pending implementation)
+
+## Goal
+
+Add a `-s|--suggest` flag to `dot-backup.sh` that surfaces config
+directories and files created since the last backup that are not yet
+tracked in `DOTFILES`, and prints a paste-ready block for `files.list`.
+
+The flag is read-only: it never copies, commits, pushes, or mutates any
+config. It scans, prints, and exits.
+
+## Behavior
+
+### Flag
+
+- New flag `-s` / `--suggest`, registered in the arg loop and `usage()`.
+- When set, run suggest logic, then **exit 0**. No backup/restore/copy/commit.
+- Mutually exclusive with `--restore` in practice (suggest exits first if both
+ given; suggest takes precedence).
+
+### Scan sources
+
+Three sources, all scanned one level deep only:
+
+1. `~/.config/*/` — direct child directories of `~/.config`.
+ Emitted as `.config/<name>`.
+2. `~/.*/` — top-level hidden directories in `$HOME`.
+ Emitted as `.<name>`.
+3. `~/.*` (files) — top-level hidden regular files in `$HOME`.
+ Emitted as `.<name>`.
+
+Always skip the special entries `.` and `..`.
+
+### Candidate filter
+
+A scanned path is suggested only if **all** of the following hold:
+
+1. **Newer than last backup** — its mtime is greater than the last-backup
+ timestamp (see "lastupdate change" below). If the timestamp file is
+ missing (never run, or an older backup that predates this feature), treat
+ every path as new.
+2. **Not already covered** by a `DOTFILES` entry (see "Dedup matching").
+3. **Not ignored** — its basename is not in `SUGGEST_IGNORE`.
+
+### Dedup matching
+
+The emitted path of a candidate is `c` (e.g. `.config/foobar`, `.newtool`).
+For each existing `DOTFILES` entry `e`, `c` is considered **covered** if any
+of:
+
+- `c == e` (exact match), OR
+- `c` is under `e/` (e is an ancestor — e.g. `e=.config`, `c=.config/foobar`), OR
+- `e` is under `c/` (c is an ancestor — e.g. `c=.config`, `e=.config/nvim`).
+
+If covered by any entry, the candidate is dropped. This prevents suggesting a
+path already listed, a path whose parent is listed, or a parent whose child is
+already listed.
+
+Note: `DOTFILES` entries are compared as-emitted (relative home paths and
+absolute system paths). System entries (starting `/`) never match home-relative
+candidates, which is correct — suggest only scans `$HOME`.
+
+### SUGGEST_IGNORE
+
+A bash array of basenames to skip. If unset (not defined in the config file),
+the script falls back to a baked-in default:
+
+```bash
+SUGGEST_IGNORE=(.cache .local .git .ssh .gnupg .Trash .pki .nv \
+ .mozilla .thunderbird .npm .cargo .rustup .java \
+ .dbus .config)
+```
+
+`.config` is in the list to exclude it from the **home-dir** scan only (its
+children are scanned separately as source 1). The home-dir dir/file scans check
+basenames against this list; the `~/.config/*` scan does **not** apply the
+`.config` skip to its children (only to deeper basenames if present).
+
+The user may override by defining `SUGGEST_IGNORE` in
+`~/.config/dot-backup/config`. A commented example is added to `config.example`.
+
+Detection of "unset": use `${SUGGEST_IGNORE+set}` test so an explicitly empty
+array from the user is respected and not overwritten by the default.
+
+## lastupdate change (required)
+
+The current `lastupdate()` writes a human string:
+
+```
+Last update: <date>
+```
+
+This is not machine-parseable for mtime comparison. Change `lastupdate()` to
+**also** write epoch seconds to a sibling file:
+
+- `${DEFAULT_OUTPUT_DIR}/lastupdate` — unchanged human-readable file (kept for
+ display and backward compatibility).
+- `${DEFAULT_OUTPUT_DIR}/lastupdate.epoch` — new file containing a single
+ integer: `date +%s`.
+
+The suggest logic reads `lastupdate.epoch`. If the file is absent or
+unparseable, the last-backup timestamp is treated as `0` (everything is new).
+
+This is the only change to existing backup behavior.
+
+## Output
+
+When candidates exist:
+
+```
+New since last backup (<human date of last backup>):
+ .config/foobar
+ .newtool
+ .somerc
+
+Paste into files.list:
+.config/foobar
+.newtool
+.somerc
+```
+
+- The indented top section is for human reading (colored like other output).
+- The bottom block is unindented, plain, ready to paste/append to `files.list`.
+- Candidates sorted alphabetically for stable output.
+
+When no candidates:
+
+```
+No new config dirs/files since last backup.
+```
+
+When `lastupdate.epoch` missing:
+
+- Still works; header reads `New since last backup (never):` and all unlisted,
+ unignored paths are shown.
+
+## Interaction with `--quiet`
+
+`--suggest` output goes to stdout like normal. If combined with `--quiet`, it
+follows the existing redirect (writes to log). No special handling — suggest is
+a read-only report, quiet just redirects it. Document this is allowed but
+unusual.
+
+## Scope cuts (YAGNI)
+
+- No interactive prompt, no `y/n`, no appending to `files.list`. Print only.
+- No recursion deeper than one level in any scan source.
+- No mtime tracking per-path beyond the single last-backup epoch.
+- No new dependencies (pure bash + coreutils `date`, `stat`).
+
+## Files touched
+
+- `dot-backup.sh`:
+ - Add `SUGGEST=false` flag var + `-s|--suggest` case + usage line.
+ - Modify `lastupdate()` to also write `lastupdate.epoch`.
+ - Add `SUGGEST_IGNORE` default (guarded by unset test).
+ - Add `do_suggest()` function (scan, filter, dedup, print).
+ - Early dispatch: if `SUGGEST == true`, call `do_suggest` and exit before
+ the backup/restore flow.
+- `config.example`: add commented `SUGGEST_IGNORE` example.
+- `README.md`: document the `--suggest` flag in the options/usage section.
+- `CLAUDE.md`: note the flag and the `lastupdate.epoch` file.
+
+## Testing (manual)
+
+No test harness in repo. Manual verification steps:
+
+1. `./dot-backup.sh --suggest` with no `lastupdate.epoch` → lists all unlisted,
+ unignored hidden dirs/files and `.config` children.
+2. Run a real backup, then `--suggest` → list shrinks to only paths newer than
+ the backup.
+3. `mkdir ~/.config/zzztest`, then `--suggest` → `zzztest` appears.
+4. Add `.config/zzztest` to `files.list`, re-run `--suggest` → it disappears
+ (dedup).
+5. Add `zzztest` basename to `SUGGEST_IGNORE` in config (after removing from
+ list) → disappears (ignore).
+6. `--dry-run --suggest` and `--suggest --restore` → suggest wins, exits clean.