From b051aa380d25116873cb1269a23e7c2654c863f2 Mon Sep 17 00:00:00 2001 From: A Tobey Date: Mon, 31 Aug 2026 17:14:49 -0400 Subject: [PATCH] chore: bump to v0.17.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workspace version and all 17 inter-crate pins move to 0.17.0, and CHANGELOG.md's Unreleased section is stamped as [0.17.0] - 2026-08-31. The release review turned up one undeclared break and four documentation defects, all fixed here rather than left for a reader to trip over. ExecContext gained kill_grace and background_job this cycle. It is public, re-exported at the crate root, and every one of its 30 fields is pub, with no #[non_exhaustive] — so a struct literal in an embedder stopped compiling while the changelog filed the change under Changed. Six public constructors already exist (new, with_backend, with_scope, and three more), so the supported construction path was never the literal. Marked BREAKING and made the struct #[non_exhaustive], which costs the literal once and buys silence on every field the kernel adds after this. The changelog claimed the symlink conformance suite has 19 cases; it registers 20, and it said "any Filesystem backend" runs them when the runners are LocalFs, MemoryFs and OverlayFs. DevFs is exempt because it has no symlinks, which is a reason worth stating rather than implying. PlannedStatement.plan still promised "with every credential redacted" after 0.14.0 removed the credential and this release removed the redaction. LANGUAGE.md read as though bare true and false were numeric literals. They are variable names, pinned as unset by arithmetic_adversarial_tests.rs:362; only a variable holding a Bool coerces to 1 or 0. EMBEDDING.md's plan-JSON sample showed kaish_version 0.16.0 carrying the three build-identity keys. 0.16.0 never emitted them — they ship here — so the example described a document that could not exist. The glued-argv error span from #413 had no changelog bullet; it has one now. The "Require kaish 0.16 or newer" line stays as written: it is about --plan existing at all, not about the version fields, which the same section already tells a consumer to read as optional. Gates on this branch: clippy -D warnings, cargo test --all (203 suites, 6485 passed), insta --check, no-default-features, wasi build, and rustdoc -D warnings all clean. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 24 ++++++++++++++++++------ Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- README.md | 2 +- crates/kaish-client/Cargo.toml | 6 +++--- crates/kaish-help/Cargo.toml | 2 +- crates/kaish-kernel/Cargo.toml | 12 ++++++------ crates/kaish-kernel/src/ast/plan.rs | 2 +- crates/kaish-kernel/src/tools/context.rs | 5 +++++ crates/kaish-repl/Cargo.toml | 6 +++--- crates/kaish-tool-api/Cargo.toml | 2 +- crates/kaish-tools-host/Cargo.toml | 4 ++-- crates/kaish-vfs/Cargo.toml | 2 +- docs/EMBEDDING.md | 6 +++--- docs/LANGUAGE.md | 7 ++++--- 15 files changed, 60 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5892e360..259bd9b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,15 +10,21 @@ breaking entries are marked **BREAKING**. ## [Unreleased] +## [0.17.0] - 2026-08-31 + ### Changed - **BREAKING: removed plan-side confirm-key redaction** — `PlannedValue::Redacted`, `StatementPlan::presented_keys`, `strip_confirm_tokens`, and `redact_keys` are gone. The credential they protected was removed in 0.14.0; `--confirm=` now plans and renders like any other argument. -- `ExecContext` carries `kill_grace` and `background_job`, so a tool holding only - an `ExecContext` can spawn a child with the kernel's external-command - discipline. `tools::DEFAULT_KILL_GRACE` is 2s. Only a struct literal changes. +- **BREAKING: `ExecContext` gained two fields and is now `#[non_exhaustive]`** — + it carries `kill_grace` and `background_job`, so a tool holding only an + `ExecContext` can spawn a child with the kernel's external-command discipline. + `tools::DEFAULT_KILL_GRACE` is 2s. A struct literal outside the crate no longer + compiles: build one with `ExecContext::new` or a `with_*` constructor and set + the fields you need. `#[non_exhaustive]` is the point — the next field the + kernel adds will not break you again. - **Arithmetic diverges from bash on purpose** — overflow, an unset/empty operand, and a leading zero are errors, never a wrap or 0; strings are @@ -30,8 +36,9 @@ breaking entries are marked **BREAKING**. path resolver for a backend rooted at a host directory: sync, std-only, containment inside. `Follow::Final` follows the last component; `Follow::LinkItself` acts on the link (remove, rename, lstat, symlink). -- **`kaish_vfs::conformance`** (`conformance` feature) — 19 symlink cases any - `Filesystem` backend runs against itself via `run_all(make_root)`. +- **`kaish_vfs::conformance`** (`conformance` feature) — 20 symlink cases a + `Filesystem` backend runs against itself via `run_all(make_root)`. LocalFs, + MemoryFs, and OverlayFs run them; DevFs is exempt, having no symlinks. - **`[[ -L path ]]`** (alias `-h`) and `test -L` — true when the path is a symlink, including a dangling one. - **`cp -P`/`-L`**; `cp -r` recreates a symlink as a link with the same @@ -86,6 +93,10 @@ breaking entries are marked **BREAKING**. that says what to fix, including the validator's suggested rewrite. A context that carries information, like `Failed to read script: `, stays. +- **The glued-argv error names the word that needs quoting** — `git show + HEAD:dir/x.py` blamed `show`, an innocent token three characters earlier. + The span now covers the whole pasted run, so the advice points at the word + it is about. - **A comparison kaish cannot make is a fault, not `false`** — `[[ ]]`, `test`, and `(( ))` now agree: exit 2 where nothing reads the result as a boolean, and an abort where something does (`if`/`while`, `!`, the left operand of @@ -2568,7 +2579,8 @@ Initial public release of **kaish** (会sh) — a predictable Bourne-like shell - **REPL** (`kaish-repl`) with multi-line input, completion, and history; **MCP server** (`kaish-mcp`) exposing `kaish_execute` with help resources and structured + plain-text content blocks. - **`KernelClient` trait** + `EmbeddedClient` for in-process embedding; topic-based help system; `kaish-wasi` `wasm32-wasip1` target. -[Unreleased]: https://github.com/tobert/kaish/compare/v0.16.0...HEAD +[Unreleased]: https://github.com/tobert/kaish/compare/v0.17.0...HEAD +[0.17.0]: https://github.com/tobert/kaish/compare/v0.16.0...v0.17.0 [0.16.0]: https://github.com/tobert/kaish/compare/v0.15.0...v0.16.0 [0.15.0]: https://github.com/tobert/kaish/compare/v0.14.1...v0.15.0 [0.14.1]: https://github.com/tobert/kaish/compare/v0.14.0...v0.14.1 diff --git a/Cargo.lock b/Cargo.lock index 7b0a5b1e..939c6d00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1011,7 +1011,7 @@ dependencies = [ [[package]] name = "kaish-client" -version = "0.16.0" +version = "0.17.0" dependencies = [ "anyhow", "async-trait", @@ -1027,7 +1027,7 @@ dependencies = [ [[package]] name = "kaish-glob" -version = "0.16.0" +version = "0.17.0" dependencies = [ "async-trait", "ignore", @@ -1039,14 +1039,14 @@ dependencies = [ [[package]] name = "kaish-help" -version = "0.16.0" +version = "0.17.0" dependencies = [ "kaish-types", ] [[package]] name = "kaish-kernel" -version = "0.16.0" +version = "0.17.0" dependencies = [ "anyhow", "ariadne", @@ -1108,7 +1108,7 @@ dependencies = [ [[package]] name = "kaish-repl" -version = "0.16.0" +version = "0.17.0" dependencies = [ "anyhow", "chrono", @@ -1130,7 +1130,7 @@ dependencies = [ [[package]] name = "kaish-tool-api" -version = "0.16.0" +version = "0.17.0" dependencies = [ "async-trait", "clap", @@ -1139,7 +1139,7 @@ dependencies = [ [[package]] name = "kaish-tools-host" -version = "0.16.0" +version = "0.17.0" dependencies = [ "async-trait", "clap", @@ -1150,7 +1150,7 @@ dependencies = [ [[package]] name = "kaish-types" -version = "0.16.0" +version = "0.17.0" dependencies = [ "base64", "schemars", @@ -1163,7 +1163,7 @@ dependencies = [ [[package]] name = "kaish-vfs" -version = "0.16.0" +version = "0.17.0" dependencies = [ "async-trait", "getrandom 0.3.4", @@ -1175,7 +1175,7 @@ dependencies = [ [[package]] name = "kaish-wasi" -version = "0.16.0" +version = "0.17.0" dependencies = [ "kaish-kernel", "kaish-types", diff --git a/Cargo.toml b/Cargo.toml index 181db8e8..91ab0c89 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ members = [ ] [workspace.package] -version = "0.16.0" +version = "0.17.0" edition = "2024" rust-version = "1.85" authors = ["Amy Tobey "] diff --git a/README.md b/README.md index 16916add..01a99ce1 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ Construct a `Kernel`, point it at a sandbox root, call `execute()`: ```toml [dependencies] -kaish-kernel = "0.16" +kaish-kernel = "0.17" tokio = { version = "1", features = ["full"] } ``` diff --git a/crates/kaish-client/Cargo.toml b/crates/kaish-client/Cargo.toml index 5a45bb6e..966a19be 100644 --- a/crates/kaish-client/Cargo.toml +++ b/crates/kaish-client/Cargo.toml @@ -16,8 +16,8 @@ readme = "../../README.md" # Feature-neutral on purpose: the embedder decides the kernel's feature set # (kaish-web builds no-default for wasm; native hosts add localfs etc.). # Unification means enabling defaults here would trample that choice. -kaish-kernel = { path = "../kaish-kernel", version = "0.16.0", default-features = false } -kaish-types = { path = "../kaish-types", version = "0.16.0" } +kaish-kernel = { path = "../kaish-kernel", version = "0.17.0", default-features = false } +kaish-types = { path = "../kaish-types", version = "0.17.0" } # Async traits only — no runtime: the library spawns nothing itself, and a # tokio dep here would drag net/mio into wasm builds (kaish-extras). @@ -39,7 +39,7 @@ tracing = { workspace = true } # this, `cargo test -p kaish-client` alone sees a no-localfs kernel while a # whole-workspace run gets localfs unified in by the REPL, and outcomes # depend on which crates happened to build. -kaish-kernel = { path = "../kaish-kernel", version = "0.16.0", features = ["localfs"] } +kaish-kernel = { path = "../kaish-kernel", version = "0.17.0", features = ["localfs"] } proptest = { workspace = true } tempfile = { workspace = true } # Tests drive the async client API; the library itself needs no runtime — diff --git a/crates/kaish-help/Cargo.toml b/crates/kaish-help/Cargo.toml index 711735a4..a4f91ccf 100644 --- a/crates/kaish-help/Cargo.toml +++ b/crates/kaish-help/Cargo.toml @@ -13,7 +13,7 @@ categories.workspace = true readme = "../../README.md" [dependencies] -kaish-types = { path = "../kaish-types", version = "0.16.0" } +kaish-types = { path = "../kaish-types", version = "0.17.0" } [lints] workspace = true diff --git a/crates/kaish-kernel/Cargo.toml b/crates/kaish-kernel/Cargo.toml index 08592300..a19a188e 100644 --- a/crates/kaish-kernel/Cargo.toml +++ b/crates/kaish-kernel/Cargo.toml @@ -14,11 +14,11 @@ readme = "../../README.md" exclude = ["tests/snapshots/"] [dependencies] -kaish-glob = { path = "../kaish-glob", version = "0.16.0" } -kaish-types = { path = "../kaish-types", version = "0.16.0", features = [] } -kaish-help = { path = "../kaish-help", version = "0.16.0" } -kaish-tool-api = { path = "../kaish-tool-api", version = "0.16.0" } -kaish-vfs = { path = "../kaish-vfs", version = "0.16.0", features = ["memory", "overlay"] } +kaish-glob = { path = "../kaish-glob", version = "0.17.0" } +kaish-types = { path = "../kaish-types", version = "0.17.0", features = [] } +kaish-help = { path = "../kaish-help", version = "0.17.0" } +kaish-tool-api = { path = "../kaish-tool-api", version = "0.17.0" } +kaish-vfs = { path = "../kaish-vfs", version = "0.17.0", features = ["memory", "overlay"] } # Async runtime — minimal base; `localfs` adds tokio/fs, `subprocess` adds # tokio/process + tokio/rt-multi-thread (see [features]). @@ -90,7 +90,7 @@ jaq-json = { workspace = true } # Optional deps, each pulled in by its capability feature: # kaish-tools-host → host, trash/directories → os-integration, # tiktoken-rs → tokens. -kaish-tools-host = { path = "../kaish-tools-host", version = "0.16.0", optional = true } +kaish-tools-host = { path = "../kaish-tools-host", version = "0.17.0", optional = true } trash = { workspace = true, optional = true } directories = { workspace = true, optional = true } tiktoken-rs = { workspace = true, optional = true } diff --git a/crates/kaish-kernel/src/ast/plan.rs b/crates/kaish-kernel/src/ast/plan.rs index 18aad2d7..53eee712 100644 --- a/crates/kaish-kernel/src/ast/plan.rs +++ b/crates/kaish-kernel/src/ast/plan.rs @@ -82,7 +82,7 @@ pub struct PlannedStatement { /// gaps: `plans[i].index == i`, always. Indexing the list by this number /// reads the statement it names. pub index: usize, - /// What the statement was asked to run, with every credential redacted. + /// What the statement was asked to run. pub plan: Plan, } diff --git a/crates/kaish-kernel/src/tools/context.rs b/crates/kaish-kernel/src/tools/context.rs index 6a0252ac..03727021 100644 --- a/crates/kaish-kernel/src/tools/context.rs +++ b/crates/kaish-kernel/src/tools/context.rs @@ -125,6 +125,11 @@ pub(crate) enum ExternalCommandOutcome { /// /// Provides access to the backend (for file operations and tool dispatch), /// scope, and other kernel state. +/// +/// Build one with [`ExecContext::new`] or one of the `with_*` constructors and +/// set the fields you need. The struct is `#[non_exhaustive]`, so the kernel can +/// add a field without breaking an embedder that never asked for it. +#[non_exhaustive] pub struct ExecContext { /// Kernel backend for I/O operations. /// diff --git a/crates/kaish-repl/Cargo.toml b/crates/kaish-repl/Cargo.toml index c8f24938..c2770f9e 100644 --- a/crates/kaish-repl/Cargo.toml +++ b/crates/kaish-repl/Cargo.toml @@ -19,9 +19,9 @@ path = "src/main.rs" [dependencies] # The REPL is the full interactive human shell: it needs every capability # (external commands + job control, git, host introspection, tokens, trash). -kaish-kernel = { path = "../kaish-kernel", version = "0.16.0", features = ["full"] } -kaish-client = { path = "../kaish-client", version = "0.16.0" } -kaish-types = { path = "../kaish-types", version = "0.16.0" } +kaish-kernel = { path = "../kaish-kernel", version = "0.17.0", features = ["full"] } +kaish-client = { path = "../kaish-client", version = "0.17.0" } +kaish-types = { path = "../kaish-types", version = "0.17.0" } # Line editing rustyline = { workspace = true } diff --git a/crates/kaish-tool-api/Cargo.toml b/crates/kaish-tool-api/Cargo.toml index 2ca3c72b..9c7ed49e 100644 --- a/crates/kaish-tool-api/Cargo.toml +++ b/crates/kaish-tool-api/Cargo.toml @@ -13,7 +13,7 @@ categories.workspace = true readme = "../../README.md" [dependencies] -kaish-types = { path = "../kaish-types", version = "0.16.0", features = [] } +kaish-types = { path = "../kaish-types", version = "0.17.0", features = [] } async-trait = { workspace = true } clap = { workspace = true } diff --git a/crates/kaish-tools-host/Cargo.toml b/crates/kaish-tools-host/Cargo.toml index 9b33c68d..dba7a3db 100644 --- a/crates/kaish-tools-host/Cargo.toml +++ b/crates/kaish-tools-host/Cargo.toml @@ -13,8 +13,8 @@ categories.workspace = true readme = "../../README.md" [dependencies] -kaish-types = { path = "../kaish-types", version = "0.16.0", features = [] } -kaish-tool-api = { path = "../kaish-tool-api", version = "0.16.0" } +kaish-types = { path = "../kaish-types", version = "0.17.0", features = [] } +kaish-tool-api = { path = "../kaish-tool-api", version = "0.17.0" } async-trait = { workspace = true } clap = { workspace = true } diff --git a/crates/kaish-vfs/Cargo.toml b/crates/kaish-vfs/Cargo.toml index 756c585b..73fa4519 100644 --- a/crates/kaish-vfs/Cargo.toml +++ b/crates/kaish-vfs/Cargo.toml @@ -13,7 +13,7 @@ categories.workspace = true readme = "../../README.md" [dependencies] -kaish-types = { path = "../kaish-types", version = "0.16.0", features = [] } +kaish-types = { path = "../kaish-types", version = "0.17.0", features = [] } async-trait = { workspace = true } # DevFs's /dev/urandom pulls from the OS CSPRNG. Pure (no tokio); works on # Unix/macOS/WASI. diff --git a/docs/EMBEDDING.md b/docs/EMBEDDING.md index a445eaf5..9ea4e34e 100644 --- a/docs/EMBEDDING.md +++ b/docs/EMBEDDING.md @@ -6,7 +6,7 @@ and output capture. ## Stability -kaish is pre-1.0 (currently 0.16.x, MSRV 1.85). The language has settled; +kaish is pre-1.0 (currently 0.17.x, MSRV 1.85). The language has settled; the embedding API may still change between minor versions where it improves both kaish and its embedders — [kaijutsu](https://github.com/tobert/kaijutsu) is the reference embedder. Pin a minor version and read release notes when @@ -1172,7 +1172,7 @@ PY" "redirects":[{"kind":"<<","target":{"plain":"'PY'"}}],"background":false, "heredocs":[{"index":0,"delimiter":"PY","literal":true,"strip_tabs":false, "body":{"plain":"import os\n"},"body_offset":15}]}]}}], -"kaish_version":"0.16.0","kaish_git_hash":"b27ea4dd","kaish_build_date":"2026-08-23"} +"kaish_version":"0.17.0","kaish_git_hash":"4d02cd6b","kaish_build_date":"2026-08-31"} ``` **Nothing executes and no kernel is built** — planning is a pure function of the @@ -1182,7 +1182,7 @@ source text, so it touches no filesystem and needs no capability feature. The output is always a JSON object, so a caller parses one shape whatever happened: `{"statements": [...]}` and exit **0**, or `{"errors": [...]}` and exit **2** — the same usage code a builtin returns for bad argv. Both shapes -also carry `kaish_version` (bare semver, e.g. `"0.16.0"`), `kaish_git_hash` +also carry `kaish_version` (bare semver, e.g. `"0.17.0"`), `kaish_git_hash` (short hash, or `"unknown"` when kaish was built with no `.git` present — a crates.io tarball build, for instance), and `kaish_build_date` (`YYYY-MM-DD`) at the top level, so a caller windowing measurements by diff --git a/docs/LANGUAGE.md b/docs/LANGUAGE.md index f46898d1..cbb80baa 100644 --- a/docs/LANGUAGE.md +++ b/docs/LANGUAGE.md @@ -1057,9 +1057,10 @@ echo $((2#$bits)) # a string of binary digits ### Variables and expansions -A variable name may omit the `$`. An integer works directly; `true` is 1 and -`false` is 0; a string holding one number in any spelling above is read as -that number. +A variable name may omit the `$`. An integer works directly; a variable holding +`true` reads as 1 and `false` as 0; a string holding one number in any spelling +above is read as that number. A bare `true` is a variable name, not a literal — +`$(( true + 1 ))` names `true` as unset unless you set it. ```sh count=4