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 follow-up adds a post-generation empty-schema guard so specs containing no file-backed settings, including all-env-only configs, fail instead of emitting an unusable schema.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Reviews (8): 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
`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.
311e564 to
badb54a
Compare
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 badb54a. Configure here.
| object.remove("anyOf"); | ||
| object.insert("enum".into(), allowed); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choices skip nested container values
Medium Severity
choices are applied to only the first items or additionalProperties schema, so a nested container such as map<string, list<string>> or list<map<string, string>> gets enum on the inner array or object rather than on the scalar values. That enum is then AND-combined with type: array or type: object, which no value can satisfy, so an editor rejects every config that sets the property.
Reviewed by Cursor Bugbot for commit badb54a. Configure here.


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 or auth changes. Schema semantics are well-tested but affect editor validation for adopters.
Overview
Adds
usage generate json-schema(usage g json-schema) to emit draft 2020-12 JSON Schema from a usage spec’sconfigblock, with--file/--spec, optional--titleand--url, and--out-file(including-for stdout).A new
cli/src/schemamodule maps spec types, dotted keys, defaults, choices, and deprecation into schema shape; it omitsscope="env"props, keeps hidden props, and usesunevaluatedProperties: false. The command errors when there are no file-relevant properties.command_effectstreats the subcommand as read-only, write when--out-fileis set.Regenerated Fig, man, usage.kdl, and docs reference; unit and integration tests cover mapping edge cases and CLI behavior.
Reviewed by Cursor Bugbot for commit badb54a. Bugbot is set up for automated code reviews on this repo. Configure here.