Skip to content

Add YAML config file support and a --verbose mode - #27

Draft
PiotrRogulski wants to merge 8 commits into
mainfrom
claude/yaml-config-support-sjf9mg
Draft

Add YAML config file support and a --verbose mode#27
PiotrRogulski wants to merge 8 commits into
mainfrom
claude/yaml-config-support-sjf9mg

Conversation

@PiotrRogulski

Copy link
Copy Markdown
Member

Every command-line option can now be set in a ciach.yaml, and -v explains what a run is doing.

Config file

ciach.yaml in the analyzed package root, discovered automatically. Keys are the long option names minus the --, plus path for the positional argument:

public: false                     # --no-public
exclude: ['test/**', 'tool/**']   # repeatable options take a list, or a bare string
kinds: [class, function, method]
format: github
set-exit-if-changed: true
  • Precedence is command line > config file > built-in default. An explicitly passed flag wins even when its value equals the default (ciach --public beats public: false), and a repeatable option on the command line replaces the config's list rather than appending.
  • Discovery looks for that one file name in the package root only — no walking up to parents, so each package in a monorepo owns its config. --config <path> reads a file from anywhere else; --no-config ignores a discovered one (combining the two is a usage error).
  • Errors name the file and the offending key: unknown keys (with the valid list), wrong-typed values, bad format/kinds, non-positive concurrency. Exit code 2, as with any usage error.
  • A test asserts every long option name has a matching config key, so the two can't drift.

-v, --verbose

Narrates 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 --remove touches.

$ ciach -v
[  0.0s] Read config from ciach.yaml.
[  0.0s]   It sets 2 options:
[  0.0s]     public: false
[  0.0s]     exclude: test/**
[  0.0s] Settings for this run:
[  0.0s]   path: /home/me/pkg

[  0.1s] Starting Dart analysis server…
[  0.3s] Collecting declarations from 13 file(s)…
[  0.5s] Scanned 13 file(s) and checked 44 declaration(s) in 478ms: 4 unused, 1 referenced only from doc comments.

Everything goes to stderr, so ciach -v -f json | jq still works. It supersedes --progress, whose self-overwriting line would fight with it. Settable in the config as verbose: true, like everything else.

Structure

  • lib/src/cli/config.dartConfigFile: 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 in buildParser, and resolveOptions is the one place that reads them all.
  • lib/src/cli/options.dartResolvedOptions + resolveOptions, the single place the three layers merge, and the last type that knows where a setting came from. Also owns finderOptions(), so mapping to the library's FinderOptions lives with the fields rather than in bin/.
  • lib/src/cli/verbose.dart — the two message builders, as pure functions returning lines; bin/ only owns the stderr printer.
  • FinderOptions is untouched — it's the public library API, and CLI-only settings don't belong there.

Config values are validated on read, so resolveOptions reads 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 the progress read.

Also

  • Adds yaml: ^3.1.3 (SDK ^3.4.0, so the Dart 3.10 floor check is unaffected).
  • README trimmed from 446 lines to 309 while documenting both features — the options table still matches --help exactly.

Testing

dart analyze, dart format --set-exit-if-changed and dart test (131 tests, 47 of them new) all pass. The CLI was also exercised end to end against example/: discovery, command-line override, --no-config, path: from a config, each error message, -f github path prefixing, and a config-driven --remove --force run in a scratch copy.


Generated by Claude Code

claude added 7 commits July 25, 2026 09:53
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.
@PiotrRogulski
PiotrRogulski requested a review from Komoszek July 26, 2026 10:54
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
PiotrRogulski marked this pull request as draft July 30, 2026 19:03
Comment thread CHANGELOG.md
- 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioning this in changelog is unnecesary

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know; there's a lot more of unnecessary writing in this PR – I'll make sure it's better ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants