Confset generates native tool configuration from one confset.pkl. It embeds
pklr, so generation does not require an installed
Pkl CLI. Tools and editor extensions read the generated files through their
normal configuration discovery.
confset init --gitignore
# Add your tools to confset.pkl.
confset generate --watchA small configuration:
amends "package://github.com/mathematic-inc/confset/releases/download/v0.1.1/confset@0.1.1#/Config.pkl" // x-release-please-version
import "package://github.com/mathematic-inc/confset/releases/download/v0.1.1/confset@0.1.1#/Builtins.pkl" // x-release-please-version
tools {
["lint"] = (Builtins.oxlint) {
config {
categories {
correctness = "deny"
suspicious = "deny"
}
}
}
["format"] = (Builtins.oxfmt) {
config {
printWidth = 100
semi = false
}
}
}confset generate writes .oxlintrc.json and .oxfmtrc.json. Run oxlint,
oxfmt, or their editor extensions as usual. In CI, run confset generate
before your normal checks. Keep confset.pkl in version control; commit the
native files too, or opt into the managed ignore block.
Download a native archive from the GitHub releases,
or use cargo binstall confset to install a prebuilt binary. To build from
source, run cargo install confset --locked with Rust 1.98.1 or newer.
The installed command is confset.
confset [--config FILE] init [--gitignore]
confset [--config FILE] generate [--watch]
confset [--config FILE] validate
confset [--config FILE] list
confset [--config FILE] clean
| Command | Effect |
|---|---|
init |
Create an empty starter if absent; preserve an existing source. It does not generate tool configuration. |
init --gitignore |
Also evaluate the declarations and establish a marked .gitignore block. |
generate |
Compile all outputs, check ownership and conflicts, then publish. |
generate --watch |
Generate immediately and regenerate after local inputs change. Errors keep the last successful outputs; a later save retries. Ctrl-C leaves the outputs in place. |
validate |
Evaluate, render, and check paths, ownership, and competing files without publishing. It can populate the Pkl package cache. |
list |
Show each declaration, format, destination, and missing, managed, modified, or unowned status. |
clean |
Remove unchanged owned outputs, including declarations since removed. It works with invalid or deleted source files and preserves modified or ambiguous outputs. |
Failures return a nonzero exit status. Watch mode reports compilation errors and keeps running. Another generation or cleanup process cannot write while watch mode holds the project lock.
Confset selects exactly one source, in this order:
--config FILE.CONFSET_CONFIG.- The nearest
confset.pkl, searching the current directory and its ancestors. - The operating system's user configuration directory, under
confset/config.pkl.
The user locations are $XDG_CONFIG_HOME/confset/config.pkl (or
~/.config/confset/config.pkl) on Linux,
~/Library/Application Support/confset/config.pkl on macOS, and
%APPDATA%\confset\config.pkl on Windows.
Relative destinations use the selected project's configuration directory. With
the user configuration fallback they use the directory where Confset was invoked.
An explicit missing source can be created by init; otherwise init creates
confset.pkl in the current directory when discovery finds nothing.
An entry point can import modules from any depth. In the
nested example, the root imports
config/web.pkl and config/python.pkl; both import
config/shared/settings.pkl. Editing the shared file regenerates both tool
configurations in watch mode. Import paths are relative to the importing Pkl
file. Output paths remain relative to the selected entry point's project root.
Pkl inheritance works too. A file can amends "config/project.pkl", which can
amend shared/base.pkl, and override selected settings at each level. Watch mode
tracks the entire local dependency chain, including files outside the root.
Independent subprojects can each contain a confset.pkl. Running Confset from a
subproject chooses its nearest entry point. A root invocation does not recursively
run every nested entry point: compose the child modules into the root Pkl file
when you want one generation to cover the whole repository.
Built-ins are amendable Pkl Tool values. The mapping key is your label; multiple
labels can reuse a producer across directories. directory selects a
nested project or an absolute directory. A built-in's path, if supplied, must
match the native filename for the selected format.
| Producer | Formats and native filenames |
|---|---|
oxlint |
JSON: .oxlintrc.json |
oxfmt |
JSON: .oxfmtrc.json |
prettier |
JSON: .prettierrc.json; YAML: .prettierrc.yaml; TOML: .prettierrc.toml |
rustfmt |
TOML: rustfmt.toml, also used by cargo fmt |
ruff |
TOML: ruff.toml |
ty |
TOML: ty.toml |
rumdl |
TOML: .rumdl.toml |
ryl |
TOML: .ryl.toml; YAML: .yamllint.yaml |
tombi |
TOML: tombi.toml |
typos |
TOML: typos.toml |
sqlfluff |
INI: .sqlfluff |
actionlint |
YAML: .github/actionlint.yaml |
gitleaks |
TOML: .gitleaks.toml |
lychee |
TOML: lychee.toml |
cargo_deny |
TOML: deny.toml |
buf |
YAML: buf.yaml |
typespec |
YAML: tspconfig.yaml |
knip |
JSON: knip.json |
shfmt |
INI: .editorconfig |
yamlfmt |
YAML: .yamlfmt |
JSON is the default for producers that support it; ryl defaults to TOML. The
built-in ryl value enables duplicate-key checking because its TOML format requires
an explicit ruleset. Buf starts with version = "v2". Other settings come from
your config value and the tool's defaults.
Confset checks known alternative filenames and relevant package.json fields.
It refuses to generate a competing configuration over a handwritten one. When
you change formats, an unchanged owned alternative is removed in the same
publication. Options inside config remain native data: Confset checks its own
schema and representability, while the native tool validates its option names and
semantics. Format restrictions and filenames live in pkl/catalog.json.
JSON, YAML, and TOML preserve scalar values, nesting, and array order. TOML rejects nulls. INI supports scalar entries and one level of named sections; it rejects arrays, nulls, nested sections, line breaks, and surrounding whitespace that an INI parser could discard. Pkl functions must be called before rendering. There is no JavaScript or TypeScript emitter, callback representation, or runtime hook.
Pkl functions, imports, amendments, and comprehensions compute the final data.
For example, Knip receives a normal knip.json:
local modules = List("main", "worker")
tools {
["unused"] = (Builtins.knip) {
config {
entry = modules.map((name) -> "src/\(name).ts")
project = List("src/**/*.ts")
}
}
}For a tool without a built-in, declare files with path, format, and config,
or use a Tool with an explicit path and format. These outputs have the same
ownership protections. Their destination and discovery rules are your choice.
files {
["application"] {
path = "app/settings.yaml"
format = "yaml"
config {
enabled = true
retries = 3
}
}
}Companion files belong in a producer's files mapping. Use format = "text"
with any Pkl function returning a string for additional text formats. The embedded
Renderers.pkl provides lines(List<String>) for newline-delimited files. See
the web example for a companion ignore file,
the polyglot example for nested projects, and
the custom example for a Pkl text renderer.
Pkl can read env:CONFSET_PROJECT_DIR and env:CONFSET_CONFIG_DIR to obtain the
resolved output root and source directory. Ordinary Pkl local imports and reads
are tracked for watching. Remote inputs are evaluated when a local save triggers
a generation; Confset does not poll remote services or environment variables.
.confset/state.json records absolute output paths and last-written SHA-256
hashes. Keep that directory between runs. Losing it removes Confset's evidence
of ownership; identical file contents alone do not authorize adoption.
Confset refuses unowned files, externally edited owned files, symlink destinations or parents, duplicate destinations, file/directory collisions, and collisions with source inputs or state. It compiles and checks the complete output set before writing any output. Unchanged files keep their timestamps and permissions.
Changed files are staged beside their destinations and replaced atomically per file. A durable journal records the previous and staged bytes. The next writer recovers interrupted publication, preserving unexpected edits. Publication is not an atomic transaction across multiple files: a reader can briefly observe a mix during a successful update. A process interruption is recoverable; filesystem and power-loss guarantees still depend on the operating system and storage device.
clean removes only files whose bytes still match their ownership record. It
preserves the Pkl source, keeps records for modified outputs, reports them, and
returns a failure status. Move edited files aside or restore their recorded
content before retrying generation. A malformed ownership record or ambiguous
recovery journal is preserved for inspection rather than discarded.
init --gitignore enables this block:
# BEGIN CONFSET GENERATED FILES
.confset-stage-*
/.oxfmtrc.json
/.oxlintrc.json
/.confset/
# END CONFSET GENERATED FILESEntries are escaped, sorted, and deduplicated. Existing manual content, comments,
other marked blocks, and line endings are preserved. Once enabled, generate
and watch mode keep entries synchronized with published outputs. Without the
block, generation leaves .gitignore alone. Files outside the project directory
are reported as outside its ignore scope. Already tracked generated files are
reported; Confset never changes the Git index. Cleanup leaves the block available
for the next generation.
Start with a GitHub Discussion to propose a change. Mathematic maintainers implement accepted proposals and credit their authors. Read the contribution policy before starting implementation work.
Use Rust 1.98.1 (the CI toolchain), then run:
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test --locked
cargo build --release --lockedDirect dependencies use the latest stable releases selected for this version;
Cargo.lock pins the complete graph. insta snapshots cover the command surface,
diagnostics, and rendered formats. Review snapshot changes before accepting them.
The shared prebuilt workflow builds and tests macOS, Linux, and Windows on amd64 and arm64, including GNU and musl Linux distributions. Native integration tests run the public tools and the actual Oxlint/Oxfmt language servers through an LSP test client. See verification for setup and the distinction between protocol checks and editor UI checks.
When developing on a separate drive, set CARGO_HOME, CARGO_TARGET_DIR, TMPDIR,
CONFSET_PKL_CACHE_DIR, and native-tool cache locations to directories on that
drive. Resolve symlinks before relying on a directory's apparent location.
python scripts/package.py --output DIST builds a release executable and creates
its platform archive, Pkl package metadata, package ZIP, checksums, and third-party
license notices. Pass --target TRIPLE for an installed Rust target. See
the release workflow for artifact names and publication.
MIT.
Confset is free and open-source work by Mathematic Inc., a 501(c)(3) nonprofit. If you find it useful, please consider donating.