Skip to content

docs(config): write down what a resolution does - #882

Merged
jdx merged 2 commits into
agent/config-cli-layerfrom
agent/config-resolution-docs
Aug 15, 2026
Merged

docs(config): write down what a resolution does#882
jdx merged 2 commits into
agent/config-cli-layerfrom
agent/config-resolution-docs

Conversation

@jdx

@jdx jdx commented Aug 14, 2026

Copy link
Copy Markdown
Owner

The config block reference says how a CLI declares
settings. What happens to them was written down nowhere — precedence, coercion, scope, renames,
warnings existed only as the merge's implementation and the corpus that pins it. Which is why the
corpus README I wrote in #875 had to say, in as many words, "these vectors are the definition, which
is the wrong way round".

Now it is prose first and vectors second, the way /spec/argv and the argv corpus sit together, and
the page is normative rather than descriptive: an implementation in Go or TypeScript has something to
read before it starts, instead of reverse-engineering 46 JSON vectors.

It states the things a reader would otherwise have to infer from a test:

  • a declared default is the bottom layer, not a floor applied afterwards — which is why a union
    list with a default and one file gets both
  • union concatenates, so a list keeps duplicates and a set drops them, because that is what
    distinguishes the two
  • an empty value is a value: HK_EXCLUDE= is how a default gets turned off
  • a value the type cannot read costs its own key and nothing else
  • scope asks how far a place is trusted, not what kind of place it is, so a pkl file in a checkout
    is as much a thing a repository carries as a TOML file
  • an origin names the place (MISE_JOBS, --jobs, ./hk.toml) and never the kind, because a user who
    wants a different answer needs to know what to unset

The corpus README now points at it instead of standing in for it, and it is in the sidebar under the
argv grammar.

AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable.


Note

Low Risk
Documentation and conformance vectors only; no resolver or library code changes in the diff.

Overview
Adds a normative config resolution spec at docs/spec/resolution.md (precedence, merging, types, scope, renames, warnings, corpus relationship) and links it from the VitePress sidebar next to the argv grammar.

Updates corpus/config/README.md so vectors are explicitly secondary to that prose. Corpus gains coverage for multi-hop rename chains (threadsconcurrencyjobs), expanded boolean env spellings (y, on, n, empty → false), and set_by_comma on set<string> with deduplication.

Reviewed by Cursor Bugbot for commit 216d124. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 6f4ac127-35c7-4ebf-8635-1d917a239048

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds a normative configuration-resolution specification, links it from the documentation and corpus README, and expands conformance coverage. The rename-chain addition still leaves one previously identified normative behavior uncovered.

  • Documents precedence, merging, coercion, scope, renames, origins, and warning semantics.
  • Adds boolean spelling, set parser, and multi-hop rename vectors.
  • Adds the resolution page to the specification sidebar.

Confidence Score: 4/5

The PR is not yet safe to merge because the attempted corpus-coverage fix still permits implementations to omit required intermediate rename-chain deprecation warnings.

The reply shown without an author says the missing rename-chain vector was added, but the concrete counterexample remains: the only new chain has no intermediate deprecation metadata and therefore cannot enforce the normative warning behavior.

Files Needing Attention: corpus/config/04-renames.kdl, docs/spec/resolution.md

Important Files Changed

Filename Overview
corpus/config/04-renames.kdl Adds terminal multi-hop rename coverage, but does not exercise the documented intermediate-chain deprecation behavior.
corpus/config/05-types.kdl Correctly expands boolean spelling coverage and verifies set_by_comma deduplication.
docs/spec/resolution.md Adds the normative resolution contract, though its rename-chain deprecation rule is not fully represented by the accompanying corpus update.
corpus/config/README.md Repositions the normative page as prose authority and the corpus as executable conformance coverage.
docs/.vitepress/config.mts Adds the new resolution page to the specification navigation.

Fix all with Greploop

Fix All in Claude Code

Reviews (3): Last reviewed commit: "docs(config): say which rules the corpus..." | Re-trigger Greptile

Comment thread docs/spec/resolution.md Outdated
@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Instruction counts

benchmark trend instructions Δ wall (min) Δ
markdown ▂▂▃▁▇▇▆▇█ 175,733,287 → 175,904,438 +0.10% 16.07 → 16.11ms +0.20%
startup █▆▆▆▇▁▁▄▅ 1,221,955 → 1,221,989 +0.00% 0.96 → 1.01ms +5.35%

No instruction-count regression above 1%.

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 comparison

Parsing mise use -g node@20 against a shadow of mise's committed spec.
Reported, not gated: the shadow grows as the derive learns to express more, so
what to watch is the ratio rather than either column.

usage clap ratio
instructions, cold parse 29956 5893587 196x
usage: argv -> struct                             900 ns      0.90 µs
clap: build tree + parse -> struct             501010 ns    501.01 µs
clap: parse -> struct, tree reused              23016 ns     23.02 µs
clap: build tree only                          306047 ns    306.05 µs

216d124909e8 vs b8068da1afb6 · measured on the runner, not pushed to the history.

greptile-apps[bot]
greptile-apps Bot previously approved these changes Aug 15, 2026
jdx added 2 commits August 15, 2026 05:36
The `config` block reference says how a CLI *declares* settings. What happens to
them was written down nowhere: every rule about precedence, coercion, scope,
renames and warnings existed only as the merge's implementation and the corpus that
pins it — so the corpus README had to say "these vectors are the definition, which
is the wrong way round".

Now it is prose first and vectors second, the way the argv grammar and its corpus
sit together, and the page is normative: an implementation in Go or TypeScript has
something to read before it starts.

Includes the things a reader would otherwise have to infer from a test: that a
declared default is the bottom layer rather than a floor, that a `union` list keeps
duplicates and a `set` drops them, that an empty value is a value, that a refused
value costs its own key, and that scope asks how far a place is trusted rather than
what kind of place it is.
…e rest

"Every rule on this page has vectors" was not true. Five boolean spellings, the
`set_by_comma` parser and a chain of two renames had none, so an implementation
could pass every vector and still read `y` as false.

Those now have vectors. Two rules still have none, both because a vector says
what a resolution *produces* and neither of these is one:
`list_by_os_path_separator` splits on a character that depends on the machine, and
a corpus whose answers differ by platform stops being the definition of correct;
`not-read` is a layer's choice between two of its own variables, which a vector
describing what a layer supplies cannot express. Said on the page rather than
left to a reader to discover by diffing.
@jdx
jdx force-pushed the agent/config-resolution-docs branch from 14479f7 to 216d124 Compare August 15, 2026 10:36
@greptile-apps
greptile-apps Bot dismissed their stale review August 15, 2026 10:36

Dismissed because a newer commit was pushed; Greptile will re-review the current head.

Comment on lines +83 to +85
setting "jobs" type="uint" default=1
setting "concurrency" type="uint" renamed-to="jobs"
setting "threads" type="uint" renamed-to="concurrency"

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.

P1 Rename-chain deprecations remain uncovered

When an intermediate alias carries the first deprecation notice, this vector tests only terminal resolution and a renamed warning. The corpus therefore accepts implementations that omit the required intermediate-chain deprecated warning, including the current resolution path that checks only the exact written alias.

Knowledge Base Used: Configuration registries and layered resolution

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Correct at the time, and closed by #893, which merged alongside this one — the vector you are describing was what found the bug.

On main now:

vector "a-notice-anywhere-along-a-chain-is-reported" {
  setting "jobs" type="uint" default=1
  setting "concurrency" type="uint" renamed-to="jobs" deprecated="Use jobs instead."
  setting "threads" type="uint" renamed-to="concurrency"
  layer "file" id="hk.toml" { value "threads" "8" }
  expect { value "jobs" 8; warning "deprecated"; warning "renamed" }
}

And you had the resolution path right too: it checked the exact written alias while explain walked the chain, so config explain threads told a user to stop using a key that running the CLI said nothing about. There is one walker now, Registry::deprecation, called by both.

AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5; version: unavailable.

@jdx
jdx merged commit 653ec1e into main Aug 15, 2026
9 of 13 checks passed
@jdx
jdx deleted the agent/config-resolution-docs branch August 15, 2026 11:00
jdx added a commit that referenced this pull request Aug 15, 2026
…hain (#893)

A bug found by writing the corpus vector for a rule
[#882](#882) already
stated on the page.

`explain` walked the chain of renames looking for a deprecation notice —
`a` renamed to `b`, and `b`
the one carrying the notice that says to use `c` — with a comment
explaining why. The merge read the
notice off the declaration the user wrote. So:

```
$ hk config explain threads
threads = 8
  deprecated: Use jobs instead.

$ hk check          # nothing at all
```

The quieter of the two is the one a CLI actually surfaces, so the user
is told to stop using the key
only if they happen to ask about it.

Same shape as most of this stack's findings: one rule, two
implementations. Fixed by moving the
walker onto `Registry` and having both callers ask it, rather than
teaching the second one to agree.

The message still names the key that was written — `threads is
deprecated: Use jobs instead.` —
because that is the line in the file they would go and edit, not the one
the notice happens to be
attached to.

## Verification

A test at the merge and the corpus vector that found it. Mutating the
walk back to "read the
declaration written" fails both.

*AI-assisted — Tool: Claude Code; model: anthropic/claude-opus-5;
version: unavailable.*

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Targeted behavior fix in config resolution and explain output; covered
by new tests and corpus vectors, with no auth or security surface.
> 
> **Overview**
> **Unifies deprecation discovery** so `config explain` and config merge
behave the same when a notice sits on an intermediate rename (e.g.
`threads` → `concurrency` → `jobs`, with deprecation on `concurrency`).
> 
> The rename-chain walker moves from a private helper in `explain` to
**`Registry::deprecation`**. **`explain`** and **`resolve`** both call
it. Merge no longer reads `deprecated` only off the declaration for the
key the user wrote, which meant CLIs could stay silent while `config
explain` still showed a deprecation.
> 
> Warnings still use the **written** key (`threads is deprecated: …`) so
messages point at the line in the file users would edit.
> 
> Adds a merge unit test and corpus vector
**`a-notice-anywhere-along-a-chain-is-reported`** in `04-renames.kdl`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
a09162b. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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.

1 participant