From e2ca6ad849e75b2ebd9f4f318a2f073ef5474b9a Mon Sep 17 00:00:00 2001 From: "Vitaly D." Date: Wed, 24 Jun 2026 20:57:37 +0300 Subject: [PATCH] feat(cli)!: expose cak as public command Why: - The product CLI should have one public name, cak, instead of exposing the runtime-internal cakrt prototype name. - Keeping both names would make install/update UX and host adapter docs ambiguous. What changed: - Rename the Cargo binary target and clap command from cakrt to cak. - Update host skill wrappers, descriptors, fixtures docs, and runtime docs to call cak. - Add regression coverage that rejects the legacy --cakrt wrapper option. Testing: - prek run --all-files - cargo run -q --bin cak -- --help - cargo run -q -p cak-runtime-cli -- gate --proposal runtime-fixtures/rdr-review/pending_trace_status_blocked.request.json - cargo run -q --bin cakrt -- --help verified the legacy binary no longer exists. Risk: - narrow - consumers using cakrt directly must switch to cak; no compatibility alias is intentionally kept. BREAKING CHANGE: The public CLI binary is now cak; cakrt is removed. --- crates/cak-runtime-cli/Cargo.toml | 4 +- crates/cak-runtime-cli/src/main.rs | 4 +- crates/cak-runtime-cli/tests/cli.rs | 24 +++++----- docs/22_cak_runtime_v0.md | 22 ++++----- runtime-fixtures/README.md | 4 +- skills/cak-host-adapter/SKILL.md | 2 +- skills/cak-host-adapter/scripts/cak_gate.py | 13 +++--- skills/cak-rdr-review/SKILL.md | 10 ++--- skills/cak-rdr-review/cak.yaml | 2 +- tests/test_cak_host_adapter_skill.py | 50 ++++++++++++++++----- 10 files changed, 82 insertions(+), 53 deletions(-) diff --git a/crates/cak-runtime-cli/Cargo.toml b/crates/cak-runtime-cli/Cargo.toml index b8bd23f..f534100 100644 --- a/crates/cak-runtime-cli/Cargo.toml +++ b/crates/cak-runtime-cli/Cargo.toml @@ -4,13 +4,13 @@ version.workspace = true edition.workspace = true license.workspace = true repository.workspace = true -description = "cakrt: thin CLI over the CAK Runtime v0 core. Reads an EvalRequest JSON file, prints a Decision." +description = "cak: public CLI over the CAK Runtime v0 core. Reads EvalRequest and host proposal JSON files." [lints] workspace = true [[bin]] -name = "cakrt" +name = "cak" path = "src/main.rs" [dependencies] diff --git a/crates/cak-runtime-cli/src/main.rs b/crates/cak-runtime-cli/src/main.rs index fb7d59e..2ac28d6 100644 --- a/crates/cak-runtime-cli/src/main.rs +++ b/crates/cak-runtime-cli/src/main.rs @@ -1,4 +1,4 @@ -//! `cakrt` — a thin CLI over the CAK Runtime v0 core. +//! `cak` — the public CLI over the CAK Runtime v0 core. //! //! The CLI does the I/O the core refuses to do (read a file, print JSON) and //! nothing else. It never evaluates anything itself; it hands the parsed @@ -21,7 +21,7 @@ use clap::{Parser, Subcommand}; /// CAK Runtime v0 decision engine CLI. #[derive(Debug, Parser)] -#[command(name = "cakrt", version, about, long_about = None)] +#[command(name = "cak", version, about, long_about = None)] struct Cli { #[command(subcommand)] command: Command, diff --git a/crates/cak-runtime-cli/tests/cli.rs b/crates/cak-runtime-cli/tests/cli.rs index 8edf706..5315758 100644 --- a/crates/cak-runtime-cli/tests/cli.rs +++ b/crates/cak-runtime-cli/tests/cli.rs @@ -1,9 +1,9 @@ -//! CLI integration: exit-code policy and command behavior for `cakrt`. +//! CLI integration: exit-code policy and command behavior for `cak`. use std::path::{Path, PathBuf}; use std::process::Command; -const BIN: &str = env!("CARGO_BIN_EXE_cakrt"); +const BIN: &str = env!("CARGO_BIN_EXE_cak"); fn fixtures_root() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")) @@ -27,7 +27,7 @@ fn eval_block_exits_zero_by_default() { .arg("--request") .arg(request("rdr-review/not_ready_merge")) .output() - .expect("run cakrt"); + .expect("run cak"); assert_eq!( output.status.code(), Some(0), @@ -47,7 +47,7 @@ fn eval_block_exits_two_with_enforce_flag() { .arg(request("rdr-review/not_ready_merge")) .arg("--enforce-exit-code") .status() - .expect("run cakrt"); + .expect("run cak"); assert_eq!( status.code(), Some(2), @@ -64,7 +64,7 @@ fn eval_non_block_exits_zero_even_with_enforce_flag() { .arg(request("skill-graph/draft_skill_authoritative_activation")) .arg("--enforce-exit-code") .status() - .expect("run cakrt"); + .expect("run cak"); assert_eq!(status.code(), Some(0)); // `allow` likewise. @@ -74,7 +74,7 @@ fn eval_non_block_exits_zero_even_with_enforce_flag() { .arg(request("misc/unrelated_readme_edit_allowed")) .arg("--enforce-exit-code") .status() - .expect("run cakrt"); + .expect("run cak"); assert_eq!(status.code(), Some(0)); } @@ -85,7 +85,7 @@ fn eval_missing_file_exits_one() { .arg("--request") .arg(fixtures_root().join("does_not_exist.json")) .status() - .expect("run cakrt"); + .expect("run cak"); assert_eq!(status.code(), Some(1), "missing input must exit 1"); } @@ -98,7 +98,7 @@ fn fixture_check_passes_on_match() { .arg("--expected") .arg(expected("proof/verified_claim_without_grounding")) .status() - .expect("run cakrt"); + .expect("run cak"); assert_eq!(status.code(), Some(0)); } @@ -112,7 +112,7 @@ fn fixture_check_fails_on_mismatch() { .arg("--expected") .arg(expected("misc/unrelated_readme_edit_allowed")) .status() - .expect("run cakrt"); + .expect("run cak"); assert_eq!(status.code(), Some(1)); } @@ -123,7 +123,7 @@ fn gate_denies_blocking_decision_by_default_with_zero_exit() { .arg("--proposal") .arg(request("rdr-review/pending_trace_status_blocked")) .output() - .expect("run cakrt gate"); + .expect("run cak gate"); assert_eq!( output.status.code(), Some(0), @@ -145,7 +145,7 @@ fn gate_denies_blocking_decision_with_exit_two_when_enforced() { .arg(request("rdr-review/pending_trace_status_blocked")) .arg("--enforce-exit-code") .status() - .expect("run cakrt gate"); + .expect("run cak gate"); assert_eq!( status.code(), Some(2), @@ -160,7 +160,7 @@ fn gate_allows_non_rdr_mark_ready() { .arg("--proposal") .arg(request("rdr-review/non_rdr_mark_ready_allowed")) .output() - .expect("run cakrt gate"); + .expect("run cak gate"); assert_eq!(output.status.code(), Some(0)); let outcome: serde_json::Value = diff --git a/docs/22_cak_runtime_v0.md b/docs/22_cak_runtime_v0.md index 4c06b2c..3505581 100644 --- a/docs/22_cak_runtime_v0.md +++ b/docs/22_cak_runtime_v0.md @@ -15,7 +15,7 @@ it does. It is implemented as a small Rust workspace inside this repository: Cargo.toml workspace root (resolver 2, edition 2021) crates/cak-runtime-core/ the engine: data models + evaluators (no I/O) crates/cak-host-adapter/ maps runtime decisions to host-facing outcomes -crates/cak-runtime-cli/ cakrt: a thin CLI over the core +crates/cak-runtime-cli/ cak: the public CLI over the core runtime-fixtures/ request/expected JSON pairs (the contract, executable) skills/cak-rdr-review/ Agent-Skills-compatible pilot host package skills/cak-host-adapter/ thin Python launcher for the Rust adapter @@ -27,7 +27,7 @@ skills/cak-host-adapter/ thin Python launcher for the Rust adapter - A set of four small **evaluators** (gates) composed by priority. - A host adapter that maps runtime decisions to `proceed`, `deny`, `inject_context`, and `needs_*` outcomes. -- A CLI, `cakrt`, that reads request/proposal files and prints decisions or +- A CLI, `cak`, that reads request/proposal files and prints decisions or host-facing outcomes. The design follows the RDR-001 working hypothesis that an agent-native skill is @@ -112,7 +112,7 @@ matches, the composite returns a neutral `allow`. ## Exit-code policy -`cakrt eval` distinguishes a **domain decision** from a **process error**: +`cak eval` distinguishes a **domain decision** from a **process error**: - exit `0` for any valid decision by default — including `block`; - exit `1` only for invalid input or a runtime error (missing file, bad JSON, a @@ -127,27 +127,27 @@ failure when it opts in with `--enforce-exit-code`. ```sh # Evaluate a request and print the decision (exit 0 even for block): -cakrt eval --request runtime-fixtures/rdr-review/not_ready_merge.request.json +cak eval --request runtime-fixtures/rdr-review/not_ready_merge.request.json # Same, but a block decision exits 2 (CI gate mode): -cakrt eval --request --enforce-exit-code +cak eval --request --enforce-exit-code # Check a request against its expected decision fixture: -cakrt fixture-check \ +cak fixture-check \ --request runtime-fixtures/rdr-review/not_ready_merge.request.json \ --expected runtime-fixtures/rdr-review/not_ready_merge.expected.json # Evaluate a host proposal and print a host-facing outcome: -cakrt gate --proposal runtime-fixtures/rdr-review/pending_trace_status_blocked.request.json +cak gate --proposal runtime-fixtures/rdr-review/pending_trace_status_blocked.request.json ``` A host skill calls the runtime exactly the same way: it assembles an `EvalRequest` JSON document from whatever facts it has gathered, runs -`cakrt eval --request `, and honors the returned decision. The runtime +`cak eval --request `, and honors the returned decision. The runtime decision is authoritative over prose. See `skills/cak-rdr-review/SKILL.md` for the pilot host package. -For hosts that need a pre-execution adapter, `cakrt gate --proposal ` +For hosts that need a pre-execution adapter, `cak gate --proposal ` returns a host-facing outcome. The bundled `skills/cak-host-adapter` package is only a Python launcher around that Rust command; it does not implement policy. @@ -205,7 +205,7 @@ Title: feat(runtime): add Rust CAK runtime v0 Summary - Adds a host-neutral Rust decision engine: EvalRequest -> Decision. -- Two crates: cak-runtime-core (pure, no I/O) and cak-runtime-cli (cakrt). +- Two crates: cak-runtime-core (pure, no I/O) and cak-runtime-cli (binary: `cak`). - Four evaluators (lifecycle, stage, proof, rdr-review) composed by priority. - 15 request/expected fixtures as the executable contract. - Agent-Skills-compatible pilot package skills/cak-rdr-review. @@ -217,7 +217,7 @@ Boundary and non-goals the request JSON only. Exit-code decision -- `block` is a valid domain decision: `cakrt eval` exits 0 for any valid +- `block` is a valid domain decision: `cak eval` exits 0 for any valid decision by default. It only exits non-zero for `block` when `--enforce-exit-code` is used (then exit 2). Exit 1 is reserved for invalid input or runtime error. diff --git a/runtime-fixtures/README.md b/runtime-fixtures/README.md index 8f525a5..dbaabc8 100644 --- a/runtime-fixtures/README.md +++ b/runtime-fixtures/README.md @@ -6,7 +6,7 @@ Each pair is a request and the exact decision the engine must produce: ```text /.request.json -> an EvalRequest -/.expected.json -> the Decision cakrt must return +/.expected.json -> the Decision cak must return ``` The Rust test suite (`cargo test --workspace`) evaluates every request and @@ -41,4 +41,4 @@ cargo run -p cak-runtime-cli -- fixture-check \ Expected files are generated from the engine and committed as the regression baseline. If you change rule behavior intentionally, regenerate the affected -`*.expected.json` with `cakrt eval` and review the diff. +`*.expected.json` with `cak eval` and review the diff. diff --git a/skills/cak-host-adapter/SKILL.md b/skills/cak-host-adapter/SKILL.md index ba94862..8fb2932 100644 --- a/skills/cak-host-adapter/SKILL.md +++ b/skills/cak-host-adapter/SKILL.md @@ -9,7 +9,7 @@ This skill is a thin launcher for the Rust host adapter. It does not implement policy logic in Python. The script calls: ```sh -cakrt gate --proposal +cak gate --proposal ``` The proposal JSON currently uses the same shape as `EvalRequest`; the Rust diff --git a/skills/cak-host-adapter/scripts/cak_gate.py b/skills/cak-host-adapter/scripts/cak_gate.py index 34793db..4b40d7d 100755 --- a/skills/cak-host-adapter/scripts/cak_gate.py +++ b/skills/cak-host-adapter/scripts/cak_gate.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 -"""Thin Python launcher for `cakrt gate`. +"""Thin Python launcher for `cak gate`. The policy decision stays in Rust. This script exists so Agent-Skills-style -hosts that expect a Python script can invoke the canonical CAK adapter binary. +hosts that expect a Python script can invoke the canonical CAK CLI. """ from __future__ import annotations @@ -14,9 +14,12 @@ def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Run the CAK Rust host adapter gate.") + parser = argparse.ArgumentParser( + description="Run the CAK Rust host adapter gate.", + allow_abbrev=False, + ) parser.add_argument("--proposal", required=True, type=Path, help="Path to proposal JSON.") - parser.add_argument("--cakrt", default="cakrt", help="Path to the cakrt binary.") + parser.add_argument("--cak", default="cak", help="Path to the cak binary.") parser.add_argument( "--enforce-exit-code", action="store_true", @@ -27,7 +30,7 @@ def parse_args() -> argparse.Namespace: def main() -> int: args = parse_args() - command = [args.cakrt, "gate", "--proposal", str(args.proposal)] + command = [args.cak, "gate", "--proposal", str(args.proposal)] if args.enforce_exit_code: command.append("--enforce-exit-code") diff --git a/skills/cak-rdr-review/SKILL.md b/skills/cak-rdr-review/SKILL.md index a786c8b..c711f5e 100644 --- a/skills/cak-rdr-review/SKILL.md +++ b/skills/cak-rdr-review/SKILL.md @@ -6,7 +6,7 @@ description: Review CAK R&D PRs and research packets for unsupported claims, ove # CAK RDR Review This skill gates CAK research-and-development pull requests and research packets -through the **CAK Runtime v0** decision engine (`cakrt`). It is the pilot host +through the **CAK Runtime v0** decision engine (`cak`). It is the pilot host package for that runtime. ## When to use @@ -32,7 +32,7 @@ encoding them into an `EvalRequest` JSON document. The contract is: ```text -EvalRequest (JSON) -> cakrt eval -> Decision (JSON) +EvalRequest (JSON) -> cak eval -> Decision (JSON) ``` The host agent should: @@ -42,12 +42,12 @@ The host agent should: 2. Run the runtime: ```sh - cakrt eval --request + cak eval --request ``` 3. Read the `Decision` from stdout and honor it. -By default `cakrt eval` exits `0` for **any** valid decision, including `block` +By default `cak eval` exits `0` for **any** valid decision, including `block` — `block` is a domain verdict, not a process failure. Pass `--enforce-exit-code` to make a `block` decision exit `2` (useful as a CI gate). @@ -128,7 +128,7 @@ or `pass` — *"Trace corpus plan is not sufficient."* Worked request/expected pairs live in `runtime-fixtures/`. Verify any of them: ```sh -cakrt fixture-check \ +cak fixture-check \ --request runtime-fixtures/rdr-review/not_ready_merge.request.json \ --expected runtime-fixtures/rdr-review/not_ready_merge.expected.json ``` diff --git a/skills/cak-rdr-review/cak.yaml b/skills/cak-rdr-review/cak.yaml index 17c1c13..4e1cc09 100644 --- a/skills/cak-rdr-review/cak.yaml +++ b/skills/cak-rdr-review/cak.yaml @@ -9,7 +9,7 @@ version: 0.1.0 kind: advisory_plus_verifier runtime: - binary: cakrt + binary: cak command: eval mode: cli diff --git a/tests/test_cak_host_adapter_skill.py b/tests/test_cak_host_adapter_skill.py index 77bf9af..bf4ac8a 100644 --- a/tests/test_cak_host_adapter_skill.py +++ b/tests/test_cak_host_adapter_skill.py @@ -8,13 +8,8 @@ SCRIPT = ROOT / "skills" / "cak-host-adapter" / "scripts" / "cak_gate.py" -def test_skill_shim_delegates_to_cakrt_gate(tmp_path: Path) -> None: - proposal = tmp_path / "proposal.json" - proposal.write_text('{"schema_version":"0.1.0"}', encoding="utf-8") - argv_path = tmp_path / "argv.json" - - fake_cakrt = tmp_path / "fake-cakrt" - fake_cakrt.write_text( +def write_fake_cak(path: Path, argv_path: Path) -> None: + path.write_text( "\n".join( [ "#!/usr/bin/env python3", @@ -28,7 +23,16 @@ def test_skill_shim_delegates_to_cakrt_gate(tmp_path: Path) -> None: ), encoding="utf-8", ) - os.chmod(fake_cakrt, 0o755) + os.chmod(path, 0o755) + + +def test_skill_shim_delegates_to_cak_gate_from_path(tmp_path: Path) -> None: + proposal = tmp_path / "proposal.json" + proposal.write_text('{"schema_version":"0.1.0"}', encoding="utf-8") + argv_path = tmp_path / "argv.json" + + fake_cak = tmp_path / "cak" + write_fake_cak(fake_cak, argv_path) result = subprocess.run( [ @@ -36,19 +40,41 @@ def test_skill_shim_delegates_to_cakrt_gate(tmp_path: Path) -> None: str(SCRIPT), "--proposal", str(proposal), - "--cakrt", - str(fake_cakrt), ], check=False, capture_output=True, text=True, + env={**os.environ, "PATH": f"{tmp_path}{os.pathsep}{os.environ['PATH']}"}, ) assert result.returncode == 0 assert json.loads(result.stdout)["outcome"] == "proceed" - assert json.loads(argv_path.read_text(encoding="utf-8")) == [ - str(fake_cakrt), + argv = json.loads(argv_path.read_text(encoding="utf-8")) + assert Path(argv[0]).name == "cak" + assert argv[1:] == [ "gate", "--proposal", str(proposal), ] + + +def test_skill_shim_rejects_legacy_cakrt_option(tmp_path: Path) -> None: + proposal = tmp_path / "proposal.json" + proposal.write_text('{"schema_version":"0.1.0"}', encoding="utf-8") + + result = subprocess.run( + [ + sys.executable, + str(SCRIPT), + "--proposal", + str(proposal), + "--cakrt", + str(tmp_path / "cakrt"), + ], + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode == 2 + assert "unrecognized arguments: --cakrt" in result.stderr