一键导入
update-backend
Guide for making changes to the Rust backend of the server template, covering the engine crate, the CLI + HTTP API transports, and testing patterns.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Guide for making changes to the Rust backend of the server template, covering the engine crate, the CLI + HTTP API transports, and testing patterns.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Produce a Secret Hitler game preview as an Artifact by running the native game-report generator (never a hand-built mock). Trigger whenever the user asks to preview/show/screenshot/visualize a game, run, match, replay, transcript, report, or the UI (e.g. "show me a preview", "preview of the run", "make an artifact of my last game", "artifact of that match") — default to the fixed sample fixture, or use recent/stored/live results when asked. A "run" or "match" holds many games: list its games and pick the decisive one (or ask) rather than treating it as a single game.
The Secret Hitler visual brand (colors, fonts, design language) shared by every frontend in this repo — the Vite eval console and the Next.js/Fumadocs docs. Use whenever building or restyling any UI, adding a new frontend, choosing colors/typography, or generating screenshots/marketing so the look stays consistent with the board game.
Instructions for running code quality checks and maintaining standards in the Secret-Hitler-Evals project.
Instructions for using prek as a replacement for pre-commit.
| name | update-backend |
| description | Guide for making changes to the Rust backend of the server template, covering the engine crate, the CLI + HTTP API transports, and testing patterns. |
Use this skill whenever you are modifying Rust backend logic — adding commands, probes, traits, or configuration in crates/engine or crates/cli.
The backend is split into two layers:
| Layer | Path | Role |
|---|---|---|
| engine | crates/engine/ | All real backend logic. No transport dependency — runs in the CLI, the HTTP API, and tests. |
| shbench | crates/cli/ | The shbench binary. Drives engine over the CLI (call/probe/doctor/run-scenario) and the axum HTTP API (serve), gated behind the cli / http-api cargo features. |
crates/engine.FilesystemOps, NetworkOps. Inject real platform or headless stubs via AppContext.CommandResult with run_id, status, error, timing_ms, and env_summary.SKIP or UNSUPPORTED error codes.snake_case for functions/modules/variablesPascalCase for structs/enumscargo fmt before committing; pass cargo clippy with no warningsCommands implement the typed, async Command trait (one input struct drives the
CLI args, the HTTP body schema, and the future MCP tool schema) and
self-register at link time via register_command! — there is no
hand-maintained registration list.
The fastest path is the scaffolder: shbench new <name> (or make new name=<name>) generates the file below from templates/command.rs.tpl and
inserts the mod <name>; line for you. To do it by hand:
crates/engine/src/commands/my_command.rs:use crate::commands::{Command, CommandError};
use crate::context::Ctx;
use crate::register_command;
use async_trait::async_trait;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Default)]
pub struct MyCommand;
#[derive(Debug, Deserialize, JsonSchema)]
pub struct MyCommandInput {
pub key: String,
}
#[derive(Debug, Serialize, JsonSchema)]
pub struct MyCommandOutput {
pub result: String,
}
#[async_trait]
impl Command for MyCommand {
type Input = MyCommandInput;
type Output = MyCommandOutput;
fn name(&self) -> &'static str { "my_command" }
fn description(&self) -> &'static str { "One-line description." }
// Optional: restrict transports, e.g. `Expose::cli_only()` / `Expose::no_mcp()`.
async fn run(&self, input: MyCommandInput, cx: &Ctx<'_>)
-> Result<MyCommandOutput, CommandError>
{
// `cx.fs()`, `cx.network()` reach the capabilities;
// `cx.request_id` / `cx.deadline` are request-scoped.
Ok(MyCommandOutput { result: input.key })
}
}
register_command!(MyCommand);
Declare the module in crates/engine/src/commands/mod.rs (mod my_command;).
The register_command! line does the rest — CommandRegistry::new() collects
it automatically. (shbench new inserts this line for you.)
No transport change is needed: the HTTP POST /api/v1/commands/:name route is
auto-derived from the registry, and the CLI call path dispatches by name.
Smoke-test headlessly with shbench:
cargo build -p shbench
shbench call my_command --args '{"key": "value"}' --json
INVALID_INPUT automatically. Return typed
CommandError variants (Unsupported, NetworkError, Timeout, …) for
everything else; CapError from a capability converts via ?.Output; the CLI/scenario runner wrap it in the
CommandResult envelope. Introspect schemas via registry.schema(name).Implement the relevant trait from crates/engine/src/traits.rs:
use engine::traits::{NetworkOps, CapResult, CapError};
struct OfflineNetwork;
#[async_trait::async_trait]
impl NetworkOps for OfflineNetwork {
async fn dns_resolve(&self, _host: &str) -> CapResult<Vec<String>> {
Err(CapError::Unsupported("offline".into()))
}
async fn https_get(&self, _url: &str, _timeout_ms: u64) -> CapResult<(u16, String)> {
Err(CapError::Unsupported("offline".into()))
}
}
Inject via AppContext — AppContext::default() wires the real platform capabilities (used by shbench); pass stub implementations to AppContext::new(fs, network) to run a command against fakes in tests.
Config lives in its own crate, crates/config (crate name app-config).
Source of truth: crates/config/global_config.yaml (layered with
production_config.yaml / .global_config.yaml and APP__-prefixed env
overrides; APP_CONFIG_PATH points a deployed binary at its config file).
let config = app_config::get_config();
println!("Model: {}", config.default_llm.default_model);
crates/engine stays config-agnostic — do not import app-config there unless a
command genuinely needs config; prefer passing values in via the input struct.
The shbench CLI drives engine without a running HTTP server:
# Build
cargo build -p shbench
# Diagnostics
shbench doctor --json
# Call a command
shbench call ping --json
shbench call read_file --args '{"path": "/etc/hostname"}' --json
# Run a capability probe
shbench probe filesystem --json
shbench probe network --json
# Run a YAML scenario
shbench run-scenario scenario.yaml --json
Write scenario files for regression tests:
name: my feature smoke test
steps:
- call: "my_command"
args:
key: "value"
expect_status: "pass"
Every command result has this stable JSON schema:
{
"run_id": "uuid",
"command": "call|probe|doctor|run-scenario",
"target": "<cmd or probe name>",
"status": "pass|fail|skip|error",
"error": { "code": "ERROR_CODE", "message": "..." },
"timing_ms": { "total": 1234 },
"env_summary": { "os": "linux|macos", "arch": "x86_64|aarch64", "headless": true },
"data": {}
}
Error codes: INVALID_INPUT, UNSUPPORTED, UNIMPLEMENTED, DEPENDENCY_MISSING,
PERMISSION_DENIED, NETWORK_ERROR, IO_ERROR, TIMEOUT, EXTERNAL_INTERFERENCE, INTERNAL_ERROR.
cargo fmt appliedcargo clippy passes (no warnings)cargo test passesshbench call <cmd> --jsonengine crate has no transport imports (no CLI/axum/HTTP types)