Skip to content

feat(client)!: non_exhaustive API types with request initializers; Card.external_links and CardFile.uid; external links without a second request; cargo-semver-checks in CI - #27

Merged
dsociative merged 12 commits into
masterfrom
feat/26-non-exhaustive-models
Aug 27, 2026
Merged

feat(client)!: non_exhaustive API types with request initializers; Card.external_links and CardFile.uid; external links without a second request; cargo-semver-checks in CI#27
dsociative merged 12 commits into
masterfrom
feat/26-non-exhaustive-models

Conversation

@dsociative

@dsociative dsociative commented Aug 27, 2026

Copy link
Copy Markdown
Owner

Closes #26, closes #24. Stacked on #25 (base branch chore/refresh-card-fixtures; retarget to master once #25 is in).

What changes

  1. #[non_exhaustive] on every response model and on KaitenError (FileRef stays a closed enum). Fields remain pub and readable; only struct literals and exhaustive matches outside the crate stop compiling. From here on, adding fields and variants is a minor change — cargo semver-checks classifies the follow-up field additions in this very PR as minor.
  2. Request types get initializersCreateCard::new(board_id, title) (its misleading Default is gone), UpdateCard::default(), CardFilter::default(), then field assignment; all three are #[non_exhaustive], so new inputs are added as Option<T> without touching signatures. The 22 literal constructions in the CLI, MCP and tests were rewritten.
  3. Fields the API already sends: Card.external_links, CardFile.uid (the uid key on the classic storage, the string id on the newer one). FileRef::Uid now comes from uid (url parsing is the fallback) and FileRef::matches accepts the wire uid, so every uid shown can be passed back to card file get / download_file.
  4. card view --include external_links / get_card include: read the links from the card response instead of a second request #24 done the simple way: card view prints the card's links and MCP get_card returns them from the card response — zero extra requests (verified live with -vv: one request for card view, two with --include comments). --include external_links / include: ["external_links"] stay accepted (CLI/MCP contracts are only extended) but are deprecated like --comments: help annotation, one stderr line, no-op; README lists the deprecated forms, removed only in a CLI major. comments remains opt-in.
  5. Review of the old workarounds: derived uid wrappers in the CLI (FileListEntry) and MCP (FileView) replaced by the model field; docs updated (Card, CardFile, ExternalLink, crate docs, README Versioning).
  6. CI: cargo semver-checks on pull requests against the PR's base commit — minor by default, major with the breaking label, read live when the job runs (this PR carries it; probe PRs [probe — do not merge] semver gate must fail on a breaking change without the breaking label #28/[probe — do not merge] semver gate (base-commit baseline) must fail without the breaking label #29 showed the red path without it). The check against the last release stays a local step before tagging. Known blind spot documented: a removed derive (CreateCard: Default here) is not reported by cargo-semver-checks 0.50.
  7. AGENTS.md (+ CLAUDE.md importing it): the project's working rules, including the compatibility rule for the binary's contracts and the commit gate.

semver

cargo semver-checks check-release -p kaiten-client --baseline-rev v0.5.0major, exactly two lints: struct_marked_non_exhaustive (25 structs) and enum_marked_non_exhaustive (KaitenError). With --release-type major: clean. No version bump in this PR — the release decides the number (next 0.y per Cargo's rules).

Migration for downstream users of kaiten-client

  • Card { .. } / other model literals → deserialize (serde_json::from_value) or read fields; match err { KaitenError::Api { .. } => .. } needs a _ => arm.
  • CreateCard { board_id, title, ..Default::default() }let mut req = CreateCard::new(board_id, title); req.description = ..; (CreateCard::default() no longer exists). UpdateCard { .. } / CardFilter { .. }::default() + assignments.

User-visible behaviour

  • card view shows External links: whenever the card has any; --json carries external_links inside the card object.
  • card file list --json prints uid for classic-storage files too (the API's own value); MCP FileView.uid likewise.
  • MCP get_card includes external_links when non-empty without include.

Verification

299 tests green, clippy/fmt clean; live smoke on dstest (read-only on the frozen fixture card, mutations on the playground card: CreateCard::new / UpdateCard / CardFilter paths, temporary card deleted); a throwaway PR without the label proves the semver job goes red on a breaking change.

Every response model in `models.rs` (all but the closed `FileRef` enum) and
`KaitenError` can no longer be constructed with a struct literal or matched
exhaustively outside the crate, so adding fields and variants is a minor
change from here on. Fields stay `pub` and readable. Nothing in this
workspace needed a change — the CLI already matches errors with a wildcard
and builds models only from JSON.

BREAKING CHANGE for downstream code that wrote `Card { .. }` literals or
matched `KaitenError` without `_`.
…d/CardFilter are #[non_exhaustive]

`CreateCard::new(board_id, title)` covers the two required fields (its
`Default`, which produced an invalid empty request, is gone);
`UpdateCard::default()` / `CardFilter::default()` stay for the all-optional
ones. Optional fields are set by assignment on the value, so new ones can be
added as `Option<T>` without touching any signature. The 22 literal
constructions in the CLI, MCP server and tests were rewritten accordingly.

BREAKING CHANGE for downstream struct literals of the three request types.
…hrough CLI and MCP

`Card.external_links` is what `GET /cards/{id}` (and `GET /cards`, for cards
that have any) embeds. `CardFile.uid` is the file's UUID on both storages —
the `uid` key on the classic one, the string `id` on the newer one — and
replaces the url parsing as the primary source of `FileRef::Uid`;
`FileRef::matches` accepts the wire uid as well, so every uid shown can be
passed back to `card file get` / `download_file`. `card file list --json`
and the MCP `FileView` now print the model's `uid` instead of a derived
one (classic files gain the key). Additive on top of the non_exhaustive
models — `cargo semver-checks` classifies it as minor.
`card view` prints the card's `external_links` and MCP `get_card` returns
them from the card response itself; `--include external_links` and
`include: ["external_links"]` stay accepted for compatibility (the CLI
value still adds the top-level `external_links` key to the `--json`
envelope), but trigger no request. `comments` remains an opt-in second
request. Tests pin `.expect(0)` on `/cards/{id}/external-links` for the
view/get paths; README and help texts follow.
…th the `breaking` label

`kaiten-client`'s public API is compared with its latest crates.io release.
Additive changes pass; a breaking change fails unless the PR carries the
`breaking` label, which turns the check into a declared major (0.y) bump.
Pull requests only, so master between a declared break and its release stays
green.
@dsociative dsociative added the breaking Intentional breaking change of the kaiten-client public API (semver-major; ships in the next 0.y) label Aug 27, 2026
@codecov-commenter

codecov-commenter commented Aug 27, 2026

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 95.93909% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.94%. Comparing base (27a562f) to head (2b8be03).

Files with missing lines Patch % Lines
crates/kaiten/src/mcp/mod.rs 94.11% 6 Missing ⚠️
crates/kaiten/src/commands/card.rs 94.73% 2 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@                       Coverage Diff                       @@
##           chore/refresh-card-fixtures      #27      +/-   ##
===============================================================
+ Coverage                        86.70%   86.94%   +0.23%     
===============================================================
  Files                               33       33              
  Lines                             4897     4941      +44     
===============================================================
+ Hits                              4246     4296      +50     
+ Misses                             651      645       -6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…; review follow-ups

With the crates.io release as baseline every PR after a merged breaking
change would fail until the release is published, and a label added after
the run would not count. The job now compares the PR with its base commit
(`fetch-depth: 0`) and reads the `breaking` label through `gh` when it runs.
cargo-semver-checks does not report a removed derive (`CreateCard: Default`
here) — documented as a blind spot to list by hand.

Also: `get_card include: ["external_links"]` keeps the `[]` shape on a card
without links (pinned, CLI counterpart pinned too); an empty string never
becomes a file uid; docs no longer say the sections are "fetched".
@dsociative
dsociative force-pushed the feat/26-non-exhaustive-models branch from a7b2170 to c368cd2 Compare August 27, 2026 16:27
… releases and only ever extended; gates before every commit
…ernal_links"]` instead of removing it

Same path as `--comments`: still accepted, still a no-op, marked deprecated
in --help and the MCP param description, one stderr line when used; README
lists the deprecated forms, all of which go away only in a CLI major.
…ecation stays in help, stderr and the MCP param description
@dsociative
dsociative changed the base branch from chore/refresh-card-fixtures to master August 27, 2026 16:47
@dsociative
dsociative merged commit 1ff7832 into master Aug 27, 2026
8 checks passed
@dsociative
dsociative deleted the feat/26-non-exhaustive-models branch August 27, 2026 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Intentional breaking change of the kaiten-client public API (semver-major; ships in the next 0.y)

Projects

None yet

2 participants