diff --git a/.claude/agents/architect.md b/.claude/agents/architect.md index b3f8397..1025225 100644 --- a/.claude/agents/architect.md +++ b/.claude/agents/architect.md @@ -26,12 +26,12 @@ memory: project ## Repo Context -stagecraft-cli is one Rust binary named `stagecraft` with two faces: CLI subcommands for humans and an MCP server (stdio) for agents, both calling the Stagecraft control plane's API under the same identity and JSON shapes. spec-spine is an installed CLI tool that governs the repo: a typed, hash-verifiable authority ledger over the spec corpus. It is a dependency, not source code you edit. +statecraft-cli is one Rust binary named `statecraft` with two faces: CLI subcommands for humans and an MCP server (stdio) for agents, both calling the Statecraft control plane's API under the same identity and JSON shapes. spec-spine is an installed CLI tool that governs the repo: a typed, hash-verifiable authority ledger over the spec corpus. It is a dependency, not source code you edit. | Surface | Path | Notes | |---------|------|-------| | Spec corpus | `specs/NNN-slug/spec.md` | Markdown + YAML frontmatter, the authoritative design record; specs 002-005 are the ordered build backlog (crate scaffold, auth + API client, governance verbs, MCP server) | -| Code | `Cargo.toml`, `src/` (clap command tree; planned by spec 002, pre-code today) | The single `stagecraft` binary the specs govern | +| Code | `Cargo.toml`, `src/` (clap command tree; planned by spec 002, pre-code today) | The single `statecraft` binary the specs govern | | Standard | `standards/spec/{constitution.md,contract.md,templates/}` | Durable principles, normative contract, spec template | | Derived | `.derived/` | Committed compiler output (registry, index), read only through the binary | @@ -56,7 +56,7 @@ Read the request or task document. Identify which surfaces are affected. For each proposed change, check: - Does a spec already exist? If not, should one be authored first? -- Does the approach align with the spec's stated design and constraints? `specs/001-cli-mcp-thesis/spec.md` holds the decided constraints (binary name `stagecraft`, Rust, stdio MCP, Apache-2.0, rustls only, no TUI). +- Does the approach align with the spec's stated design and constraints? `specs/001-cli-mcp-thesis/spec.md` holds the decided constraints (binary name `statecraft`, Rust, stdio MCP, Apache-2.0, rustls only, no TUI). - Are the spec's `depends_on` specs implemented, per the backlog protocol? If a cross-repo dependency or operator prerequisite is missing, the plan must stop and report exactly what is needed, never mock around it. - Are there relationship edges (`establishes`, `extends`, `refines`, `supersedes`, `amends`, `depends_on`) the change must respect or extend? - Will the change require recompiling the registry or refreshing the codebase index? diff --git a/.claude/agents/explorer.md b/.claude/agents/explorer.md index b190b41..79fd8ed 100644 --- a/.claude/agents/explorer.md +++ b/.claude/agents/explorer.md @@ -26,7 +26,7 @@ mutation: read-only ## Repo Context -stagecraft-cli is one Rust binary named `stagecraft` with two faces: CLI subcommands (clap command tree) and an MCP server over stdio. spec-spine is an installed CLI tool that governs the spec corpus. It is a dependency, not source code you edit. +statecraft-cli is one Rust binary named `statecraft` with two faces: CLI subcommands (clap command tree) and an MCP server over stdio. spec-spine is an installed CLI tool that governs the spec corpus. It is a dependency, not source code you edit. | Surface | Path | Tech | |---------|------|------| diff --git a/.claude/agents/implementer.md b/.claude/agents/implementer.md index 6fc048c..da685ee 100644 --- a/.claude/agents/implementer.md +++ b/.claude/agents/implementer.md @@ -27,7 +27,7 @@ mutation: read-write ## Repo Context -stagecraft-cli is one Rust binary named `stagecraft` (CLI subcommands + MCP server over stdio). spec-spine is an installed CLI tool that governs the spec corpus. It is a dependency, not source code you edit. +statecraft-cli is one Rust binary named `statecraft` (CLI subcommands + MCP server over stdio). spec-spine is an installed CLI tool that governs the spec corpus. It is a dependency, not source code you edit. | Surface | Path | Build / verify | |---------|------|----------------| diff --git a/.claude/agents/reviewer.md b/.claude/agents/reviewer.md index 44db56d..34b5459 100644 --- a/.claude/agents/reviewer.md +++ b/.claude/agents/reviewer.md @@ -26,7 +26,7 @@ memory: project ## Repo Context -stagecraft-cli is one Rust binary named `stagecraft` (CLI subcommands + MCP server over stdio) calling the Stagecraft control plane's API. spec-spine is an installed CLI tool that governs the spec corpus. It is a dependency, not source code you edit. +statecraft-cli is one Rust binary named `statecraft` (CLI subcommands + MCP server over stdio) calling the Statecraft control plane's API. spec-spine is an installed CLI tool that governs the spec corpus. It is a dependency, not source code you edit. | Surface | Path | Key concerns | |---------|------|--------------| diff --git a/.claude/skills/init/SKILL.md b/.claude/skills/init/SKILL.md index d9700c4..b23021b 100644 --- a/.claude/skills/init/SKILL.md +++ b/.claude/skills/init/SKILL.md @@ -15,7 +15,7 @@ Claude Code, Codex CLI, Cursor, Copilot, and any future agent). `## ` heading exclusive. That section is the step list. 2. Execute the protocol described there, using parallel tool calls wherever steps are independent. -3. Emit a structured `## initialized: stagecraft-cli` summary: the lifecycle +3. Emit a structured `## initialized: statecraft-cli` summary: the lifecycle counts reported by `spec-spine registry status-report --json --nonzero-only` and the next pending spec in the 002-005 build backlog. diff --git a/.claude/skills/setup/SKILL.md b/.claude/skills/setup/SKILL.md index c776f72..6f90f32 100644 --- a/.claude/skills/setup/SKILL.md +++ b/.claude/skills/setup/SKILL.md @@ -15,7 +15,7 @@ ad-hoc parsing of `.derived/**/*.json` (see ### 1. Verify the Rust toolchain -This repo builds one Rust binary (`stagecraft`, spec 002). Check the +This repo builds one Rust binary (`statecraft`, spec 002). Check the toolchain the gates need: ```bash @@ -68,7 +68,7 @@ re-check. Do not parse `.derived/**/*.json` directly to "verify" success. Report exactly: ``` -## setup: stagecraft-cli +## setup: statecraft-cli **Toolchain:** {cargo / missing} **Install:** {ok / failed at } diff --git a/.derived/codebase-index/by-package/stagecraft-cli.json b/.derived/codebase-index/by-package/stagecraft-cli.json index 436f3b3..b830d5e 100644 --- a/.derived/codebase-index/by-package/stagecraft-cli.json +++ b/.derived/codebase-index/by-package/stagecraft-cli.json @@ -2,7 +2,7 @@ "package": { "edition": "2021", "kind": "rust-bin", - "name": "stagecraft-cli", + "name": "statecraft-cli", "path": "", "specRef": "002-crate-scaffold", "version": "0.1.0" diff --git a/.derived/codebase-index/by-spec/003-auth-api-client.json b/.derived/codebase-index/by-spec/003-auth-api-client.json index bf98387..5ce8c44 100644 --- a/.derived/codebase-index/by-spec/003-auth-api-client.json +++ b/.derived/codebase-index/by-spec/003-auth-api-client.json @@ -23,7 +23,7 @@ "ownership": true, "sourceField": "establishes", "unit": { - "id": "stagecraft_cli::api", + "id": "statecraft_cli::api", "kind": "symbol" } }, @@ -40,7 +40,7 @@ "ownership": true, "sourceField": "establishes", "unit": { - "id": "stagecraft_cli::auth", + "id": "statecraft_cli::auth", "kind": "symbol" } } diff --git a/.derived/codebase-index/by-spec/004-governance-verbs.json b/.derived/codebase-index/by-spec/004-governance-verbs.json index e93b10e..030422f 100644 --- a/.derived/codebase-index/by-spec/004-governance-verbs.json +++ b/.derived/codebase-index/by-spec/004-governance-verbs.json @@ -23,7 +23,7 @@ "ownership": true, "sourceField": "establishes", "unit": { - "id": "stagecraft_cli::verbs", + "id": "statecraft_cli::verbs", "kind": "symbol" } } diff --git a/.derived/codebase-index/by-spec/005-mcp-server.json b/.derived/codebase-index/by-spec/005-mcp-server.json index b6d77cc..4a071f7 100644 --- a/.derived/codebase-index/by-spec/005-mcp-server.json +++ b/.derived/codebase-index/by-spec/005-mcp-server.json @@ -23,7 +23,7 @@ "ownership": true, "sourceField": "establishes", "unit": { - "id": "stagecraft_cli::mcp", + "id": "statecraft_cli::mcp", "kind": "symbol" } } diff --git a/.derived/codebase-index/by-spec/006-template-upgrade-verb.json b/.derived/codebase-index/by-spec/006-template-upgrade-verb.json index e5f6639..4e4decc 100644 --- a/.derived/codebase-index/by-spec/006-template-upgrade-verb.json +++ b/.derived/codebase-index/by-spec/006-template-upgrade-verb.json @@ -23,7 +23,7 @@ "ownership": true, "sourceField": "establishes", "unit": { - "id": "stagecraft_cli::verbs::template", + "id": "statecraft_cli::verbs::template", "kind": "symbol" } } diff --git a/.derived/spec-registry/by-spec/001-cli-mcp-thesis.json b/.derived/spec-registry/by-spec/001-cli-mcp-thesis.json index 9d0548a..cb27b73 100644 --- a/.derived/spec-registry/by-spec/001-cli-mcp-thesis.json +++ b/.derived/spec-registry/by-spec/001-cli-mcp-thesis.json @@ -13,7 +13,7 @@ "id": "001-cli-mcp-thesis", "implementation": "pending", "sectionHeadings": [ - "001: stagecraft-cli thesis", + "001: statecraft-cli thesis", "1. Purpose", "2. Decided constraints", "3. Territory", @@ -21,8 +21,8 @@ ], "specPath": "specs/001-cli-mcp-thesis/spec.md", "status": "approved", - "summary": "The successor to OPC (the retired Tauri desktop cockpit): a single binary named stagecraft that exposes the platform's governance verbs twice, as CLI subcommands for humans and as an MCP server for agents. The MCP face is the product's genuinely unique surface: any agent (Claude Code first) operates natively under Stagecraft governance, requesting approvals, checking spec-code coupling, and triggering factory stages. This is milestone M4 in the Stagecraft ladder; the spec records the thesis and the decided constraints so the repo is born governed ahead of its build.\n", - "title": "stagecraft-cli: one binary, two faces (CLI verbs + MCP server)" + "summary": "The successor to OPC (the retired Tauri desktop cockpit): a single binary named statecraft that exposes the platform's governance verbs twice, as CLI subcommands for humans and as an MCP server for agents. The MCP face is the product's genuinely unique surface: any agent (Claude Code first) operates natively under Statecraft governance, requesting approvals, checking spec-code coupling, and triggering factory stages. This is milestone M4 in the Statecraft ladder; the spec records the thesis and the decided constraints so the repo is born governed ahead of its build.\n", + "title": "statecraft-cli: one binary, two faces (CLI verbs + MCP server)" }, "shardHash": "a58ad16200ff335e20b8daa248e375284e48b2c62b668b75ce5f9cbb211f4862", "specVersion": "1.1.0" diff --git a/.derived/spec-registry/by-spec/002-crate-scaffold.json b/.derived/spec-registry/by-spec/002-crate-scaffold.json index 4f1a98d..0926e1b 100644 --- a/.derived/spec-registry/by-spec/002-crate-scaffold.json +++ b/.derived/spec-registry/by-spec/002-crate-scaffold.json @@ -33,8 +33,8 @@ ], "specPath": "specs/002-crate-scaffold/spec.md", "status": "approved", - "summary": "The Rust crate for the single binary named stagecraft: clap-based command tree, layered configuration (flags > env > config file), structured output discipline (human tables on TTY, JSON with --output json), and a CI workflow (fmt, clippy -D warnings, test, release build). No network calls yet; spec 003 adds auth and the API client. After this spec, `cargo install --path .` yields a binary whose skeleton every later verb hangs off.\n", - "title": "The stagecraft binary: crate scaffold, config, CI" + "summary": "The Rust crate for the single binary named statecraft: clap-based command tree, layered configuration (flags > env > config file), structured output discipline (human tables on TTY, JSON with --output json), and a CI workflow (fmt, clippy -D warnings, test, release build). No network calls yet; spec 003 adds auth and the API client. After this spec, `cargo install --path .` yields a binary whose skeleton every later verb hangs off.\n", + "title": "The statecraft binary: crate scaffold, config, CI" }, "shardHash": "8576aef9f1f550842270168da67a5fc80a2c886fdbecac54781f3963a854f3d2", "specVersion": "1.1.0" diff --git a/.derived/spec-registry/by-spec/003-auth-api-client.json b/.derived/spec-registry/by-spec/003-auth-api-client.json index abc3feb..3e0fa16 100644 --- a/.derived/spec-registry/by-spec/003-auth-api-client.json +++ b/.derived/spec-registry/by-spec/003-auth-api-client.json @@ -6,11 +6,11 @@ ], "establishes": [ { - "id": "stagecraft_cli::api", + "id": "statecraft_cli::api", "kind": "symbol" }, { - "id": "stagecraft_cli::auth", + "id": "statecraft_cli::auth", "kind": "symbol" } ], @@ -27,7 +27,7 @@ ], "specPath": "specs/003-auth-api-client/spec.md", "status": "approved", - "summary": "The binary learns to authenticate against a Stagecraft control plane and speak its API. Auth v1 is a browser-assisted session-cookie handoff (the control plane's chassis auth is cookie based, and the embedded rauthy exposes OIDC; the exact mechanism is DECIDE-AT-IMPLEMENTATION between OAuth device-flow-style polling and a localhost callback, constrained below). Tokens/cookies are stored in a 0600 credentials file, never in the config file. An api module gives every later verb a typed, authenticated request path with consistent error mapping.\n", + "summary": "The binary learns to authenticate against a Statecraft control plane and speak its API. Auth v1 is a browser-assisted session-cookie handoff (the control plane's chassis auth is cookie based, and the embedded rauthy exposes OIDC; the exact mechanism is DECIDE-AT-IMPLEMENTATION between OAuth device-flow-style polling and a localhost callback, constrained below). Tokens/cookies are stored in a 0600 credentials file, never in the config file. An api module gives every later verb a typed, authenticated request path with consistent error mapping.\n", "title": "Auth + control-plane API client" }, "shardHash": "46b6c05e0ca45e03fc21761df69431c89464e6a11e5e9088c4f76bf97e65cd59", diff --git a/.derived/spec-registry/by-spec/004-governance-verbs.json b/.derived/spec-registry/by-spec/004-governance-verbs.json index b76ad87..0b441c9 100644 --- a/.derived/spec-registry/by-spec/004-governance-verbs.json +++ b/.derived/spec-registry/by-spec/004-governance-verbs.json @@ -6,7 +6,7 @@ ], "establishes": [ { - "id": "stagecraft_cli::verbs", + "id": "statecraft_cli::verbs", "kind": "symbol" } ], @@ -27,7 +27,7 @@ ], "specPath": "specs/004-governance-verbs/spec.md", "status": "approved", - "summary": "The CLI face becomes useful: the stub commands from spec 002 gain real implementations over the API client, mirroring the control plane's tenants (stagecraft spec 004), factory (spec 005), and fleet (spec 006) services. Every verb has a stable JSON output shape, because spec 005 exposes these same verbs as MCP tools and the JSON is the shared contract between both faces.\n", + "summary": "The CLI face becomes useful: the stub commands from spec 002 gain real implementations over the API client, mirroring the control plane's tenants (statecraft spec 004), factory (spec 005), and fleet (spec 006) services. Every verb has a stable JSON output shape, because spec 005 exposes these same verbs as MCP tools and the JSON is the shared contract between both faces.\n", "title": "Governance verbs v1: tenants, stamps, fleet" }, "shardHash": "c8282f2410f0072e4a10578af7d13cff12921806b154d05fe5144ef07cd12148", diff --git a/.derived/spec-registry/by-spec/005-mcp-server.json b/.derived/spec-registry/by-spec/005-mcp-server.json index 0e84808..70d3525 100644 --- a/.derived/spec-registry/by-spec/005-mcp-server.json +++ b/.derived/spec-registry/by-spec/005-mcp-server.json @@ -6,7 +6,7 @@ ], "establishes": [ { - "id": "stagecraft_cli::mcp", + "id": "statecraft_cli::mcp", "kind": "symbol" } ], @@ -22,8 +22,8 @@ ], "specPath": "specs/005-mcp-server/spec.md", "status": "approved", - "summary": "Milestone M4's core: `stagecraft mcp` runs a Model Context Protocol server over stdio exposing the governance verbs as tools, so a coding agent (Claude Code first) operates under Stagecraft governance natively: listing tenants, launching and watching stamps, inspecting and operating fleets, all with the same auth, the same guards, and the same JSON shapes as the CLI face. The MCP face is not a privileged side door: it calls the identical verb layer from spec 004, and destructive guards (explicit posture, confirm-name) pass through to the agent verbatim.\n", - "title": "The MCP face: stagecraft mcp (stdio server)" + "summary": "Milestone M4's core: `statecraft mcp` runs a Model Context Protocol server over stdio exposing the governance verbs as tools, so a coding agent (Claude Code first) operates under Statecraft governance natively: listing tenants, launching and watching stamps, inspecting and operating fleets, all with the same auth, the same guards, and the same JSON shapes as the CLI face. The MCP face is not a privileged side door: it calls the identical verb layer from spec 004, and destructive guards (explicit posture, confirm-name) pass through to the agent verbatim.\n", + "title": "The MCP face: statecraft mcp (stdio server)" }, "shardHash": "3b2d09c65623c532afd2e5329891a969ea1f4efe279772974e0bf832a6220252", "specVersion": "1.1.0" diff --git a/.derived/spec-registry/by-spec/006-template-upgrade-verb.json b/.derived/spec-registry/by-spec/006-template-upgrade-verb.json index d892698..2303923 100644 --- a/.derived/spec-registry/by-spec/006-template-upgrade-verb.json +++ b/.derived/spec-registry/by-spec/006-template-upgrade-verb.json @@ -6,7 +6,7 @@ ], "establishes": [ { - "id": "stagecraft_cli::verbs::template", + "id": "statecraft_cli::verbs::template", "kind": "symbol" } ], @@ -22,8 +22,8 @@ ], "specPath": "specs/006-template-upgrade-verb/spec.md", "status": "approved", - "summary": "The upgrade half of the 2026-07-14 packaging decision: templates stay small because the chassis ships as versioned npm packages (enrahitu spec 018), and upgrading a stamped app is a verb, not a migration project. `stagecraft template upgrade`, run in a stamped app checkout, reads template.toml, bumps the chassis package pins, applies template-shipped codemods, runs the contract verify verb, and commits on a branch. The CLI orchestrates; all structure knowledge stays in the template and its packages. This verb is the boundary that keeps the CLI from ever becoming a build daemon.\n", - "title": "stagecraft template upgrade: chassis upgrades as a governed verb" + "summary": "The upgrade half of the 2026-07-14 packaging decision: templates stay small because the chassis ships as versioned npm packages (enrahitu spec 018), and upgrading a stamped app is a verb, not a migration project. `statecraft template upgrade`, run in a stamped app checkout, reads template.toml, bumps the chassis package pins, applies template-shipped codemods, runs the contract verify verb, and commits on a branch. The CLI orchestrates; all structure knowledge stays in the template and its packages. This verb is the boundary that keeps the CLI from ever becoming a build daemon.\n", + "title": "statecraft template upgrade: chassis upgrades as a governed verb" }, "shardHash": "5c1f7935e130ca8d1a42dfe060bf18f01e8a19f24be733760bc9d37cfc98f646", "specVersion": "1.1.0" diff --git a/.github/workflows/spec-spine.yml b/.github/workflows/spec-spine.yml index e586831..074888f 100644 --- a/.github/workflows/spec-spine.yml +++ b/.github/workflows/spec-spine.yml @@ -13,7 +13,7 @@ jobs: fetch-depth: 0 # full history so the gate can diff the merge base - name: Install spec-spine (prebuilt, pinned) run: | - curl -fsSL https://raw.githubusercontent.com/stagecraft-ing/spec-spine/main/install.sh | \ + curl -fsSL https://raw.githubusercontent.com/statecrafting/spec-spine/main/install.sh | \ SPEC_SPINE_VERSION=v0.10.0 SPEC_SPINE_BIN_DIR="$HOME/.local/bin" sh echo "$HOME/.local/bin" >> "$GITHUB_PATH" - run: spec-spine compile diff --git a/AGENTS.md b/AGENTS.md index 91bcab1..2bc4a97 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,4 +1,4 @@ -# AGENTS.md: stagecraft-cli +# AGENTS.md: statecraft-cli This file is the cross-agent session-init protocol authority, read by Claude Code, Codex CLI, Cursor, and GitHub Copilot via the AAIF/Linux diff --git a/CLAUDE.md b/CLAUDE.md index 111dd2a..de66346 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,10 +1,10 @@ -# CLAUDE.md: stagecraft-cli +# CLAUDE.md: statecraft-cli ## Project Overview -The successor to OPC: one Rust binary named `stagecraft` with two +The successor to OPC: one Rust binary named `statecraft` with two faces, CLI subcommands for humans and an MCP server (stdio) for -agents, both calling the Stagecraft control plane's API under the same +agents, both calling the Statecraft control plane's API under the same identity, guards, and JSON shapes. Thesis and decided constraints: `specs/001-cli-mcp-thesis/spec.md`. The build order is the spec backlog: 002 crate scaffold, 003 auth + API client, 004 governance @@ -37,7 +37,7 @@ spec-spine compile && spec-spine index && spec-spine lint # after spec 002 lands: cargo fmt --check && cargo clippy --all-targets -- -D warnings cargo test -cargo build --release # target/release/stagecraft +cargo build --release # target/release/statecraft ``` ## Key Conventions diff --git a/Cargo.lock b/Cargo.lock index 015cd44..fbba452 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1841,7 +1841,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "stagecraft-cli" +name = "statecraft-cli" version = "0.1.0" dependencies = [ "anyhow", diff --git a/Cargo.toml b/Cargo.toml index fffe237..d8ea037 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,19 @@ [package] -name = "stagecraft-cli" +name = "statecraft-cli" version = "0.1.0" edition = "2021" license = "Apache-2.0" -description = "Stagecraft governance verbs as CLI subcommands for humans and an MCP server for agents." -repository = "https://github.com/stagecraft-ing/stagecraft-cli" +description = "Statecraft governance verbs as CLI subcommands for humans and an MCP server for agents." +repository = "https://github.com/statecrafting/statecraft-cli" readme = "README.md" # spec-spine couples this crate's code paths to its owning spec. [package.metadata.spec-spine] spec = "002-crate-scaffold" -# One binary named `stagecraft` (spec 001: the OPC name retires). +# One binary named `statecraft` (spec 001: the OPC name retires). [[bin]] -name = "stagecraft" +name = "statecraft" path = "src/main.rs" [dependencies] diff --git a/README.md b/README.md index e45b73b..edf44b1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -# stagecraft-cli +# statecraft-cli -One binary, two faces. `stagecraft` exposes the -[Stagecraft](https://github.com/stagecraft-ing/stagecraft) control +One binary, two faces. `statecraft` exposes the +[Statecraft](https://github.com/statecrafting/statecraft) control plane's governance verbs as CLI subcommands for humans and as an MCP server for agents, so a coding agent (Claude Code first) can request approvals, check spec-code coupling, and trigger factory stages natively @@ -12,14 +12,14 @@ desktop app is retired, the governance verbs live on here. ## Status -Born governed, pre-code. This is milestone M4 in the Stagecraft ladder; +Born governed, pre-code. This is milestone M4 in the Statecraft ladder; the thesis and decided constraints (binary name, Rust, stdio MCP, Apache-2.0, no TUI) live in `specs/001-cli-mcp-thesis/spec.md`. The crate lands when M4 starts. ## Governance -Governed by [spec-spine](https://github.com/stagecraft-ing/spec-spine) +Governed by [spec-spine](https://github.com/statecrafting/spec-spine) (`cargo install spec-spine-cli`): `spec-spine compile | index | lint`, coupling gate at PR time, derived shards under `.derived/` are compiler-owned. diff --git a/specs/001-cli-mcp-thesis/spec.md b/specs/001-cli-mcp-thesis/spec.md index c2e9f33..c863b32 100644 --- a/specs/001-cli-mcp-thesis/spec.md +++ b/specs/001-cli-mcp-thesis/spec.md @@ -1,6 +1,6 @@ --- id: "001-cli-mcp-thesis" -title: "stagecraft-cli: one binary, two faces (CLI verbs + MCP server)" +title: "statecraft-cli: one binary, two faces (CLI verbs + MCP server)" status: approved created: "2026-07-14" implementation: pending @@ -10,17 +10,17 @@ establishes: - "README.md" summary: > The successor to OPC (the retired Tauri desktop cockpit): a single - binary named stagecraft that exposes the platform's governance verbs + binary named statecraft that exposes the platform's governance verbs twice, as CLI subcommands for humans and as an MCP server for agents. The MCP face is the product's genuinely unique surface: any agent - (Claude Code first) operates natively under Stagecraft governance, + (Claude Code first) operates natively under Statecraft governance, requesting approvals, checking spec-code coupling, and triggering - factory stages. This is milestone M4 in the Stagecraft ladder; the spec + factory stages. This is milestone M4 in the Statecraft ladder; the spec records the thesis and the decided constraints so the repo is born governed ahead of its build. --- -# 001: stagecraft-cli thesis +# 001: statecraft-cli thesis ## 1. Purpose @@ -30,11 +30,11 @@ ran: on the developer's machine, next to the code and the agents. This repo rebuilds that asset in its natural shape: one binary, two faces. - **CLI face** (humans): governance verbs as subcommands: authenticate - against a Stagecraft control plane, inspect tenants and fleets, request + against a Statecraft control plane, inspect tenants and fleets, request and grant approvals, check coupling status, trigger and watch factory stages. - **MCP face** (agents): the same verbs exposed as MCP tools over stdio, - so a coding agent operates under Stagecraft governance natively instead + so a coding agent operates under Statecraft governance natively instead of shelling out around it. Claude Code is the first target client. Both faces call the same control-plane API with the same identity model; @@ -42,7 +42,7 @@ the MCP face is not a privileged side door. ## 2. Decided constraints -- **Binary name `stagecraft`**; npm distribution as `stagecraft-cli` (the +- **Binary name `statecraft`**; npm distribution as `statecraft-cli` (the bare npm name is taken; brew/cargo naming is uncontested). The OPC name retires with the desktop app. - **Implementation language: Rust** (single static binary, no runtime @@ -56,13 +56,13 @@ the MCP face is not a privileged side door. ## 3. Territory `README.md` now; the crate and its manifests when the build starts (M4 in -the Stagecraft milestone ladder; see stagecraft spec 001 §6). Verb +the Statecraft milestone ladder; see statecraft spec 001 §6). Verb inventory and API surface graduate into their own numbered specs at that point, informed by the OPC governance-verb inventory (knowledge://open-agentic-platform/product/apps/opc). ## 4. Out of scope -- Control-plane behavior (stagecraft repo). +- Control-plane behavior (statecraft repo). - Template mechanics (enrahitu repo). - A desktop or graphical client of any kind. diff --git a/specs/002-crate-scaffold/spec.md b/specs/002-crate-scaffold/spec.md index 9eddb03..6208a34 100644 --- a/specs/002-crate-scaffold/spec.md +++ b/specs/002-crate-scaffold/spec.md @@ -1,6 +1,6 @@ --- id: "002-crate-scaffold" -title: "The stagecraft binary: crate scaffold, config, CI" +title: "The statecraft binary: crate scaffold, config, CI" status: approved created: "2026-07-14" implementation: complete @@ -12,7 +12,7 @@ establishes: - { kind: directory, path: "src/" } - ".github/workflows/ci.yml" summary: > - The Rust crate for the single binary named stagecraft: clap-based + The Rust crate for the single binary named statecraft: clap-based command tree, layered configuration (flags > env > config file), structured output discipline (human tables on TTY, JSON with --output json), and a CI workflow (fmt, clippy -D warnings, test, @@ -25,8 +25,8 @@ summary: > ## 1. Territory -Root `Cargo.toml` (crate name `stagecraft-cli`, binary `[[bin]] name = -"stagecraft"`, `[package.metadata.spec-spine] spec = "002-crate-scaffold"`, +Root `Cargo.toml` (crate name `statecraft-cli`, binary `[[bin]] name = +"statecraft"`, `[package.metadata.spec-spine] spec = "002-crate-scaffold"`, license Apache-2.0, edition 2021), `Cargo.lock` (committed; this is a binary), `src/`, `.github/workflows/ci.yml`. Update `spec-spine.toml` if the indexer needs the workspace declared (single root crate: the @@ -41,12 +41,12 @@ defaults should already cover it; verify with `spec-spine index`). pre-add. - Command tree v1 (stubs that print a clear "not implemented until spec NNN" error and exit 2, so help text is honest from day one): - `stagecraft login|whoami` (003), `tenants list|show` (004), + `statecraft login|whoami` (003), `tenants list|show` (004), `stamp new|status` (004), `fleet list|deploy|update|backup|remove` (004), `mcp` (005), plus working `version` and `completions `. -- Config: `~/.config/stagecraft/config.toml` via the directories - crate: `base_url`, `output` default; env prefix `STAGECRAFT_` - overrides file, flags override env. `stagecraft config show` prints +- Config: `~/.config/statecraft/config.toml` via the directories + crate: `base_url`, `output` default; env prefix `STATECRAFT_` + overrides file, flags override env. `statecraft config show` prints the effective, merged config with sources annotated. - Output discipline: every command renders through one output layer: human-readable on TTY, `--output json` emits stable machine JSON @@ -61,8 +61,8 @@ defaults should already cover it; verify with `spec-spine index`). ## 3. Acceptance -- `cargo build --release` produces `target/release/stagecraft`; - `stagecraft --help` shows the full tree; stub commands exit 2 with +- `cargo build --release` produces `target/release/statecraft`; + `statecraft --help` shows the full tree; stub commands exit 2 with the owning spec named; `config show` and `completions zsh` work. - `cargo test` covers config layering (file/env/flag precedence) and the not-implemented exit code. diff --git a/specs/003-auth-api-client/spec.md b/specs/003-auth-api-client/spec.md index 12500a0..86622c8 100644 --- a/specs/003-auth-api-client/spec.md +++ b/specs/003-auth-api-client/spec.md @@ -7,10 +7,10 @@ implementation: complete depends_on: - "002-crate-scaffold" establishes: - - { kind: symbol, id: "stagecraft_cli::api" } - - { kind: symbol, id: "stagecraft_cli::auth" } + - { kind: symbol, id: "statecraft_cli::api" } + - { kind: symbol, id: "statecraft_cli::auth" } summary: > - The binary learns to authenticate against a Stagecraft control plane + The binary learns to authenticate against a Statecraft control plane and speak its API. Auth v1 is a browser-assisted session-cookie handoff (the control plane's chassis auth is cookie based, and the embedded rauthy exposes OIDC; the exact mechanism is @@ -25,7 +25,7 @@ summary: > ## 1. Cross-repo dependency -A reachable control plane (stagecraft repo, specs 002+004) is needed +A reachable control plane (statecraft repo, specs 002+004) is needed for live verification. Everything unit-testable must be tested against a local mock HTTP server (httpmock or wiremock crate); do not gate `cargo test` on a live platform. If no control plane is reachable for @@ -39,7 +39,7 @@ check as pending. - **Auth mechanism constraints** (implementer picks the simplest that the control plane's auth surface supports, and records the choice here via amendment): - 1. `stagecraft login [--base-url URL]` must work on a headless + 1. `statecraft login [--base-url URL]` must work on a headless machine with a browser elsewhere: print a URL + one-time code, or open a localhost callback when a browser is local. The rauthy OIDC authorization-code flow with a loopback redirect @@ -47,11 +47,11 @@ check as pending. personal access token pasted at a prompt is an acceptable v1 fallback if the loopback flow needs control-plane changes that do not exist yet (report which). - 2. Credentials at `~/.config/stagecraft/credentials.toml`, mode + 2. Credentials at `~/.config/statecraft/credentials.toml`, mode 0600, keyed by base_url (multiple planes allowed). Refresh transparently when the mechanism supports it; on 401, error with - "run stagecraft login". -- `stagecraft whoami`: GET the control plane's auth identity endpoint + "run statecraft login". +- `statecraft whoami`: GET the control plane's auth identity endpoint (`/api/v1/auth/me` in the chassis) and render id/email; exit 1 when unauthenticated. - **api module**: base_url + credentials -> a client with: typed @@ -65,7 +65,7 @@ check as pending. ### Auth mechanism decision (2026-07-14 amendment) Chosen v1 mechanism: **browser-assisted bearer-token handoff (paste)**, -the fallback sanctioned by constraint 1 above. `stagecraft login +the fallback sanctioned by constraint 1 above. `statecraft login [--base-url URL]` guides the operator to sign in through a browser at the control plane, then reads the resulting chassis session token (`access_token`) from stdin (a piped value or an interactive prompt, so @@ -98,7 +98,7 @@ control plane adds the loopback client plus a token-return endpoint, contract or the api module. Refresh: the paste mechanism hands the CLI no refresh token, so v1 has -no transparent refresh; on HTTP 401 the CLI errors with "run stagecraft +no transparent refresh; on HTTP 401 the CLI errors with "run statecraft login". Longer-lived or refreshable CLI tokens are control-plane work to add alongside the loopback flow. (The chassis `access_token` is a 15-minute JWT, a control-plane limitation reported here, not a CLI diff --git a/specs/004-governance-verbs/spec.md b/specs/004-governance-verbs/spec.md index 06e0129..4282ace 100644 --- a/specs/004-governance-verbs/spec.md +++ b/specs/004-governance-verbs/spec.md @@ -7,11 +7,11 @@ implementation: complete depends_on: - "003-auth-api-client" establishes: - - { kind: symbol, id: "stagecraft_cli::verbs" } + - { kind: symbol, id: "statecraft_cli::verbs" } summary: > The CLI face becomes useful: the stub commands from spec 002 gain real implementations over the API client, mirroring the control - plane's tenants (stagecraft spec 004), factory (spec 005), and fleet + plane's tenants (statecraft spec 004), factory (spec 005), and fleet (spec 006) services. Every verb has a stable JSON output shape, because spec 005 exposes these same verbs as MCP tools and the JSON is the shared contract between both faces. @@ -21,7 +21,7 @@ summary: > ## 1. Cross-repo dependency -Endpoint shapes are defined by the stagecraft repo's specs 004/005/006 +Endpoint shapes are defined by the statecraft repo's specs 004/005/006 and inlined below as the CLI's expectation; if the live control plane diverges, the platform spec wins: update the shapes here via amendment rather than coding to drift. Services that do not exist yet on the @@ -30,26 +30,26 @@ target plane must surface as "not enabled on this control plane" ## 2. Verb surface (all support --output json) -- `stagecraft tenants list` -> GET /api/v1/tenants -- `stagecraft tenants show ` -> GET /api/v1/tenants/:id (includes +- `statecraft tenants list` -> GET /api/v1/tenants +- `statecraft tenants show ` -> GET /api/v1/tenants/:id (includes installations) -- `stagecraft tenants install-url ` -> GET +- `statecraft tenants install-url ` -> GET /api/v1/tenants/:id/github/install-url (prints the URL; `--open` opens the browser) -- `stagecraft stamp new --tenant --app --org +- `statecraft stamp new --tenant --app --org [--frontend vue] --posture ` -> POST /api/v1/tenants/:id/stamps. Posture is a REQUIRED flag with no default: the CLI must not invent a posture (the platform rejects defaulted postures by design). -- `stagecraft stamp status [--watch]` -> GET +- `statecraft stamp status [--watch]` -> GET /api/v1/stamps/:jobId; --watch polls (2s interval, backoff to 10s) until green|failed, exit 0|1 accordingly, streaming state changes as they happen. -- `stagecraft fleet list --tenant `; `stagecraft fleet deploy - --tenant --app --image `; `stagecraft fleet - update --image `; `stagecraft fleet backup `; - `stagecraft fleet remove --confirm ` (the confirm-name - guard from stagecraft spec 006 is surfaced verbatim; no +- `statecraft fleet list --tenant `; `statecraft fleet deploy + --tenant --app --image `; `statecraft fleet + update --image `; `statecraft fleet backup `; + `statecraft fleet remove --confirm ` (the confirm-name + guard from statecraft spec 006 is surfaced verbatim; no --force/--yes shortcut exists, deliberately). - Human output: aligned tables with the fields a operator scans for (ids, names, status, age); JSON output: the platform response passed @@ -67,7 +67,7 @@ target plane must surface as "not enabled on this control plane" ## 4. Out of scope - Approvals verbs (arrive when the platform's approvals surface - exists, follow-up to stagecraft spec 008). + exists, follow-up to statecraft spec 008). - MCP exposure (005). - Any local stamping (stamping is the platform's job; the CLI only triggers and watches). @@ -79,7 +79,7 @@ target plane must surface as "not enabled on this control plane" §2 named the verbs but left the concrete HTTP mapping, the request bodies, and the envelope's error arm implicit. This section inlines -them, read off stagecraft specs 004 (tenants), 005 (factory), and 006 +them, read off statecraft specs 004 (tenants), 005 (factory), and 006 (fleet) on 2026-07-15. §1's divergence rule still governs: if a live plane differs, the platform spec wins and this section is re-amended, never coded around. @@ -100,17 +100,17 @@ never coded around. | `fleet remove ` | DELETE `/fleet/:appId` | `{confirm}` | Every mapping above is verbatim from the platform specs except one: -`fleet list` -> GET `/tenants/:id/fleet`. Stagecraft spec 006 defines +`fleet list` -> GET `/tenants/:id/fleet`. Statecraft spec 006 defines the fleet collection's deploy (POST `/tenants/:id/fleet`) and the per-app status/update/backup/remove, but names no list endpoint. The CLI inlines the symmetric collection GET as its expectation, mirroring the factory's `GET /tenants/:id/stamps` list; if the plane lands a different list shape, §1 governs. `stamp new`'s `posture` is a required field the caller must supply: the factory rejects a defaulted posture -by design (stagecraft spec 005 §3, pipeline step 3), so the CLI makes +by design (statecraft spec 005 §3, pipeline step 3), so the CLI makes `--posture` a required flag with no default and forwards it as `posture`. `fleet remove`'s `confirm` carries the literal app name the -operator echoes (stagecraft spec 006 §3, destructive guard); there is +operator echoes (statecraft spec 006 §3, destructive guard); there is no `--force`/`--yes` shortcut. ### 5.2 JSON envelope (`--output json`) @@ -138,7 +138,7 @@ operational (exit 1) with the `run login` hint, matching `whoami`. The passthrough envelope means `--output json` is faithful to whatever the plane returns. Human tables need named fields, so the CLI reads a tolerant subset (unknown fields ignored, absent optional fields blank). -The shapes below were read off the stagecraft service code +The shapes below were read off the statecraft service code (`backend/tenants/api.ts`, `backend/factory/api.ts`, `backend/fleet/api.ts`) and confirmed against a running plane on 2026-07-15; a shape the human renderer cannot read surfaces as a @@ -184,7 +184,7 @@ exhausted. Implemented: the full §2/§5.1 verb surface (tenants list/show/ install-url, stamp new/status with `--watch`, fleet list/deploy/update/ -backup/remove) in the new `stagecraft_cli::verbs` module over the spec +backup/remove) in the new `statecraft_cli::verbs` module over the spec 003 client, which grew GET/POST/DELETE returning a passthrough `serde_json::Value`. Human output is aligned tables; `--output json` emits the §5.2 `{ok, data|error}` envelope. The `--posture` guard is @@ -201,7 +201,7 @@ non-idempotent DELETE is not retried, and `--debug` never leaks the token. Live e2e (2026-07-15, resolves §3): run against a local plane (the -stagecraft services stamped and reachable). `login` (bearer handoff), +statecraft services stamped and reachable). `login` (bearer handoff), then every read verb against real data: `tenants list` (table), `tenants show` (record + installations), `fleet list` (empty), `stamp status` on a real job, `stamp status --watch` on a terminal `failed` diff --git a/specs/005-mcp-server/spec.md b/specs/005-mcp-server/spec.md index 99cd0bc..af50527 100644 --- a/specs/005-mcp-server/spec.md +++ b/specs/005-mcp-server/spec.md @@ -1,17 +1,17 @@ --- id: "005-mcp-server" -title: "The MCP face: stagecraft mcp (stdio server)" +title: "The MCP face: statecraft mcp (stdio server)" status: approved created: "2026-07-14" implementation: complete depends_on: - "004-governance-verbs" establishes: - - { kind: symbol, id: "stagecraft_cli::mcp" } + - { kind: symbol, id: "statecraft_cli::mcp" } summary: > - Milestone M4's core: `stagecraft mcp` runs a Model Context Protocol + Milestone M4's core: `statecraft mcp` runs a Model Context Protocol server over stdio exposing the governance verbs as tools, so a coding - agent (Claude Code first) operates under Stagecraft governance + agent (Claude Code first) operates under Statecraft governance natively: listing tenants, launching and watching stamps, inspecting and operating fleets, all with the same auth, the same guards, and the same JSON shapes as the CLI face. The MCP face is not a @@ -46,11 +46,11 @@ summary: > human; this guard is the product surface, keep it loud. - Auth: the server reuses the credentials file (spec 003); if unauthenticated it starts, but every tool call returns a structured - error instructing `stagecraft login` (an agent must never be able to + error instructing `statecraft login` (an agent must never be able to trigger the login browser flow itself). - Every tool result includes the platform's attestation/record ids when present, so agent transcripts can cite the governed record. -- `stagecraft mcp --print-config` prints the .mcp.json snippet for +- `statecraft mcp --print-config` prints the .mcp.json snippet for easy installation. ### Transport decision (2026-07-15 amendment) @@ -92,11 +92,11 @@ new decision recorded by amendment. ## 4. Status (2026-07-15) -Implemented: `stagecraft mcp` runs the stdio JSON-RPC server (the -transport decision above) in the new `stagecraft_cli::mcp` module, +Implemented: `statecraft mcp` runs the stdio JSON-RPC server (the +transport decision above) in the new `statecraft_cli::mcp` module, exposing all nine §1 tools. Each tool calls the identical spec 004 verb request (the endpoint and body knowledge lives once, in -`stagecraft_cli::verbs`; the MCP face only chooses which to call), and +`statecraft_cli::verbs`; the MCP face only chooses which to call), and the tool result is the spec 004 §5.2 `{ok,data|error}` envelope verbatim, so any attestation/record ids in the plane's payload are carried through. The guards pass through: `stamp_new` rejects a @@ -105,9 +105,9 @@ missing or empty `posture` and `fleet_remove` a missing or empty nor `install-url --open` is exposed, so an agent can never trigger the browser flow. An unauthenticated server still starts and answers every tool call with a structured `{ok:false,error:{kind:"unauthenticated"}}` -result naming `stagecraft login`; a corrupt or unreadable credentials +result naming `statecraft login`; a corrupt or unreadable credentials store degrades to the same unauthenticated start rather than refusing to -boot. `stagecraft mcp --print-config` prints the `.mcp.json` snippet. +boot. `statecraft mcp --print-config` prints the `.mcp.json` snippet. The tool-result `error.kind` set a client can see is the spec 004 §5.2 taxonomy passed through (`network`, `api`, `server`, `decode`) plus two @@ -128,8 +128,8 @@ notification handling, and the print-config snippet. Live check (2026-07-15, resolves §2): run against a local plane. A scripted stdio client (the identical newline-delimited JSON-RPC a -`claude mcp add` client speaks) drove the real `stagecraft mcp` server -against the plane: `initialize` (serverInfo `stagecraft`) -> +`claude mcp add` client speaks) drove the real `statecraft mcp` server +against the plane: `initialize` (serverInfo `statecraft`) -> `notifications/initialized` -> `tools/list` (9 tools) -> `tools/call tenants_list` (returned the real tenant "E2E stamp check" in the `{ok:true,data:{tenants:[…]}}` passthrough envelope) -> `tools/call diff --git a/specs/006-template-upgrade-verb/spec.md b/specs/006-template-upgrade-verb/spec.md index b116b6d..463fa68 100644 --- a/specs/006-template-upgrade-verb/spec.md +++ b/specs/006-template-upgrade-verb/spec.md @@ -1,18 +1,18 @@ --- id: "006-template-upgrade-verb" -title: "stagecraft template upgrade: chassis upgrades as a governed verb" +title: "statecraft template upgrade: chassis upgrades as a governed verb" status: approved created: "2026-07-14" implementation: in-progress depends_on: - "002-crate-scaffold" establishes: - - { kind: symbol, id: "stagecraft_cli::verbs::template" } + - { kind: symbol, id: "statecraft_cli::verbs::template" } summary: > The upgrade half of the 2026-07-14 packaging decision: templates stay small because the chassis ships as versioned npm packages (enrahitu spec 018), and upgrading a stamped app is a verb, not a - migration project. `stagecraft template upgrade`, run in a stamped + migration project. `statecraft template upgrade`, run in a stamped app checkout, reads template.toml, bumps the chassis package pins, applies template-shipped codemods, runs the contract verify verb, and commits on a branch. The CLI orchestrates; all structure @@ -34,7 +34,7 @@ re-import path; do not attempt a tree merge. ## 2. Behavior -`stagecraft template upgrade [--to ] [--dry-run] +`statecraft template upgrade [--to ] [--dry-run] [--no-branch]`, executed in a stamped app repo root: 1. **Preflight**: refuse on a dirty working tree; require @@ -87,8 +87,8 @@ re-import path; do not attempt a tree merge. ## 5. Status (2026-07-15) -Implemented: `stagecraft template upgrade` in the new -`stagecraft_cli::verbs::template` module (the symbol this spec +Implemented: `statecraft template upgrade` in the new +`statecraft_cli::verbs::template` module (the symbol this spec establishes). It is the one *local* governed verb: unlike the spec 004 verbs it never calls the control plane. It reads `template.toml`, bumps the chassis package pins in `package.json`, refreshes the lockfile, diff --git a/src/api.rs b/src/api.rs index b5eddd7..9f224df 100644 --- a/src/api.rs +++ b/src/api.rs @@ -19,7 +19,7 @@ use crate::error::{AppError, AppResult}; use crate::output::{self, OutputFormat}; /// Sent on every request so control-plane logs can attribute CLI traffic. -const USER_AGENT: &str = concat!("stagecraft/", env!("CARGO_PKG_VERSION")); +const USER_AGENT: &str = concat!("statecraft/", env!("CARGO_PKG_VERSION")); /// Total attempts for an idempotent GET: one initial call plus retries. const MAX_ATTEMPTS: u32 = 3; /// Backoff base; the nth retry waits `RETRY_BASE * n` plus jitter. @@ -48,7 +48,7 @@ impl std::fmt::Display for ApiError { match self { ApiError::Network(e) => write!(f, "network error talking to the control plane: {e}"), ApiError::Unauthenticated => { - write!(f, "not authenticated; run `stagecraft login`") + write!(f, "not authenticated; run `statecraft login`") } ApiError::Api { status, message } => { write!(f, "control plane returned {status}: {message}") @@ -289,7 +289,7 @@ pub fn run_whoami(resolved: &ResolvedConfig, format: OutputFormat, debug: bool) let base_url = require_base_url(resolved)?; let token = crate::auth::load_token(&base_url)?.ok_or_else(|| { AppError::Operational(anyhow::anyhow!( - "not authenticated for {base_url}; run `stagecraft login`" + "not authenticated for {base_url}; run `statecraft login`" )) })?; let client = ApiClient::new(base_url.clone(), Some(token), debug)?; @@ -321,7 +321,7 @@ pub(crate) fn require_base_url(resolved: &ResolvedConfig) -> AppResult { .map(normalize_base_url) .ok_or_else(|| { AppError::Usage( - "no control-plane base URL; pass --base-url or set STAGECRAFT_BASE_URL".to_string(), + "no control-plane base URL; pass --base-url or set STATECRAFT_BASE_URL".to_string(), ) }) } @@ -467,7 +467,7 @@ mod tests { fn unauthenticated_message_hints_login() { assert!(ApiError::Unauthenticated .to_string() - .contains("stagecraft login")); + .contains("statecraft login")); } #[test] diff --git a/src/auth.rs b/src/auth.rs index f624605..32e0269 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,11 +1,11 @@ //! Authentication and the on-disk credentials store (spec 003 §2). //! -//! `stagecraft login` performs the browser-assisted bearer-token handoff +//! `statecraft login` performs the browser-assisted bearer-token handoff //! chosen in the spec's 2026-07-14 amendment: the operator signs in through a //! browser at the control plane, pastes the resulting session token (piped or //! at a prompt, so a headless machine with a browser elsewhere works), the CLI //! validates it against `/auth/me`, and stores it. Tokens live in -//! `~/.config/stagecraft/credentials.toml` at mode 0600, keyed by base URL, +//! `~/.config/statecraft/credentials.toml` at mode 0600, keyed by base URL, //! never in the config file. The stored token is replayed as a bearer token by //! the api module; the acquisition path can later become the RFC 8252 loopback //! flow with no change to this store. @@ -37,10 +37,10 @@ pub struct PlaneCredential { pub token: String, } -/// The credentials file path: `~/.config/stagecraft/credentials.toml` on Linux, +/// The credentials file path: `~/.config/statecraft/credentials.toml` on Linux, /// alongside `config.toml` but holding only secrets. `None` without a home dir. pub fn default_credentials_path() -> Option { - directories::ProjectDirs::from("", "", "stagecraft") + directories::ProjectDirs::from("", "", "statecraft") .map(|dirs| dirs.config_dir().join("credentials.toml")) } @@ -228,7 +228,7 @@ mod tests { /// A unique scratch path per test so parallel runs never collide. fn scratch(tag: &str) -> PathBuf { let dir = - std::env::temp_dir().join(format!("stagecraft-cred-{}-{}", std::process::id(), tag)); + std::env::temp_dir().join(format!("statecraft-cred-{}-{}", std::process::id(), tag)); std::fs::create_dir_all(&dir).unwrap(); dir.join("credentials.toml") } @@ -333,7 +333,7 @@ mod tests { #[test] fn missing_file_loads_as_empty() { let path = std::env::temp_dir() - .join(format!("stagecraft-cred-{}-absent", std::process::id())) + .join(format!("statecraft-cred-{}-absent", std::process::id())) .join("credentials.toml"); let loaded = load_from(&path).unwrap(); assert!(loaded.planes.is_empty()); diff --git a/src/cli.rs b/src/cli.rs index 3219655..759b2db 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,4 +1,4 @@ -//! The `stagecraft` command tree (spec 002 §2). +//! The `statecraft` command tree (spec 002 §2). //! //! Stub verbs are present from day one so `--help` is honest: each carries //! about-text naming the spec that implements it, and its handler exits 2. @@ -9,12 +9,12 @@ use clap::{Parser, Subcommand, ValueEnum}; use crate::output::OutputFormat; -/// Stagecraft: governance verbs for humans (CLI) and agents (MCP). +/// Statecraft: governance verbs for humans (CLI) and agents (MCP). #[derive(Debug, Parser)] #[command( - name = "stagecraft", + name = "statecraft", version, - about = "Stagecraft governance verbs: CLI subcommands for humans, MCP server for agents.", + about = "Statecraft governance verbs: CLI subcommands for humans, MCP server for agents.", subcommand_required = true, arg_required_else_help = true )] @@ -23,7 +23,7 @@ pub struct Cli { #[arg(long, global = true, value_enum, value_name = "FORMAT")] pub output: Option, - /// Stagecraft control-plane base URL (overrides config / env). + /// Statecraft control-plane base URL (overrides config / env). #[arg(long, global = true, value_name = "URL")] pub base_url: Option, @@ -42,7 +42,7 @@ pub struct Cli { /// Top-level verbs. Stubs name their owning spec in about-text. #[derive(Debug, Subcommand)] pub enum Command { - /// Authenticate against a Stagecraft control plane (spec 003). + /// Authenticate against a Statecraft control plane (spec 003). Login, /// Show the currently authenticated identity (spec 003). Whoami, diff --git a/src/commands.rs b/src/commands.rs index 2f27db9..b95c2b1 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -171,10 +171,10 @@ struct VersionInfo { fn version(format: OutputFormat) { let info = VersionInfo { - name: "stagecraft", + name: "statecraft", version: env!("CARGO_PKG_VERSION"), }; - output::emit(format, &info, || format!("stagecraft {}", info.version)); + output::emit(format, &info, || format!("statecraft {}", info.version)); } /// Config-file location and existence, for `config show`. diff --git a/src/config.rs b/src/config.rs index 5cf1995..6aa48c4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,7 +13,7 @@ use crate::error::{AppError, AppResult}; use crate::output::OutputFormat; /// Environment-variable prefix for config overrides. -pub const ENV_PREFIX: &str = "STAGECRAFT_"; +pub const ENV_PREFIX: &str = "STATECRAFT_"; /// Where a resolved value came from, highest precedence last. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] @@ -52,7 +52,7 @@ pub struct FileConfig { pub output: Option, } -/// Config drawn from the environment (`STAGECRAFT_*`). Every field optional. +/// Config drawn from the environment (`STATECRAFT_*`). Every field optional. #[derive(Clone, Debug, Default)] pub struct EnvConfig { pub base_url: Option, @@ -149,10 +149,10 @@ fn pick_opt( } } -/// The default config file path (`~/.config/stagecraft/config.toml` on Linux), +/// The default config file path (`~/.config/statecraft/config.toml` on Linux), /// derived via the `directories` crate. `None` if no home directory is known. pub fn default_config_path() -> Option { - directories::ProjectDirs::from("", "", "stagecraft") + directories::ProjectDirs::from("", "", "statecraft") .map(|dirs| dirs.config_dir().join("config.toml")) } @@ -167,9 +167,9 @@ pub fn load_file(path: &Path) -> Result { } } -/// Read config overrides from the process environment (`STAGECRAFT_*`). +/// Read config overrides from the process environment (`STATECRAFT_*`). /// -/// Empty values are treated as unset. An unrecognized `STAGECRAFT_OUTPUT` +/// Empty values are treated as unset. An unrecognized `STATECRAFT_OUTPUT` /// is misuse (exit 2), surfaced as [`AppError::Usage`]. pub fn load_env() -> AppResult { let base_url = non_empty_var(&format!("{ENV_PREFIX}BASE_URL")); diff --git a/src/error.rs b/src/error.rs index 62ad04a..3db3de7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,7 +21,7 @@ pub enum AppError { /// it; it is retained because the spec mandates the taxonomy and a later /// spec scaffolds its command as a stub before implementing it. #[allow(dead_code)] - #[error("`stagecraft {command}` is not implemented until spec {spec}")] + #[error("`statecraft {command}` is not implemented until spec {spec}")] NotImplemented { command: String, spec: &'static str }, /// Invalid usage we detect ourselves (as opposed to clap's own parse errors). diff --git a/src/main.rs b/src/main.rs index 0c2fbaf..57a594b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -//! `stagecraft`: one binary, two faces (spec 001). This crate is the scaffold +//! `statecraft`: one binary, two faces (spec 001). This crate is the scaffold //! (spec 002); auth (003), governance verbs (004), and the MCP server (005) //! hang off the command tree established here. diff --git a/src/mcp.rs b/src/mcp.rs index f63302c..87109b0 100644 --- a/src/mcp.rs +++ b/src/mcp.rs @@ -1,5 +1,5 @@ -//! The MCP face (spec 005): `stagecraft mcp` runs a Model Context Protocol -//! server over stdio so a coding agent operates under Stagecraft governance +//! The MCP face (spec 005): `statecraft mcp` runs a Model Context Protocol +//! server over stdio so a coding agent operates under Statecraft governance //! natively. //! //! Transport is hand-rolled JSON-RPC 2.0 over newline-delimited stdio (spec 005 @@ -15,7 +15,7 @@ //! `stamp_new` requires an explicit `posture`, `fleet_remove` a `confirm_name`. //! Neither `login` nor `install-url --open` is exposed, so an agent can never //! trigger a browser flow; an unauthenticated server answers every tool call -//! with a structured error naming `stagecraft login`. +//! with a structured error naming `statecraft login`. use std::io::{self, BufRead, Write}; @@ -28,7 +28,7 @@ use crate::error::{AppError, AppResult}; use crate::verbs::{envelope_value, error_envelope, fleet, stamp, tenants}; /// Server identity reported in `initialize` and used as the `.mcp.json` key. -const SERVER_NAME: &str = "stagecraft"; +const SERVER_NAME: &str = "statecraft"; /// The MCP protocol version we answer with when a client offers none. const PROTOCOL_VERSION: &str = "2025-06-18"; @@ -53,13 +53,13 @@ struct ServerContext { runtime: tokio::runtime::Runtime, } -/// `stagecraft mcp`: run the stdio server until stdin closes (or a `shutdown`). +/// `statecraft mcp`: run the stdio server until stdin closes (or a `shutdown`). /// /// The credential is loaded once, at startup, from the spec 003 store; a client /// that logs in afterward reconnects (Claude Code restarts the server on config /// change), which keeps the request loop free of filesystem access and testable. /// A missing or unreadable store is not fatal: the server starts unauthenticated -/// and instructs `stagecraft login` per call (spec 005 §1 "if unauthenticated it +/// and instructs `statecraft login` per call (spec 005 §1 "if unauthenticated it /// starts"). pub fn run(resolved: &ResolvedConfig, debug: bool) -> AppResult<()> { let base_url = resolved.base_url.value.as_deref().map(normalize_base_url); @@ -93,10 +93,10 @@ pub fn run(resolved: &ResolvedConfig, debug: bool) -> AppResult<()> { serve(stdin.lock(), stdout.lock(), &ctx) } -/// `stagecraft mcp --print-config`: print the `.mcp.json` snippet that installs +/// `statecraft mcp --print-config`: print the `.mcp.json` snippet that installs /// this binary as a Claude Code MCP server. The command path is this executable /// so the snippet works before the binary is on `PATH`; when a base URL is -/// resolved it is pinned as `STAGECRAFT_BASE_URL` so the snippet is turnkey. +/// resolved it is pinned as `STATECRAFT_BASE_URL` so the snippet is turnkey. pub fn print_config(resolved: &ResolvedConfig) -> AppResult<()> { let command = std::env::current_exe() .ok() @@ -115,7 +115,7 @@ pub fn print_config(resolved: &ResolvedConfig) -> AppResult<()> { fn config_snippet(base_url: Option<&str>, command: &str) -> Value { let mut server = json!({ "command": command, "args": ["mcp"] }); if let Some(base) = base_url { - server["env"] = json!({ "STAGECRAFT_BASE_URL": base }); + server["env"] = json!({ "STATECRAFT_BASE_URL": base }); } json!({ "mcpServers": { SERVER_NAME: server } }) } @@ -530,7 +530,7 @@ where /// Build an authenticated client, or the structured error envelope that the tool /// result carries when the server cannot make a call: `config` when no base URL -/// was set, `unauthenticated` (naming `stagecraft login`) when no credential is +/// was set, `unauthenticated` (naming `statecraft login`) when no credential is /// stored. An agent can never resolve the latter itself: `login` is not a tool. fn client_or_envelope(ctx: &ServerContext) -> Result { let base_url = match &ctx.base_url { @@ -538,7 +538,7 @@ fn client_or_envelope(ctx: &ServerContext) -> Result { None => { return Err(error_envelope( "config", - "no control-plane base URL; launch `stagecraft mcp` with STAGECRAFT_BASE_URL set (or --base-url)".to_string(), + "no control-plane base URL; launch `statecraft mcp` with STATECRAFT_BASE_URL set (or --base-url)".to_string(), None, )) } @@ -665,7 +665,7 @@ mod tests { ); assert_eq!(out.len(), 1); assert_eq!(out[0]["id"], 1); - assert_eq!(out[0]["result"]["serverInfo"]["name"], "stagecraft"); + assert_eq!(out[0]["result"]["serverInfo"]["name"], "statecraft"); assert_eq!(out[0]["result"]["protocolVersion"], "2025-03-26"); assert!(out[0]["result"]["capabilities"]["tools"].is_object()); } @@ -788,7 +788,7 @@ mod tests { assert!(envelope["error"]["message"] .as_str() .unwrap() - .contains("stagecraft login")); + .contains("statecraft login")); } Dispatch::Invalid(_) => { panic!("an unauthenticated call is a tool result, not invalid params") @@ -1058,14 +1058,14 @@ mod tests { #[test] fn print_config_snippet_carries_mcp_args_and_optional_env() { - let with = config_snippet(Some("http://localhost:4000"), "/usr/local/bin/stagecraft"); - let entry = &with["mcpServers"]["stagecraft"]; - assert_eq!(entry["command"], "/usr/local/bin/stagecraft"); + let with = config_snippet(Some("http://localhost:4000"), "/usr/local/bin/statecraft"); + let entry = &with["mcpServers"]["statecraft"]; + assert_eq!(entry["command"], "/usr/local/bin/statecraft"); assert_eq!(entry["args"][0], "mcp"); - assert_eq!(entry["env"]["STAGECRAFT_BASE_URL"], "http://localhost:4000"); + assert_eq!(entry["env"]["STATECRAFT_BASE_URL"], "http://localhost:4000"); // No base URL resolved: no env block to hardcode. - let without = config_snippet(None, "stagecraft"); - assert!(without["mcpServers"]["stagecraft"].get("env").is_none()); + let without = config_snippet(None, "statecraft"); + assert!(without["mcpServers"]["statecraft"].get("env").is_none()); } } diff --git a/src/output.rs b/src/output.rs index 8e9f984..0890960 100644 --- a/src/output.rs +++ b/src/output.rs @@ -7,7 +7,7 @@ use clap::ValueEnum; use serde::{Deserialize, Serialize}; -/// The rendering format, selected by `--output` / `STAGECRAFT_OUTPUT` / config. +/// The rendering format, selected by `--output` / `STATECRAFT_OUTPUT` / config. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, ValueEnum, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum OutputFormat { diff --git a/src/verbs/fleet.rs b/src/verbs/fleet.rs index 570b163..521873e 100644 --- a/src/verbs/fleet.rs +++ b/src/verbs/fleet.rs @@ -1,5 +1,5 @@ //! The `fleet` verb family (spec 004 §5.1): list, deploy, update, backup, -//! remove. `remove` carries the confirm-name guard verbatim (stagecraft spec +//! remove. `remove` carries the confirm-name guard verbatim (statecraft spec //! 006 §3); there is deliberately no `--force`/`--yes` shortcut. use std::fmt::Write; @@ -51,7 +51,7 @@ pub(crate) async fn backup_request(client: &ApiClient, app_id: &str) -> Result AppResult { - // GET /tenants/:id/fleet is `{apps:[…]}` (stagecraft `ListFleetResponse`), + // GET /tenants/:id/fleet is `{apps:[…]}` (statecraft `ListFleetResponse`), // not a bare array; unwrap the collection key (spec 004 §5.3). let apps = array_field(v, "apps")?; if apps.is_empty() { @@ -176,7 +176,7 @@ fn render_app(v: &Value) -> AppResult { } /// A backup receipt (`fleet backup` response): the restic `repository`, the -/// snapshot `tag`, and the Kubernetes `jobName` (stagecraft `BackupResponse`, +/// snapshot `tag`, and the Kubernetes `jobName` (statecraft `BackupResponse`, /// spec 004 §5.3). This is a completed-backup receipt, not an op record: it /// carries no `id`/`status`. `repository` is required, so a shape missing it is /// a decode error (the drift signal), not a silent blank. diff --git a/src/verbs/mod.rs b/src/verbs/mod.rs index a68f47c..816aa10 100644 --- a/src/verbs/mod.rs +++ b/src/verbs/mod.rs @@ -55,7 +55,7 @@ fn client_for(resolved: &ResolvedConfig, debug: bool) -> AppResult { let base_url = api::require_base_url(resolved)?; let token = crate::auth::load_token(&base_url)?.ok_or_else(|| { AppError::Operational(anyhow::anyhow!( - "not authenticated for {base_url}; run `stagecraft login`" + "not authenticated for {base_url}; run `statecraft login`" )) })?; Ok(ApiClient::new(base_url, Some(token), debug)?) @@ -132,7 +132,7 @@ fn to_pretty(value: &T) -> String { /// Interpret a passthrough value as a wrapped list: an object whose `key` holds /// a JSON array. Every platform collection is wrapped (`{tenants:[…]}` from -/// stagecraft `ListTenantsResponse`, `{apps:[…]}` from `ListFleetResponse`; spec +/// statecraft `ListTenantsResponse`, `{apps:[…]}` from `ListFleetResponse`; spec /// 004 §5.3), never a bare array. A decode error names what came back instead. /// Human path only; JSON passthrough never calls this. fn array_field<'a>(v: &'a Value, key: &str) -> AppResult<&'a Vec> { diff --git a/src/verbs/template.rs b/src/verbs/template.rs index 0d1303e..efb1dec 100644 --- a/src/verbs/template.rs +++ b/src/verbs/template.rs @@ -1307,7 +1307,7 @@ mod tests { static COUNTER: AtomicU32 = AtomicU32::new(0); let n = COUNTER.fetch_add(1, Ordering::Relaxed); let dir = - std::env::temp_dir().join(format!("stagecraft-tmpl-{}-{}", std::process::id(), n)); + std::env::temp_dir().join(format!("statecraft-tmpl-{}-{}", std::process::id(), n)); std::fs::create_dir_all(&dir).unwrap(); dir } diff --git a/src/verbs/tenants.rs b/src/verbs/tenants.rs index f52debc..b2bf0d2 100644 --- a/src/verbs/tenants.rs +++ b/src/verbs/tenants.rs @@ -70,7 +70,7 @@ pub fn install_url( } fn render_list(v: &Value) -> AppResult { - // GET /tenants is `{tenants:[…]}` (stagecraft `ListTenantsResponse`), not a + // GET /tenants is `{tenants:[…]}` (statecraft `ListTenantsResponse`), not a // bare array; unwrap the collection key (spec 004 §5.3). let tenants = array_field(v, "tenants")?; if tenants.is_empty() { @@ -84,7 +84,7 @@ fn render_list(v: &Value) -> AppResult { } fn render_detail(v: &Value) -> AppResult { - // GET /tenants/:id is `{tenant:{…}, installations:[…]}` (stagecraft + // GET /tenants/:id is `{tenant:{…}, installations:[…]}` (statecraft // `TenantDetailResponse`): the record lives under `tenant`, and // `installations` is a sibling array, not nested in it (spec 004 §5.3). let tenant = v.get("tenant").ok_or_else(|| { @@ -174,7 +174,7 @@ mod tests { #[test] fn detail_shows_installations() { // `{tenant:{…}, installations:[…]}`: record under `tenant`, installations - // a sibling (stagecraft `TenantDetailResponse`, spec 004 §5.3). + // a sibling (statecraft `TenantDetailResponse`, spec 004 §5.3). let value = json!({ "tenant": { "id": "t_1", diff --git a/tests/cli.rs b/tests/cli.rs index 292980e..178c156 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -1,30 +1,30 @@ -//! End-to-end checks that drive the built `stagecraft` binary (spec 002 §3). +//! End-to-end checks that drive the built `statecraft` binary (spec 002 §3). //! -//! Cargo sets `CARGO_BIN_EXE_stagecraft`, so no extra test crates are needed. +//! Cargo sets `CARGO_BIN_EXE_statecraft`, so no extra test crates are needed. use std::io::Write; use std::process::{Command, Output, Stdio}; fn run(args: &[&str]) -> Output { - Command::new(env!("CARGO_BIN_EXE_stagecraft")) + Command::new(env!("CARGO_BIN_EXE_statecraft")) .args(args) // Neutralize ambient config so tests are deterministic. - .env_remove("STAGECRAFT_BASE_URL") - .env_remove("STAGECRAFT_OUTPUT") + .env_remove("STATECRAFT_BASE_URL") + .env_remove("STATECRAFT_OUTPUT") .output() - .expect("failed to run stagecraft binary") + .expect("failed to run statecraft binary") } /// Run the binary with its working directory set to `dir`: the `template` /// verb operates on the stamped app in the current directory (spec 006). fn run_in(dir: &std::path::Path, args: &[&str]) -> Output { - Command::new(env!("CARGO_BIN_EXE_stagecraft")) + Command::new(env!("CARGO_BIN_EXE_statecraft")) .args(args) .current_dir(dir) - .env_remove("STAGECRAFT_BASE_URL") - .env_remove("STAGECRAFT_OUTPUT") + .env_remove("STATECRAFT_BASE_URL") + .env_remove("STATECRAFT_OUTPUT") .output() - .expect("failed to run stagecraft binary") + .expect("failed to run statecraft binary") } /// Create a unique temp dir seeded with a `template.toml` and `package.json`, @@ -34,7 +34,7 @@ fn stamped_fixture(template_toml: &str, package_json: &str) -> std::path::PathBu static COUNTER: AtomicU32 = AtomicU32::new(0); let n = COUNTER.fetch_add(1, Ordering::Relaxed); let dir = - std::env::temp_dir().join(format!("stagecraft-cli-tmpl-{}-{}", std::process::id(), n)); + std::env::temp_dir().join(format!("statecraft-cli-tmpl-{}-{}", std::process::id(), n)); std::fs::create_dir_all(&dir).expect("create fixture dir"); std::fs::write(dir.join("template.toml"), template_toml).expect("write template.toml"); std::fs::write(dir.join("package.json"), package_json).expect("write package.json"); @@ -44,15 +44,15 @@ fn stamped_fixture(template_toml: &str, package_json: &str) -> std::path::PathBu /// Drive the binary with `input` piped to stdin, then close it. The `mcp` stdio /// server reads newline-delimited requests and shuts down on the resulting EOF. fn run_with_stdin(args: &[&str], input: &str) -> Output { - let mut child = Command::new(env!("CARGO_BIN_EXE_stagecraft")) + let mut child = Command::new(env!("CARGO_BIN_EXE_statecraft")) .args(args) - .env_remove("STAGECRAFT_BASE_URL") - .env_remove("STAGECRAFT_OUTPUT") + .env_remove("STATECRAFT_BASE_URL") + .env_remove("STATECRAFT_OUTPUT") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() - .expect("failed to spawn stagecraft binary"); + .expect("failed to spawn statecraft binary"); child .stdin .take() @@ -63,7 +63,7 @@ fn run_with_stdin(args: &[&str], input: &str) -> Output { // shutdown, so the wait below returns rather than blocking. child .wait_with_output() - .expect("failed to wait on stagecraft binary") + .expect("failed to wait on statecraft binary") } #[test] @@ -74,9 +74,9 @@ fn mcp_print_config_emits_an_installable_snippet() { assert_eq!(out.status.code(), Some(0), "print-config should exit 0"); let value: serde_json::Value = serde_json::from_slice(&out.stdout).expect("print-config emits valid JSON"); - assert_eq!(value["mcpServers"]["stagecraft"]["args"][0], "mcp"); + assert_eq!(value["mcpServers"]["statecraft"]["args"][0], "mcp"); assert!( - value["mcpServers"]["stagecraft"]["command"].is_string(), + value["mcpServers"]["statecraft"]["command"].is_string(), "the snippet names a launch command, got: {value}" ); } @@ -92,7 +92,7 @@ fn mcp_server_answers_initialize_over_stdio() { let value: serde_json::Value = serde_json::from_str(line.trim()).expect("one JSON-RPC response line on stdout"); assert_eq!(value["id"], 1); - assert_eq!(value["result"]["serverInfo"]["name"], "stagecraft"); + assert_eq!(value["result"]["serverInfo"]["name"], "statecraft"); } #[test] @@ -132,7 +132,7 @@ fn governance_verb_without_a_credential_exits_1_with_login_hint() { assert_eq!(out.status.code(), Some(1), "no credential is exit 1"); let stderr = String::from_utf8_lossy(&out.stderr); assert!( - stderr.contains("stagecraft login"), + stderr.contains("statecraft login"), "stderr should hint at login, got: {stderr}" ); assert!(out.stdout.is_empty(), "errors must not print to stdout"); @@ -184,7 +184,7 @@ fn whoami_without_a_stored_credential_exits_1_with_login_hint() { assert_eq!(out.status.code(), Some(1), "unauthenticated is exit 1"); let stderr = String::from_utf8_lossy(&out.stderr); assert!( - stderr.contains("stagecraft login"), + stderr.contains("statecraft login"), "stderr should hint at login, got: {stderr}" ); assert!(out.stdout.is_empty(), "errors must not print to stdout"); @@ -216,7 +216,7 @@ fn version_json_shape_is_stable() { let out = run(&["--output", "json", "version"]); assert_eq!(out.status.code(), Some(0)); let value: serde_json::Value = serde_json::from_slice(&out.stdout).expect("valid JSON"); - assert_eq!(value["name"], "stagecraft"); + assert_eq!(value["name"], "statecraft"); assert!(value["version"].is_string()); } @@ -248,12 +248,12 @@ fn config_show_reports_flag_as_the_base_url_source() { #[test] fn invalid_env_output_value_is_a_usage_error() { - let out = Command::new(env!("CARGO_BIN_EXE_stagecraft")) + let out = Command::new(env!("CARGO_BIN_EXE_statecraft")) .args(["version"]) - .env_remove("STAGECRAFT_BASE_URL") - .env("STAGECRAFT_OUTPUT", "yaml") + .env_remove("STATECRAFT_BASE_URL") + .env("STATECRAFT_OUTPUT", "yaml") .output() - .expect("failed to run stagecraft binary"); + .expect("failed to run statecraft binary"); assert_eq!(out.status.code(), Some(2)); assert!( out.stdout.is_empty(), @@ -263,12 +263,12 @@ fn invalid_env_output_value_is_a_usage_error() { #[test] fn config_show_reports_env_as_the_base_url_source() { - let out = Command::new(env!("CARGO_BIN_EXE_stagecraft")) + let out = Command::new(env!("CARGO_BIN_EXE_statecraft")) .args(["--output", "json", "config", "show"]) - .env_remove("STAGECRAFT_OUTPUT") - .env("STAGECRAFT_BASE_URL", "https://env.example") + .env_remove("STATECRAFT_OUTPUT") + .env("STATECRAFT_BASE_URL", "https://env.example") .output() - .expect("failed to run stagecraft binary"); + .expect("failed to run statecraft binary"); assert_eq!(out.status.code(), Some(0)); let value: serde_json::Value = serde_json::from_slice(&out.stdout).expect("valid JSON"); assert_eq!(value["base_url"]["value"], "https://env.example"); @@ -284,7 +284,7 @@ const TOOLCHAIN_TOML: &str = fn template_upgrade_not_stamped_is_a_refusal() { // An empty directory: no template.toml, so it is not a stamped app. The // JSON error envelope lands on stdout (spec 004 §5.2), exit 1. - let dir = std::env::temp_dir().join(format!("stagecraft-cli-empty-{}", std::process::id())); + let dir = std::env::temp_dir().join(format!("statecraft-cli-empty-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); let out = run_in( &dir,