diff --git a/CHANGELOG.md b/CHANGELOG.md index 36964133..80d29c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve ## [Unreleased] +### Added + +- Added a runnable `wesley-core` Cargo example that deterministically compiles + canonical Shape IR and normalized operations into Brainfuck, executes the + resulting program, and verifies generator, source, and output provenance + through the public extension-generation contract. + ## [0.3.0-alpha.1] - 2026-07-15 ### Added diff --git a/crates/wesley-core/examples/ir_to_brainfuck.rs b/crates/wesley-core/examples/ir_to_brainfuck.rs new file mode 100644 index 00000000..412b3fef --- /dev/null +++ b/crates/wesley-core/examples/ir_to_brainfuck.rs @@ -0,0 +1,478 @@ +//! A deliberately impractical external generator built on Wesley's public API. +//! +//! The example lowers GraphQL into canonical Wesley Shape IR, derives a stable +//! summary, compiles that summary into Brainfuck, executes the result, and +//! verifies exact generator/source/output provenance. + +use std::env; +use std::error::Error; +use std::ffi::OsString; +use std::fmt::{Display, Formatter}; +use std::io::{Error as IoError, ErrorKind, Write}; + +use wesley_core::{ + compute_generation_artifact_digest_v1, list_schema_operations_sdl, lower_schema_sdl, + ExtensionGenerationInputV1, GenerationArtifactContentV1, GenerationContractError, + GenerationProvenanceManifestV1, GenerationProvenanceVerificationV1, GenerationReviewV1, + GeneratorIdentityV1, OperationType, SchemaOperation, +}; + +const FIXTURE_SCHEMA: &str = r#" +type Query { + ponder(question: String!): Thought! +} + +type Thought { + answer: String! + confidence: Int! +} +"#; + +const OWNER_DECLARATION: &[u8] = br#"{ + "apiVersion": "example.brainfuck-semantics/v1", + "cellSemantics": "wrapping-u8", + "output": "stdout" +}"#; + +const GENERATOR_SETTINGS: &[u8] = br#"{ + "commentary": "mercifully omitted", + "tapeCells": 1 +}"#; + +const GENERATOR_COMPONENT: &[u8] = include_bytes!("ir_to_brainfuck.rs"); +const MAX_BRAINFUCK_STEPS: usize = 1_000_000; + +struct GeneratedBrainfuckExtension { + input: ExtensionGenerationInputV1, + source: GenerationArtifactContentV1, + output: GenerationArtifactContentV1, + manifest: GenerationProvenanceManifestV1, + review: GenerationReviewV1, + decoded_message: String, +} + +impl GeneratedBrainfuckExtension { + fn program(&self) -> Result<&str, std::str::Utf8Error> { + std::str::from_utf8(&self.output.bytes) + } + + fn verify(&self) -> Result { + self.manifest.verify( + &self.input, + GENERATOR_COMPONENT, + std::slice::from_ref(&self.source), + std::slice::from_ref(&self.output), + ) + } +} + +#[derive(Debug, Eq, PartialEq)] +enum BrainfuckError { + InputUnsupported { offset: usize }, + PointerUnderflow { offset: usize }, + StepLimitExceeded { limit: usize }, + UnmatchedClosingBracket { offset: usize }, + UnmatchedOpeningBracket { offset: usize }, +} + +impl Display for BrainfuckError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::InputUnsupported { offset } => { + write!( + formatter, + "input instruction at byte {offset} is unsupported" + ) + } + Self::PointerUnderflow { offset } => { + write!(formatter, "tape pointer underflow at byte {offset}") + } + Self::StepLimitExceeded { limit } => { + write!(formatter, "Brainfuck execution exceeded {limit} steps") + } + Self::UnmatchedClosingBracket { offset } => { + write!(formatter, "unmatched closing bracket at byte {offset}") + } + Self::UnmatchedOpeningBracket { offset } => { + write!(formatter, "unmatched opening bracket at byte {offset}") + } + } + } +} + +impl Error for BrainfuckError {} + +fn main() -> Result<(), Box> { + let mut stdout = std::io::stdout().lock(); + run(env::args_os().skip(1), &mut stdout) +} + +fn run( + args: impl IntoIterator, + stdout: &mut impl Write, +) -> Result<(), Box> { + let source_only = match args.into_iter().collect::>().as_slice() { + [] => false, + [flag] if flag == "--source" => true, + _ => { + return Err(IoError::new( + ErrorKind::InvalidInput, + "usage: cargo run -p wesley-core --example ir_to_brainfuck [-- --source]", + ) + .into()); + } + }; + + let generated = generate_brainfuck_extension(FIXTURE_SCHEMA)?; + let verification = generated.verify()?; + let program = generated.program()?; + + if source_only { + write_brainfuck_source(stdout, program)?; + return Ok(()); + } + + let playback = String::from_utf8(execute_brainfuck(program)?)?; + if playback != generated.decoded_message { + return Err(IoError::other("Brainfuck playback did not match generated input").into()); + } + + writeln!(stdout, "IR -> Brainfuck -> stdout")?; + write!(stdout, "{playback}")?; + writeln!(stdout, "brainfuck instructions: {}", program.len())?; + writeln!(stdout, "generation input: {}", generated.input.digest()?)?; + writeln!( + stdout, + "emitted program: {}", + generated.output.reference().digest + )?; + writeln!( + stdout, + "provenance manifest: {}", + generated.manifest.digest()? + )?; + writeln!( + stdout, + "review authoritative: {}", + generated.review.authoritative() + )?; + writeln!( + stdout, + "verified materials: {} source / {} output", + verification.verified_source_count, verification.verified_output_count + )?; + stdout.flush()?; + + Ok(()) +} + +fn write_brainfuck_source(writer: &mut impl Write, program: &str) -> Result<(), std::io::Error> { + writer.write_all(program.as_bytes())?; + writer.flush() +} + +fn generate_brainfuck_extension( + schema: &str, +) -> Result> { + let shape_ir = lower_schema_sdl(schema)?; + let operations = list_schema_operations_sdl(schema)?; + let source = GenerationArtifactContentV1::new( + "example:brainfuck-semantics@1", + OWNER_DECLARATION.to_vec(), + ); + let input = ExtensionGenerationInputV1::new( + shape_ir, + operations, + None, + vec![source.reference()], + compute_generation_artifact_digest_v1(GENERATOR_SETTINGS), + vec!["brainfuck-stdout".to_owned()], + )?; + + let decoded_message = ir_summary(&input); + let program = compile_to_brainfuck(decoded_message.as_bytes()); + let output = + GenerationArtifactContentV1::new("example:brainfuck-program@1", program.into_bytes()); + let generator = GeneratorIdentityV1::for_bytes( + "example:ir-to-brainfuck@1", + env!("CARGO_PKG_VERSION"), + GENERATOR_COMPONENT, + )?; + let manifest = + GenerationProvenanceManifestV1::new(&input, generator, vec![output.reference()])?; + let review = GenerationReviewV1::from_manifest(&input, &manifest)?; + + Ok(GeneratedBrainfuckExtension { + input, + source, + output, + manifest, + review, + decoded_message, + }) +} + +fn ir_summary(input: &ExtensionGenerationInputV1) -> String { + let operation_count = input.operations.len(); + let operation_noun = if operation_count == 1 { + "OPERATION" + } else { + "OPERATIONS" + }; + let operations = input + .operations + .iter() + .map(operation_label) + .collect::>() + .join(","); + + format!( + "WESLEY LOWERED {} TYPES AND {operation_count} ROOT {operation_noun}. \ +THE IR WAS BEAUTIFUL. THEN WE COMPILED IT TO BRAINFUCK. WHAT HAPPENED NEXT WAS NOT. \ +OPERATIONS={operations}. SHAPE={}\n", + input.shape_ir.types.len(), + input.shape_digest + ) +} + +fn operation_label(operation: &SchemaOperation) -> String { + let operation_type = match operation.operation_type { + OperationType::Query => "QUERY", + OperationType::Mutation => "MUTATION", + OperationType::Subscription => "SUBSCRIPTION", + }; + format!("{operation_type}.{}", operation.field_name.to_uppercase()) +} + +fn compile_to_brainfuck(message: &[u8]) -> String { + let capacity = message.iter().map(|byte| usize::from(*byte) + 4).sum(); + let mut program = String::with_capacity(capacity); + for byte in message { + // Clear one cell, set its exact byte value, then print it. This is not + // optimized, but it is deterministic and portable across wrapping-u8 + // Brainfuck interpreters—which is more portability than the idea earns. + program.push_str("[-]"); + program.extend(std::iter::repeat_n('+', usize::from(*byte))); + program.push('.'); + } + program +} + +fn execute_brainfuck(program: &str) -> Result, BrainfuckError> { + execute_brainfuck_with_step_limit(program, MAX_BRAINFUCK_STEPS) +} + +fn execute_brainfuck_with_step_limit( + program: &str, + step_limit: usize, +) -> Result, BrainfuckError> { + let instructions = program.as_bytes(); + let jumps = bracket_jumps(instructions)?; + if let Some(offset) = instructions + .iter() + .position(|instruction| *instruction == b',') + { + return Err(BrainfuckError::InputUnsupported { offset }); + } + let mut tape = vec![0_u8]; + let mut pointer = 0_usize; + let mut offset = 0_usize; + let mut steps = 0_usize; + let mut output = Vec::new(); + + while offset < instructions.len() { + steps += 1; + if steps > step_limit { + return Err(BrainfuckError::StepLimitExceeded { limit: step_limit }); + } + + match instructions[offset] { + b'>' => { + pointer += 1; + if pointer == tape.len() { + tape.push(0); + } + offset += 1; + } + b'<' => { + pointer = pointer + .checked_sub(1) + .ok_or(BrainfuckError::PointerUnderflow { offset })?; + offset += 1; + } + b'+' => { + tape[pointer] = tape[pointer].wrapping_add(1); + offset += 1; + } + b'-' => { + tape[pointer] = tape[pointer].wrapping_sub(1); + offset += 1; + } + b'.' => { + output.push(tape[pointer]); + offset += 1; + } + b'[' if tape[pointer] == 0 => { + offset = jumps[offset].expect("validated opening bracket") + 1; + } + b'[' => offset += 1, + b']' if tape[pointer] != 0 => { + offset = jumps[offset].expect("validated closing bracket"); + } + b']' => offset += 1, + _ => offset += 1, + } + } + + Ok(output) +} + +fn bracket_jumps(instructions: &[u8]) -> Result>, BrainfuckError> { + let mut jumps = vec![None; instructions.len()]; + let mut openings = Vec::new(); + + for (offset, instruction) in instructions.iter().enumerate() { + match instruction { + b'[' => openings.push(offset), + b']' => { + let opening = openings + .pop() + .ok_or(BrainfuckError::UnmatchedClosingBracket { offset })?; + jumps[opening] = Some(offset); + jumps[offset] = Some(opening); + } + _ => {} + } + } + + if let Some(offset) = openings.pop() { + return Err(BrainfuckError::UnmatchedOpeningBracket { offset }); + } + + Ok(jumps) +} + +#[cfg(test)] +mod tests { + use super::*; + use wesley_core::GenerationContractErrorKind; + + #[test] + fn generated_brainfuck_is_deterministic_and_replays_the_ir_summary() { + let first = generate_brainfuck_extension(FIXTURE_SCHEMA).unwrap(); + let second = generate_brainfuck_extension(FIXTURE_SCHEMA).unwrap(); + + assert_eq!( + first.input.canonical_bytes().unwrap(), + second.input.canonical_bytes().unwrap() + ); + assert_eq!(first.output.bytes, second.output.bytes); + assert_eq!( + first.manifest.canonical_bytes().unwrap(), + second.manifest.canonical_bytes().unwrap() + ); + assert_eq!( + execute_brainfuck(first.program().unwrap()).unwrap(), + first.decoded_message.as_bytes() + ); + let mut source_bytes = Vec::new(); + run([OsString::from("--source")], &mut source_bytes).unwrap(); + assert_eq!(source_bytes, first.output.bytes); + assert_eq!( + compute_generation_artifact_digest_v1(&source_bytes), + "sha256:5a895685bbf8fe174cbdf148b853fd615432fc0f795a9bb82d9bb216e6cbcfe9" + ); + assert!(first.decoded_message.contains("QUERY.PONDER")); + assert!(first.decoded_message.contains(&first.input.shape_digest)); + assert!(!first.review.authoritative()); + + let verification = first.verify().unwrap(); + assert_eq!(verification.verified_source_count, 1); + assert_eq!(verification.verified_output_count, 1); + } + + #[test] + fn semantic_input_changes_move_the_input_and_brainfuck_output() { + let first = generate_brainfuck_extension(FIXTURE_SCHEMA).unwrap(); + let changed_schema = FIXTURE_SCHEMA.replace("ponder", "overthink"); + let changed = generate_brainfuck_extension(&changed_schema).unwrap(); + + assert_ne!( + first.input.digest().unwrap(), + changed.input.digest().unwrap() + ); + assert_ne!(first.output.bytes, changed.output.bytes); + assert!(changed.decoded_message.contains("QUERY.OVERTHINK")); + } + + #[test] + fn provenance_rejects_tampered_brainfuck() { + let generated = generate_brainfuck_extension(FIXTURE_SCHEMA).unwrap(); + let mut tampered = generated.output.clone(); + tampered.bytes.push(b'+'); + + let error = generated + .manifest + .verify( + &generated.input, + GENERATOR_COMPONENT, + std::slice::from_ref(&generated.source), + std::slice::from_ref(&tampered), + ) + .unwrap_err(); + + assert_eq!( + error.kind, + GenerationContractErrorKind::ArtifactDigestMismatch + ); + assert_eq!(error.subject, "example:brainfuck-program@1"); + } + + #[test] + fn interpreter_rejects_malformed_or_ambient_input_programs() { + assert_eq!( + execute_brainfuck("[").unwrap_err(), + BrainfuckError::UnmatchedOpeningBracket { offset: 0 } + ); + assert_eq!( + execute_brainfuck("]").unwrap_err(), + BrainfuckError::UnmatchedClosingBracket { offset: 0 } + ); + assert_eq!( + execute_brainfuck(",").unwrap_err(), + BrainfuckError::InputUnsupported { offset: 0 } + ); + assert_eq!( + execute_brainfuck("[,]").unwrap_err(), + BrainfuckError::InputUnsupported { offset: 1 } + ); + } + + #[test] + fn interpreter_rejects_pointer_underflow() { + assert_eq!( + execute_brainfuck("<").unwrap_err(), + BrainfuckError::PointerUnderflow { offset: 0 } + ); + } + + #[test] + fn interpreter_enforces_the_step_limit() { + assert_eq!( + execute_brainfuck_with_step_limit("+[]", 3).unwrap_err(), + BrainfuckError::StepLimitExceeded { limit: 3 } + ); + } + + #[cfg(unix)] + #[test] + fn non_utf8_arguments_return_the_usage_error() { + use std::os::unix::ffi::OsStringExt; + + let argument = OsString::from_vec(vec![0xff]); + let error = run([argument], &mut Vec::new()).unwrap_err(); + let io_error = error.downcast_ref::().unwrap(); + + assert_eq!(io_error.kind(), ErrorKind::InvalidInput); + } +} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 8ad522f5..5deee92e 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -460,6 +460,7 @@ flowchart LR Maintainer --> CargoXtask[cargo xtask] CargoXtask --> DocsCheck[docs-check] CargoXtask --> Tests[cargo test --workspace] + CargoXtask --> ExampleTests[cargo test -p wesley-core --example ir_to_brainfuck] CargoXtask --> NativeHelp[cargo run --bin wesley -- --help] CargoXtask --> Release[cargo build --release + package wesley-core] CargoXtask --> Legacy[cargo xtask legacy-preflight] @@ -477,9 +478,10 @@ wesley schema diff --schema schema.graphql --base origin/main ``` `cargo xtask preflight` is the ordinary product health check. It runs -Rust-native docs hygiene checks, Rust workspace tests, and verifies the native -CLI help surface. `cargo xtask legacy-preflight` intentionally crosses into -JavaScript package tooling only for retained package or pnpm-workspace changes. +Rust-native docs hygiene checks, Rust workspace tests, the explicit +`ir_to_brainfuck` example tests, and verifies the native CLI help surface. +`cargo xtask legacy-preflight` intentionally crosses into JavaScript package +tooling only for retained package or pnpm-workspace changes. ## Non-Compiler JavaScript Tooling @@ -631,7 +633,9 @@ Those systems may consume Wesley facts. They should not become Wesley core. flowchart LR RustTests[cargo test --workspace] --> CoreTests[core lowering + operation analysis] RustTests --> CliTests[native CLI help/unknown command] + ExampleTests[cargo test -p wesley-core --example ir_to_brainfuck] --> BrainfuckTests[external generation playback + provenance] Preflight[cargo xtask preflight] --> RustTests + Preflight --> ExampleTests Preflight --> NativeHelp[native help smoke] Legacy[cargo xtask legacy-preflight] --> Pnpm[pnpm run legacy-preflight] Pnpm --> Links[docs links] diff --git a/docs/END_TO_END.md b/docs/END_TO_END.md index 7229566b..f015c1d4 100644 --- a/docs/END_TO_END.md +++ b/docs/END_TO_END.md @@ -980,6 +980,7 @@ flowchart TD Change --> Fixtures[pnpm fixtures:ir] RustPreflight --> RustTests[cargo test --workspace] + RustPreflight --> ExampleTests[cargo test -p wesley-core --example ir_to_brainfuck] RustPreflight --> NativeHelp[native CLI help smoke] LegacyPreflight --> PnpmLegacy[pnpm run legacy-preflight] PnpmLegacy --> DocsLinks[legacy docs links] @@ -991,6 +992,7 @@ flowchart TD Fixtures --> Golden[L1 golden files] RustTests --> PR[Pull request] + ExampleTests --> PR NativeHelp --> PR DocsLinks --> PR DocsTruth --> PR diff --git a/docs/TECHNICAL_TEARDOWN.md b/docs/TECHNICAL_TEARDOWN.md index b1d182b1..2d853c1d 100644 --- a/docs/TECHNICAL_TEARDOWN.md +++ b/docs/TECHNICAL_TEARDOWN.md @@ -508,7 +508,8 @@ exits. Nothing persists unless the user chooses a command with `--out` or ### Xtask Runtime `cargo xtask preflight` is a different runtime surface. It is a repository -maintenance command that runs docs checks, `cargo test --workspace`, and +maintenance command that runs docs checks, `cargo test --workspace`, +`cargo test -p wesley-core --example ir_to_brainfuck`, and `cargo run --bin wesley -- --help`. It does not define compiler semantics; it checks that the repository remains healthy. diff --git a/docs/governance/RELEASE_POLICY.md b/docs/governance/RELEASE_POLICY.md index e669c491..b62f7151 100644 --- a/docs/governance/RELEASE_POLICY.md +++ b/docs/governance/RELEASE_POLICY.md @@ -86,7 +86,8 @@ runs, in order: 2. `cargo clippy --workspace --all-targets -- -D warnings` 3. `cargo xtask docs-check` 4. `cargo test --workspace` -5. `cargo run --bin wesley -- --help` +5. `cargo test -p wesley-core --example ir_to_brainfuck` +6. `cargo run --bin wesley -- --help` JavaScript dependency advisories are tracked by Dependabot and the `dependency-review` workflow. `pnpm audit` was removed from the gate after npm diff --git a/docs/reference/extension-generation.md b/docs/reference/extension-generation.md index 0a2ebb7a..6e0147fe 100644 --- a/docs/reference/extension-generation.md +++ b/docs/reference/extension-generation.md @@ -84,15 +84,48 @@ missing, unexpected, or mismatched material with structured error kinds. - No API in this contract reads the filesystem, environment, clock, network, package registry, or process state. +## Runnable Example: IR To Brainfuck + +For an end-to-end external-generator example with appropriately questionable +target-language judgment, run: + +```text +cargo run --quiet -p wesley-core --example ir_to_brainfuck +``` + +Cargo compiles an example target as a separate crate, so +`crates/wesley-core/examples/ir_to_brainfuck.rs` consumes only the public +`wesley-core` API. It lowers a small GraphQL schema, derives a deterministic +summary from canonical Shape IR and normalized root operations, compiles that +summary into Brainfuck, executes the program, and verifies the exact owner +declaration, generator component, and emitted program bytes. The playback also +prints the input and provenance digests and confirms that the review projection +is non-authoritative. + +Emit the complete Brainfuck source instead of running the human-readable +playback with: + +```text +cargo run --quiet -p wesley-core --example ir_to_brainfuck -- --source +``` + +The example is educational evidence for the external-generation contract. Its +compiler and interpreter remain owned by the separate example crate; the public +`wesley-core` library and `wesley` CLI gain no plugin discovery, target-execution, +Brainfuck-semantics, or command surface. + ## Verification Run the focused executable contract and fixture checks with: ```text cargo test -p wesley-core --test extension_generation +cargo test -p wesley-core --example ir_to_brainfuck ``` The integration test is compiled as an external Rust crate against only the public `wesley-core` API. Checked fixtures under `test/fixtures/extension-generation/` are compared with canonical output and -validated against the published schemas. +validated against the published schemas. `cargo xtask test` and +`pnpm run preflight` also execute the Brainfuck example tests so the shipped +playback cannot silently drift. diff --git a/docs/topics/validation.md b/docs/topics/validation.md index c6bdd44a..f031bb8f 100644 --- a/docs/topics/validation.md +++ b/docs/topics/validation.md @@ -13,9 +13,10 @@ Run strict preflight before opening or updating a substantial PR: cargo xtask preflight ``` -The gate runs formatting, clippy, docs checks, workspace tests, and a native -CLI smoke test. JavaScript dependency advisories are handled by Dependabot and -the `dependency-review` workflow, not by preflight. +The gate runs formatting, clippy, docs checks, workspace tests, the explicit +`ir_to_brainfuck` example tests, and a native CLI smoke test. JavaScript +dependency advisories are handled by Dependabot and the `dependency-review` +workflow, not by preflight. ## Focused Checks diff --git a/test/release-governance.bats b/test/release-governance.bats index 7e42347e..d78f2402 100644 --- a/test/release-governance.bats +++ b/test/release-governance.bats @@ -83,6 +83,28 @@ load 'vendor/bats-plugins/bats-assert/load' assert_success } +@test "strict-preflight docs track the explicit example tests" { + run awk ' + /^4\. `cargo test --workspace`$/ { workspace = NR } + /^5\. `cargo test -p wesley-core --example ir_to_brainfuck`$/ { example = NR } + /^6\. `cargo run --bin wesley -- --help`$/ { smoke = NR } + END { exit !(workspace && example == workspace + 1 && smoke == example + 1) } + ' docs/governance/RELEASE_POLICY.md + assert_success + + for path in \ + docs/ARCHITECTURE.md \ + docs/END_TO_END.md \ + docs/TECHNICAL_TEARDOWN.md \ + docs/topics/validation.md; do + run grep -F "ir_to_brainfuck" "$path" + assert_success + done + + run grep -F "Run Rust workspace and explicit example tests" xtask/src/main.rs + assert_success +} + @test "release profile assertions are YAML spacing tolerant" { run bash -lc "awk '/@test \"release profile names every published Wesley crate\"/{in_test=1} in_test && /^@test / && !/release profile names every published Wesley crate/{exit} in_test {print}' test/release-governance.bats | grep -F 'grep -Eq'" assert_success diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 43706659..e6d86f0d 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -92,7 +92,7 @@ fn run(args: Vec) -> Result<(), Error> { print_help(); Ok(()) } - "test" => run_command("cargo", &["test", "--workspace"]), + "test" => run_tests(), "bench-ir" => run_bench_ir(&args[1..]), "preflight" | "strict-preflight" => run_preflight(), "docs-check" => run_docs_check(), @@ -131,10 +131,18 @@ fn run_preflight() -> Result<(), Error> { // retired its audit endpoint (HTTP 410), so the call always fails, and the // Rust-native compiler preflight should not depend on npm registry health. run_docs_check()?; - run_command("cargo", &["test", "--workspace"])?; + run_tests()?; run_command("cargo", &["run", "--bin", "wesley", "--", "--help"]) } +fn run_tests() -> Result<(), Error> { + run_command("cargo", &["test", "--workspace"])?; + run_command( + "cargo", + &["test", "-p", "wesley-core", "--example", "ir_to_brainfuck"], + ) +} + fn run_bench_ir(args: &[OsString]) -> Result<(), Error> { let options = BenchIrOptions::parse(args)?; @@ -3151,7 +3159,7 @@ Usage: cargo xtask Commands: - test Run Rust workspace tests + test Run Rust workspace and explicit example tests bench-ir Run advisory Rust-native IR lowering benchmarks docs-check Run Rust-native documentation hygiene checks preflight Run the strict pre-PR/release quality gate