Add YAML config file support and a --verbose mode - #27
Draft
PiotrRogulski wants to merge 8 commits into
Draft
Conversation
Add `ciach.yaml` (or `ciach.yml`) support so a project's settings can be checked in instead of retyped on every invocation. Every command-line option is settable in the file under its long option name, plus `path` for the positional argument. - Discovery is automatic in the analyzed package root (that directory only — no walking up, so each package in a monorepo owns its config). - `--config <path>` reads a file from anywhere else, erroring out when it does not exist. - `--no-config` ignores a config file even when one is found; combining it with `--config` is a usage error. - Precedence is command line > config file > built-in defaults. An explicitly passed flag wins even when its value equals the default, and a repeatable option given on the command line replaces the config's list rather than appending to it. - Unknown keys, wrong-typed values, unknown formats/kinds and a non-positive concurrency are usage errors naming the file and key. Option resolution moves out of `bin/` into `ResolvedOptions`, which merges the three layers in one place and is covered by unit tests.
Narrow config discovery to a single file name, `ciach.yaml`. One spelling to look for, one to document; `--config <path>` still reads a file under any name from anywhere. Add `-v, --verbose`, which narrates a run on stderr with elapsed-time stamps: - which config file was read and every option it set — or that one was skipped for --no-config, or that the searched directory has none; - the settings the run resolved to, one line per option, after the command line, config file and defaults have been merged; - each scan phase as it starts (the finder's existing progress narration, kept line by line instead of overwritten in place); - the scan summary, the `-f github` path prefix, and which files `--remove` rewrites. Everything goes to stderr, so `-v -f json | jq` still works. Verbose supersedes `--progress`, whose single self-overwriting line would fight with it. Like every other option, it is settable in the config file (`verbose: true`). `--no-config` now locates the config file it is skipping — without reading or parsing it — so verbose can name what was ignored.
Three types described the same twenty settings: CiachConfig (a nullable field per option), LoadedConfig (the lookup outcome around it), and ResolvedOptions (the merged result). Every option's name was written out five times — parser declaration, config key set, config field, config parse call, merge call — so adding one meant touching all five and forgetting one was silent. Fold CiachConfig and LoadedConfig into a single ConfigFile: the validated settings it holds, where they came from, whether it was skipped, and typed readers (boolean/string/strings/oneOf/kinds/positiveInt) that name the file and key on a mismatch. The parallel field list is gone; what a file sets is just its settings map, which is also what --verbose already prints. ResolvedOptions stays — it is what makes settings non-nullable past the merge — and now owns absoluteRootPath and finderOptions(), so mapping to the library's FinderOptions lives with the fields instead of being spelled out in bin/. Config values are validated on read, so resolveOptions reads every key before deciding whether to use it: a malformed setting is reported even when the command line overrides that same option. A new test pins this down for every key, and caught one real hole — `!verbose && flag(...)` short-circuited past the `progress` read, so `progress: nonsense` was accepted on a verbose run. No behavior change otherwise; error messages and verbose output are unchanged.
Inline the two locals that only existed to keep a wrapped string out of a list literal, where they were dodging no_adjacent_strings_in_list. A message belongs in the literal that returns it, as one string, however long that line ends up. Same treatment for the other CLI messages that were split across adjacent string parts purely for source width, in bin/ and lib/src/cli/. Output is byte-for-byte unchanged. The `\n`-joined help text in cli/args.dart is left alone: those parts are the help output's own lines, not one sentence wrapped to fit.
- Group the per-file removal counts with groupFoldBy instead of building the map by hand, and filter with whereNot. - Give _VerboseLog real methods, write and writeAll, rather than an overloaded call. - whereNot(configKeys.contains) for the unknown-key scan. - Split the empty-list case out of _value's ternary into its own pattern. - Trim the README from 446 lines to 309: the config-file and verbose sections lose about half their words, "what it skips by default" becomes a table, and installation, removal, limitations and performance drop their repetition. Every option, flag and caveat is still documented — the options table is unchanged, and it still matches --help exactly.
The switch subject promotes under a bare `List()` pattern, so the arm can read the value straight off `value` instead of binding an alias for it.
Each option was declared in three places — the arg parser, the config-key set, and the merge in resolveOptions — with a test to catch the drift. Now each is declared once, as a CiachOption enum entry carrying its command-line spelling, help text, default, constraints, and the `configKey` a ciach.yaml entry is read from. package:config resolves a value for each from the command line, then the config file, then the default, so the merge and the precedence rules are the library's. What stays ours is the part the package doesn't cover: where to look for a config file, whether to read it at all, and reporting a malformed one against the path it came from. ConfigFile is now the ConfigurationBroker for that layer, handing over values already typed the way each option wants them — a bare List<dynamic> from YAML would not satisfy a List<String> option. Gains beyond the single declaration: - `--verbose` now names the layer every setting came from, straight off OptionResolution, instead of just its value. - Invalid values are all reported at once rather than one per attempt. - The positional-argument and range checks are the library's, so `--concurrency 0` and a stray second path argument word their errors slightly differently than before. - resolveOptions is left holding only conversions: kind names to symbol kinds, the two inverted flags, and the two settings that fall back to a terminal probe. Config values are still type-checked eagerly when the file is parsed — resolution only asks for the keys it needs, so a bad value under an option the command line overrides would otherwise go unreported. Three settings accept less than their type allows; their rules live on the option (allowedValues, a customValidator, min) and the file readers apply the same formatNames and parseKinds, since the package keeps validateOptionValue internal to itself. Adds config: ^0.9.0 (SDK ^3.6.0, so the 3.10 floor check still passes).
PiotrRogulski
marked this pull request as draft
July 30, 2026 19:03
Komoszek
reviewed
Jul 30, 2026
| - Add `-v`, `--verbose` to narrate a run on stderr: config used, every setting | ||
| and the layer it came from, scan phases, what `--remove` touches. Supersedes | ||
| `--progress`. | ||
| - Resolve options with `package:config`, so each one is declared once for the |
Contributor
There was a problem hiding this comment.
Mentioning this in changelog is unnecesary
Member
Author
There was a problem hiding this comment.
I know; there's a lot more of unnecessary writing in this PR – I'll make sure it's better ❤️
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every command-line option can now be set in a
ciach.yaml, and-vexplains what a run is doing.Config file
ciach.yamlin the analyzed package root, discovered automatically. Keys are the long option names minus the--, pluspathfor the positional argument:ciach --publicbeatspublic: false), and a repeatable option on the command line replaces the config's list rather than appending.--config <path>reads a file from anywhere else;--no-configignores a discovered one (combining the two is a usage error).format/kinds, non-positiveconcurrency. Exit code 2, as with any usage error.-v, --verboseNarrates the run on stderr with elapsed-time stamps: which config file was read and what it set, the settings the run resolved to, each scan phase, the scan summary, and what
--removetouches.Everything goes to stderr, so
ciach -v -f json | jqstill works. It supersedes--progress, whose self-overwriting line would fight with it. Settable in the config asverbose: true, like everything else.Structure
lib/src/cli/config.dart—ConfigFile: the validated settings a file holds, where they came from, whether it was skipped, and typed readers that name the file and key on a mismatch. Deliberately not a field per option: the options are declared once inbuildParser, andresolveOptionsis the one place that reads them all.lib/src/cli/options.dart—ResolvedOptions+resolveOptions, the single place the three layers merge, and the last type that knows where a setting came from. Also ownsfinderOptions(), so mapping to the library'sFinderOptionslives with the fields rather than inbin/.lib/src/cli/verbose.dart— the two message builders, as pure functions returning lines;bin/only owns the stderr printer.FinderOptionsis untouched — it's the public library API, and CLI-only settings don't belong there.Config values are validated on read, so
resolveOptionsreads every key before deciding whether to use it — otherwise a typo would go unreported whenever the command line overrode that same option. A test pins this for all twenty keys; it caught one real hole where!verbose && flag('progress', …)short-circuited past theprogressread.Also
yaml: ^3.1.3(SDK^3.4.0, so the Dart 3.10 floor check is unaffected).--helpexactly.Testing
dart analyze,dart format --set-exit-if-changedanddart test(131 tests, 47 of them new) all pass. The CLI was also exercised end to end againstexample/: discovery, command-line override,--no-config,path:from a config, each error message,-f githubpath prefixing, and a config-driven--remove --forcerun in a scratch copy.Generated by Claude Code