# Changelog ## Unreleased ## 0.0.7 — 2026-09-14 Security and correctness hardening, from a threat model (spec §15.1) and the tests that hold it. - File names, paths and tool messages can no longer control the terminal: control characters, bidirectional controls and invalid UTF-8 are printed as escapes (`\x1b`, `\u202e`) everywhere krino prints them. - A placeholder can no longer rename a file to "", "." or "..", or add a ".." to a destination; such a step is skipped with a reason. - A file swapped between planning and applying - for a symlink, or for another file with the same size and modification time - is not acted on. - Undo refuses a trash entry name that is not a plain name inside the Trash, and a trashinfo whose path is not absolute. - Undo reverses a file its run renamed and then moved, moved twice, or moved and then sent to the Trash. Before, undo judged the first step against the disk as it is now, found its destination empty (the later step had moved the file on) and refused the whole file as "missing". Found by the new generated apply-and-undo test. - Folding maps the capital sharp s (ẞ) to "SS", as the other capitals already were: "STRAẞE" in a document now matches the keyword "straße". - Folding replaces invalid UTF-8 with U+FFFD first, so a stray invalid byte no longer stops the letter after it from losing its accent. - `krino new` refuses a directory whose path is not valid UTF-8 with that reason, instead of reporting a broken template. - The keyword cache is also discarded when normalisation changes, so answers cached before these fixes are recomputed once. - Builds need Go 1.25 or newer and use the Go 1.26.8 toolchain, which an older `go` downloads itself. `golang.org/x/text` is updated to v0.41.0. This fixes an infinite loop a crafted file's text could cause (GO-2026-5970) and, through the toolchain, the XML nesting guard (GO-2026-6088) and the `os/exec` and `os` fixes govulncheck reported. - Tests: fuzz targets for every decoder of outside data (`make fuzz`), a generated apply-and-undo round trip (`KRINO_PROPERTY_RUNS`), enum-completeness tests, `make race` and `make vulncheck`. ## 0.0.6 — 2026-09-14 - Choosing per file: `w` now applies what was decided and quits krino, instead of going on to the next directory, so a long review can be done a session at a time. Files never reached are not logged as declined; the outcome line counts them as "not reviewed". - Each choice is echoed on its own red line under the file (`→ yes`, `→ trash`, ...). - Enter is ignored at the review prompts instead of being reported as a wrong key. ## 0.0.5 — 2026-09-14 - Keyword cache: for each file whose text it extracts, krino records which content keywords the text contains, in `~/.cache/krino/NAME.cache`, and answers content tests from it while the file is unchanged. A rerun over the same files extracts nothing: on a real downloads folder a dry run went from 12.8 s to 0.13 s. No text and no file names are stored. A changed file, a new keyword, or a changed extraction tool means reading again; failures are never cached. `krino check` shows where the cache is. - Choosing per file: `t` sends the file to the Trash and `d` deletes it permanently, after a `y` to confirm, instead of what its rules planned; both are logged under the rule `(review)`. "Done, apply chosen so far" moves from `d` to `w`, in undo's review too. ## 0.0.4 — 2026-09-14 - `max-size` setting: files larger than it are skipped as "too big", in `(defaults ...)` or a directory file. - `(exclude COND...)` sets files aside before any rule sees them, by type or extension, name regex, content, or any other condition; all conditions in one form must hold. In `krino.conf` it applies to every directory, in a directory file to that directory. Excluded files are counted in the plan and listed with the form that matched under `-v`; `explain` traces every exclusion and `check` lists them. - `--min-age DURATION` overrides every directory's `min-age` for one run, e.g. `krino -n --min-age 0` to include files written a moment ago. - `krino init` and `krino new` write commented examples of all three. ## 0.0.3 — 2026-09-14 - The plan is shown as one block per file instead of a table: the file's name, each step, then the rule and the reason it matched. On a terminal every line wraps to its width, with continuation lines indented under their own column; piped output is never wrapped. - Choosing per file (`c`) shows that same full block for each file, not only its steps. - `-P` / `--no-pager` prints the plan straight out instead of through the pager. - A duplicate's original is shown with `~` for the home directory. ## 0.0.2 — 2026-09-14 - Duplicates are found, never deleted. A rule combining `(duplicate)` with `(delete)` or `(delete permanent)` is refused by `krino check` and every run, and a file that is a duplicate under any duplicate scope its directory's rules use gets no delete step from any rule. This replaces 0.0.1's known limitation: duplicate conditions with different scopes can no longer delete every copy of a group, only move copies aside. - Coloured output on a terminal, in the terminal's own 16-colour palette: actions by kind, rule names, skipped steps and reasons, warnings, the outcome counts and the prompt keys. `--no-color`, before or after any subcommand, turns it off, as `NO_COLOR` does. ## 0.0.1 — 2026-09-13 - Per-directory rule files, a main file listing which directories run, and a template for new ones. - Conditions with `and`, `or` and `not` over type, name, path, content, size, age and duplicates. - Actions copy, move, rename and delete (to Trash by default), chained. - Plan, review and approve — all files or per file — with `-y` to skip review. - A log of every step, and `krino undo`. - Placeholders in destinations and new names. - Builds for Linux, FreeBSD and OpenBSD. Performance: no threshold or target for 0.0.1. These were measured during development and are reported rather than promised — throughput is dominated by external content extractors, not by krino itself: - Full plans over a real folder of about 265 files, 164 of them needing text extraction through `pdftotext` at roughly 80 ms each, measured between about 13 and 20 s across runs. - A synthetic benchmark during development planned a generated tree of 4,405 files needing no extraction in 1.09 s. That benchmark is not `make bench`, whose generated tree is smaller. Files that share a byte size but differ in content — six templated invoices, for instance — are still told apart, because the duplicate check hashes the first and last 64 KiB and then the whole file rather than trusting size. Known limitation: `(duplicate)` conditions with different scopes can elect different originals for the same content, and within one directory's rules this can select every copy for deletion — see `krino.conf(5)`, DUPLICATES.