feat(generate): add json-schema for a CLI's config file - #839
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Central YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
66206cd to
e82aea5
Compare
e82aea5 to
67064cd
Compare
Greptile SummaryThe PR adds a JSON Schema generator for config blocks and integrates it into the CLI, command-effect metadata, generated documentation, and tests.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; both previously reported empty-schema paths are fixed because environment-only properties are filtered before dotted groups are constructed and the command checks the resulting schema. Important Files Changed
Reviews (13): Last reviewed commit: "feat(generate): add json-schema for a CL..." | Re-trigger Greptile |
Instruction counts
2 benchmark(s) above the 1% gate: Only instruction counts gate. Wall clock is shown for context — on identical hardware it moves 4-20% run to run. Measured by tak — instruction-counted CLI benchmarks, stored in this repository's git notes. Shadow comparisonParsing
|
67064cd to
fd5de37
Compare
|
All three Bugbot findings were real. Fixed:
An all-env-only spec still emitted the rejecting schema. The bail checked Three tests, each mutation-verified; two of them at the command level in a new AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable. |
a9bf1c3 to
08ac0ea
Compare
08ac0ea to
6ba7e7d
Compare
|
Real, and my fix for the value-vs-group collision was too blunt: I replaced any parent that had no Now only a parent that cannot hold keys at all is replaced, so AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable. |
6ba7e7d to
1167b49
Compare
|
Right, and it is the same class as the list/map case with a different shape: The fix isn't to move the enum somewhere (there is no Mutation-verified in both directions. AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable. |
1167b49 to
311e564
Compare
311e564 to
badb54a
Compare
badb54a to
e734d80
Compare
|
Right, and it is my own fix from the last round not going far enough: I descended one level to It now descends all the way to the scalars, and the union case is keyed on whether it descended at all rather than on which key it found. Mutation-verified: descending only one level fails the test. AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e734d80. Configure here.
e734d80 to
5e4bed3
Compare
|
Two findings; one real, one I could not reproduce. Choices stripping container union branches — real, and my previous fix was the cause: I removed The rule is now the general one instead of a special case: the enum is applied to every position a single value can occupy — down through Dotted env-only groups bypassing the guard — I could not reproduce this, and I think the reading is off by one step. The $ usage g json-schema --spec '… prop "remote.cache" type="bool" scope="env" …'
Error: this spec declares nothing a config file can hold, so there is no schema to write
$ … prop "a.b.c" type="bool" scope="env" # same error
$ … prop "a.b" scope="env" + prop "keep" # writes a schema with `keep` onlyIf there is a shape that does produce an empty group I would rather fix it than argue, so I am happy to be shown one. AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable. |
5e4bed3 to
38a7d7a
Compare
38a7d7a to
34b6aeb
Compare
`usage g json-schema` writes a draft 2020-12 schema for the config file a spec's `config` block describes: dotted keys re-nested into the shape a file actually has, the type grammar mapped onto what a validator understands, and the editor-facing facts — default, enum, deprecated, description — taken from the same declarations the docs page renders. `unevaluatedProperties: false` on every object, so an unknown key is reported rather than ignored. Two decisions worth stating, both about what a config file can hold: - Hidden props stay in the schema. `hide` is about documentation and completion; the setting is still settable, and omitting it from a schema that rejects unknown keys would turn an editor red on a legal file. - `scope="env"` props are left out. Such a setting cannot be read from a file at all, so listing it would advertise a key the CLI ignores there. mise's schema.ts does list its five `env_only` settings — a deliberate divergence from the parity target, and an instance of the drift generating this from one declaration is meant to remove. A key declared as both a value and a group (`prop "a"` beside `prop "a.b"`) is a contradiction no schema can express; the group wins, because a scalar `type` left beside `properties` would reject every value rather than just that one. Verified against a hand-converted slice of mise's registry — bools, uint with `minimum`, `map<string, string>`, `option<bool>`, the `bool|string` union with its four choices, three levels of `task.cache.remote_mode` — and diffed against schema/mise.json for the same keys. Six tests, each mutation-checked.
34b6aeb to
0079a6d
Compare

usage g json-schemagenerates a draft 2020-12 JSON Schema for the config file a spec'sconfigblock describes. Fourth PR of stack #836; depends on the vocabulary in #835.Every CLI in the fleet has one of these. mise generates
schema/mise.jsonfrom its registry with a 200-line TypeScript script; the others hand-write theirs or go without. Generating it from the spec means the schema, the settings documentation and (eventually) the resolver cannot disagree about what a setting is.$ usage g json-schema -f mise.usage.kdl --url https://mise.jdx.dev/schema/mise.jsonWhat it maps
task.cache.remote_modebecomes three levels of object. This is what the flat spelling buys: one canonical key in the spec, the file's real shape reconstructed here.uint→integerwithminimum: 0,set<T>→ array withuniqueItems,map<K,V>→additionalProperties,url→format: uri, unions →anyOf,option<T>→ simply not required. A type only the tool knows (x-carried Rust types,Base::Custom) validates as a string, which is the most a schema can say without knowing more.default,enumfromchoices,deprecated: true, andlong_helpas thedescriptionso hover shows the whole story.unevaluatedProperties: falseon every object, so a typo'd key is reported rather than silently ignored.Two decisions about what a config file can hold
Hidden props stay in.
hidegoverns documentation and completions; the setting is still settable. WithunevaluatedProperties: false, omitting one turns an editor red on a file that is perfectly legal. mise agrees —ciishide = trueand is inschema/mise.json.scope="env"props are left out. Such a setting cannot be read from a file at all, so listing it advertises a key the CLI will ignore there. mise'sschema.tsdoes list its fiveenv_onlysettings —system_config_file,global_config_root, and three more — somise.jsontoday tells your editor those are validmise.tomlkeys. They aren't. This is a deliberate divergence from the parity target, and an instance of exactly the drift that generating from one declaration removes.A key declared as both a value and a group (
prop "a"besideprop "a.b") is a contradiction no schema can express. The group wins: a scalartypeleft besidepropertieswould reject every value rather than just that one.Verification
Six tests, each mutation-checked — reverting the behavior makes the test fail, including the two new ones (the
scope="env"filter and the value/group collision). A third mutation showed the reverse-order collision guard was unreachable, because props is aBTreeMapand a key always sorts before every key extending it, so that branch was removed rather than left untested.Beyond the unit tests, run against a hand-converted slice of mise's registry — the conversion an adoption PR would do — covering bools,
uintwith a default,map<string, string>,option<bool>, thebool|stringunion with its four documented choices, a deprecated prop withrenamed_to, andtask.cache.remote_mode. Output diffed key-by-key againstschema/mise.json: same types, same defaults, same enums. The differences are the two above plusanyOfwhere schema.ts writesoneOf(equivalent for disjoint types).cargo test --workspace --all-featuresgreen (48 binaries),clippy --all-targets -D warningsclean,mise run renderapplied.Also classifies the new command in
command_effects—read, raised towriteby--out-file— which the coverage test in that module required.Noticed in passing and deliberately left alone: the generated "Source code" link for every multi-word command points at a hyphenated filename that does not exist (
complete-word.rs,completion-init.rs). Pre-existing, unrelated to config, and getting its own change.AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable.
Note
Low Risk
Read-only generator with optional file output; no runtime config loading or auth changes. Main impact is generated schema semantics (env scope, nesting) affecting editor validation.
Overview
Adds
usage generate json-schema(usage g json-schema) to emit a draft 2020-12 JSON Schema for the config file described by a usage spec’sconfigblock, with optional--title,--url($id), and--out-file(including-for stdout).A new
cli/src/schemamodule maps spec config props to schema: dotted keys nest into objects, the config type grammar becomes JSON Schema types, and metadata (default,choices→enum,deprecated, help text) is carried through. Schemas useunevaluatedProperties: false;scope="env"props are omitted; hidden props stay. The CLI errors when there are no file-holdable properties (e.g. all env-only).Wiring includes the generate subcommand,
command_effects(read; write with--out-file), regenerated Fig/manpage/docs, and unit plus integration tests.Reviewed by Cursor Bugbot for commit 0079a6d. Bugbot is set up for automated code reviews on this repo. Configure here.