From 477a6036caad17f2f65687075ebf8778d9dfb38e Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 1 Nov 2026 05:32:19 -0800 Subject: [PATCH 01/14] Improved AGENTS.md for legible code production --- AGENTS.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index cbc42790f..1da426318 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,6 +31,7 @@ Seven terms have exactly one meaning each, everywhere in this repository: code c ## Principles +- Design for human review. A reviewer reads one crate at a time: its code, its surface (every item another crate can name, methods, fields, and variants included), and the items it names from other crates. Keep all three small, and prefer more, smaller crates to fewer, larger ones. - Do more with less. Prefer simple, foundational primitives over specific solutions: a primitive that naturally enables today's functionality and also generalizes beats a custom mechanism specified as a laundry list of requirements. Generality is the payoff, not a goal. - When evaluating how to implement a feature, check whether the existing facilities subsume the work before building new machinery. Prioritize in this order: 1. Reuse an existing facility @@ -69,6 +70,17 @@ Seven terms have exactly one meaning each, everywhere in this repository: code c - Temporary directory names come from the test's name, the process id, and a counter. - A module with more than one file keeps its files in a directory named after it, in the standard module layout (`foo.rs` beside `foo/bar.rs`). A directory may hold any number of files, one included, and a module's unit tests are one of them (`foo/tests.rs`). Never move files between a flat sibling form and a directory because of how many files a group holds, and never change a group's layout only to bring it in line with this rule. Existing `foo-bar.rs` siblings wired with `#[path = "foo-bar.rs"] mod bar;` stay as they are, and new code adds no path attribute for layout. Top-level `tests/` and `benches/` trees follow Cargo target conventions. +## Surfaces + +Test code is `#[cfg(test)]` code, `tests/`, `benches/`, `examples/`, and code gated on the `test-support` or `test-fixtures` feature. For every item in the surface of a crate directly under `crates/`, re-exported items included, a program outside this repository counts as another crate. Add no violation of these rules in a change, and fix an existing violation in a change of its own, so each review covers one purpose. + +- Make an item `pub` only when another crate names it, and gate it behind `test-support` or `test-fixtures` when every crate that names it does so in test code, adding `test-support` to a crate that has neither. Leave `pub` an item that only this crate's own `tests/` names. Make every other item `pub(crate)` or private, and delete what the `dead_code` lint then reports. +- Add no second reader of a field to the surface; a dependent that needs the value in another form derives it at its call site. A reader is the field itself when it is `pub`, or a function whose only parameter is the value holding the field, by value or by reference, and whose result depends on that field alone. Trait impls, and functions a workspace lint requires beside a reader, are not readers. +- Re-export another crate's item only when a rule that `cargo test -p build-xtask` enforces forbids the dependent that needs it from depending on the crate that defines it. +- Remove a dependency that no code in the crate names, and move a dependency to `[dev-dependencies]` when only `#[cfg(test)]` code, `tests/`, `benches/`, or `examples/` names it. Keep a dependency that only sets another dependency's features, with a comment that says so. +- Put new items in a new crate when a crate that will name them names nothing else in the crate you meant to extend. Extend that crate instead when the two would depend on each other. Treat a new crate as a smaller change than widening an existing crate's surface. +- Remove an edge between two crates by moving items, not by merging the crates, because a merge deletes the boundary review relies on. + ## SPA and CSS Rules - No raw color, size, or spacing values in Workshop UI component CSS; use the custom properties defined in `@workshop/look` and `ui/src/tokens/component.css`. From 4963533864b641e54ac308d0aece8febc42fa565 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 1 Nov 2026 05:44:54 -0800 Subject: [PATCH 02/14] Drop the parser's unused `LuaProgram` re-export The parser's surface listed `LuaProgram`, but no crate outside the parser named it through that path. Delete the re-export and have the three modules and three test modules that name the type import `runvil_lua::LuaProgram` directly. --- crates/runvil-internal/parser/src/fence.rs | 3 ++- crates/runvil-internal/parser/src/lib.rs | 2 -- crates/runvil-internal/parser/src/prompt.rs | 3 ++- crates/runvil-internal/parser/src/sections.rs | 3 ++- crates/runvil-internal/parser/src/tests.rs | 1 + crates/runvil-internal/parser/src/tests/line_mapping.rs | 1 + crates/runvil-internal/parser/src/tests/section_fences.rs | 1 + 7 files changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/runvil-internal/parser/src/fence.rs b/crates/runvil-internal/parser/src/fence.rs index 87568f473..3908dcc62 100644 --- a/crates/runvil-internal/parser/src/fence.rs +++ b/crates/runvil-internal/parser/src/fence.rs @@ -15,7 +15,8 @@ use std::sync::Arc; use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag}; use runvil_types::emitter::Emitter; -use crate::{Block, Error, LuaProgram, ParseErrorKind, Result, sections}; +use crate::{Block, Error, ParseErrorKind, Result, sections}; +use runvil_lua::LuaProgram; /// Uncompiled block produced while scanning section content. pub(super) enum RawBlock<'a> { diff --git a/crates/runvil-internal/parser/src/lib.rs b/crates/runvil-internal/parser/src/lib.rs index c905d2caf..735824955 100644 --- a/crates/runvil-internal/parser/src/lib.rs +++ b/crates/runvil-internal/parser/src/lib.rs @@ -22,8 +22,6 @@ //! `cargo test -p build-xtask` enforces the product and container //! boundaries. -pub use runvil_lua::LuaProgram; - mod body_text; mod contract; pub mod detail; diff --git a/crates/runvil-internal/parser/src/prompt.rs b/crates/runvil-internal/parser/src/prompt.rs index 934f0d3d1..871406ff8 100644 --- a/crates/runvil-internal/parser/src/prompt.rs +++ b/crates/runvil-internal/parser/src/prompt.rs @@ -6,7 +6,8 @@ //! Construction happens in the parsing modules; this module holds the value //! types and the invariant-preserving operations on them. -use crate::{BodyText, Frontmatter, LuaProgram}; +use crate::{BodyText, Frontmatter}; +use runvil_lua::LuaProgram; /// One executable block inside a section: a compiled Lua fence or prose. #[derive(Debug, Clone, PartialEq, Eq)] diff --git a/crates/runvil-internal/parser/src/sections.rs b/crates/runvil-internal/parser/src/sections.rs index 4a4d2ad80..819695ffa 100644 --- a/crates/runvil-internal/parser/src/sections.rs +++ b/crates/runvil-internal/parser/src/sections.rs @@ -8,7 +8,8 @@ use pulldown_cmark::{Event, HeadingLevel, Options, Parser, Tag, TagEnd}; use runvil_types::emitter::Emitter; use crate::fence::{self, RawBlock}; -use crate::{Block, BodyText, Error, LuaProgram, ParseErrorKind, Result, Section, list}; +use crate::{Block, BodyText, Error, ParseErrorKind, Result, Section, list}; +use runvil_lua::LuaProgram; /// Wraps a computed 1-based source line for [`LuaProgram::compile`]. /// diff --git a/crates/runvil-internal/parser/src/tests.rs b/crates/runvil-internal/parser/src/tests.rs index d6aac9942..fc8ea5e62 100644 --- a/crates/runvil-internal/parser/src/tests.rs +++ b/crates/runvil-internal/parser/src/tests.rs @@ -2,6 +2,7 @@ use std::num::NonZeroU32; +use runvil_lua::LuaProgram; use runvil_types::event::Event; use super::*; diff --git a/crates/runvil-internal/parser/src/tests/line_mapping.rs b/crates/runvil-internal/parser/src/tests/line_mapping.rs index 37172230f..13645b3a9 100644 --- a/crates/runvil-internal/parser/src/tests/line_mapping.rs +++ b/crates/runvil-internal/parser/src/tests/line_mapping.rs @@ -2,6 +2,7 @@ //! file-absolute line. use super::*; +use runvil_lua::LuaProgram; fn assert_runtime_error_line(program: &LuaProgram, absolute_line: u32) { let lua = mlua::Lua::new(); diff --git a/crates/runvil-internal/parser/src/tests/section_fences.rs b/crates/runvil-internal/parser/src/tests/section_fences.rs index 5391fac36..78c5db492 100644 --- a/crates/runvil-internal/parser/src/tests/section_fences.rs +++ b/crates/runvil-internal/parser/src/tests/section_fences.rs @@ -2,6 +2,7 @@ //! compilation reports. use super::*; +use runvil_lua::LuaProgram; #[test] fn lua_fence_separated_from_prose() { From f2b0eb3cc755a205dc6cfc0dc4c80568a3bad0a5 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 1 Nov 2026 05:50:00 -0800 Subject: [PATCH 03/14] Store the tool-loop cap as an Option Delete the `MaxToolIterations` enum, its two methods, and its hand-written `Deserialize`; the frontmatter field now holds an `Option` that one `deserialize_with` function validates. `Frontmatter::max_tool_iterations` returns the field, and each caller resolves its own default. - The range check still runs at parse: `max_tool_iterations` rejects `0`, negatives, values above `MAX_TOOL_ITERATIONS`, and values above `u32`, so an invalid cap cannot reach execution. - `detail::max_tool_iterations` is deleted; `ChainState::max_tool_iterations` and the `tool_loop` test now fold the `Option` with `map_or` at their own call sites. --- .../engine/src/execute/context.rs | 10 +- .../engine/src/execute/tests/tool_loop.rs | 8 +- crates/runvil-internal/parser/src/detail.rs | 11 +-- .../runvil-internal/parser/src/frontmatter.rs | 97 +++++++------------ crates/runvil-internal/parser/src/lib.rs | 2 +- .../parser/src/tests/frontmatter.rs | 20 +--- 6 files changed, 53 insertions(+), 95 deletions(-) diff --git a/crates/runvil-internal/engine/src/execute/context.rs b/crates/runvil-internal/engine/src/execute/context.rs index 6a2b1fe09..4ce2c168d 100644 --- a/crates/runvil-internal/engine/src/execute/context.rs +++ b/crates/runvil-internal/engine/src/execute/context.rs @@ -333,8 +333,14 @@ impl ChainState { /// call: the frontmatter's `max_tool_iterations` over the limits /// default. fn max_tool_iterations(&self) -> usize { - crate::parser::detail::max_tool_iterations(self.shared.prompt.frontmatter()) - .resolve(self.shared.limits.max_tool_iterations.get() as usize) + self.shared + .prompt + .frontmatter() + .max_tool_iterations() + .map_or( + self.shared.limits.max_tool_iterations.get() as usize, + |cap| cap.get() as usize, + ) } /// The run's top-level section count, reported as `sys.section_count`. diff --git a/crates/runvil-internal/engine/src/execute/tests/tool_loop.rs b/crates/runvil-internal/engine/src/execute/tests/tool_loop.rs index 56b04df93..6e21dfe42 100644 --- a/crates/runvil-internal/engine/src/execute/tests/tool_loop.rs +++ b/crates/runvil-internal/engine/src/execute/tests/tool_loop.rs @@ -108,14 +108,18 @@ fn run_resolves_cap_from_frontmatter_else_default() { "---\nname: t\ndescription: d\nmax_tool_iterations: 5\n---\n\n# T\n\n## S\n\np\n"; let p = Prompt::parse(declared, EXECUTION).0.unwrap(); assert_eq!( - runvil_parser::detail::max_tool_iterations(p.frontmatter()).resolve(default_cap), + p.frontmatter() + .max_tool_iterations() + .map_or(default_cap, |cap| cap.get() as usize), 5 ); let absent = "---\nname: t\ndescription: d\n---\n\n# T\n\n## S\n\np\n"; let p = Prompt::parse(absent, EXECUTION).0.unwrap(); assert_eq!( - runvil_parser::detail::max_tool_iterations(p.frontmatter()).resolve(default_cap), + p.frontmatter() + .max_tool_iterations() + .map_or(default_cap, |cap| cap.get() as usize), default_cap ); } diff --git a/crates/runvil-internal/parser/src/detail.rs b/crates/runvil-internal/parser/src/detail.rs index dafdcc210..e4a5e8628 100644 --- a/crates/runvil-internal/parser/src/detail.rs +++ b/crates/runvil-internal/parser/src/detail.rs @@ -4,9 +4,7 @@ //! The `runvil` facade never re-exports this module, so only Engine //! crates reach it. -use crate::{ - Block, Error, Frontmatter, MaxToolIterations, ParseError, ParseErrorKind, Prompt, Section, -}; +use crate::{Block, Error, ParseError, ParseErrorKind, Prompt, Section}; /// Unwraps a parse failure into the internal error it classifies, so the /// Engine can map it onto its own internal error type variant for variant. @@ -57,10 +55,3 @@ pub fn sections(prompt: &Prompt) -> &[Section] { pub fn entry(prompt: &Prompt) -> Option<&Section> { prompt.entry() } - -/// Returns the frontmatter's tool-loop cap in the form the Engine resolves -/// against its own default. -#[must_use] -pub fn max_tool_iterations(frontmatter: &Frontmatter) -> MaxToolIterations { - frontmatter.max_tool_iterations -} diff --git a/crates/runvil-internal/parser/src/frontmatter.rs b/crates/runvil-internal/parser/src/frontmatter.rs index ec3927caa..0ed15e91f 100644 --- a/crates/runvil-internal/parser/src/frontmatter.rs +++ b/crates/runvil-internal/parser/src/frontmatter.rs @@ -57,11 +57,12 @@ pub struct Frontmatter { pub(crate) runvil: Option, /// Maximum model round trips a section's tool-call loop may take. /// - /// A non-optional [`MaxToolIterations`]: an absent value deserializes to - /// [`MaxToolIterations::Default`] (the runtime applies its own cap) and any - /// explicit value is a positive, bounded count. Zero is unrepresentable. - #[serde(default)] - pub(crate) max_tool_iterations: MaxToolIterations, + /// An absent value leaves the runtime's own cap in force. A declared + /// value is a positive, bounded count: deserialization rejects `0`, + /// negatives, and values above [`MAX_TOOL_ITERATIONS`], so no invalid + /// cap can reach execution. + #[serde(default, deserialize_with = "max_tool_iterations")] + pub(crate) max_tool_iterations: Option, /// A file the prompt expects to find in the store when it starts. #[serde(default)] input: Option, @@ -85,65 +86,33 @@ pub struct Frontmatter { /// The largest explicit `max_tool_iterations` a prompt may declare. pub(crate) const MAX_TOOL_ITERATIONS: u32 = 1000; -/// A frontmatter tool-loop cap: absent or a positive, bounded count. +/// Deserializes the `max_tool_iterations` cap: absent, or a positive count no +/// larger than [`MAX_TOOL_ITERATIONS`]. /// -/// An omitted cap leaves the runtime's own default in force. Deserialization -/// rejects `0`, negatives, and values above `MAX_TOOL_ITERATIONS`, so no -/// invalid cap can reach execution. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -#[non_exhaustive] -pub enum MaxToolIterations { - /// No explicit cap; the runtime applies its own default. - #[default] - Default, - /// An explicit, positive, bounded cap. - Limit(NonZeroU32), -} - -impl MaxToolIterations { - /// Resolves to a concrete iteration cap, using `default` when none was set. - #[must_use] - pub fn resolve(self, default: usize) -> usize { - match self { - MaxToolIterations::Default => default, - MaxToolIterations::Limit(limit) => limit.get() as usize, - } - } - - /// Returns the explicit limit when one was declared. - #[must_use] - pub(crate) fn limit(self) -> Option { - match self { - MaxToolIterations::Default => None, - MaxToolIterations::Limit(limit) => Some(limit), - } - } -} - -impl<'de> serde::Deserialize<'de> for MaxToolIterations { - fn deserialize(deserializer: D) -> std::result::Result - where - D: serde::Deserializer<'de>, - { - // Deserialize as i64 so negatives and > u32 values are caught, not wrapped. - let raw = i64::deserialize(deserializer)?; - if raw <= 0 { - return Err(serde::de::Error::custom(format!( - "max_tool_iterations must be a positive integer (>= 1), got {raw}" - ))); - } - if raw > i64::from(MAX_TOOL_ITERATIONS) { - return Err(serde::de::Error::custom(format!( - "max_tool_iterations must be <= {MAX_TOOL_ITERATIONS}, got {raw}" - ))); - } - // 1 <= raw <= MAX_TOOL_ITERATIONS, so both conversions are infallible. - let value = u32::try_from(raw) - .map_err(|_| serde::de::Error::custom("max_tool_iterations is out of range"))?; - let limit = NonZeroU32::new(value) - .ok_or_else(|| serde::de::Error::custom("max_tool_iterations must be non-zero"))?; - Ok(MaxToolIterations::Limit(limit)) - } +/// Deserialization reads an `i64` so negatives and values above `u32` are +/// rejected instead of wrapped, and rejects `0` so every accepted value is a +/// [`NonZeroU32`] and no invalid cap can reach execution. +fn max_tool_iterations<'de, D>(deserializer: D) -> std::result::Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let Some(raw) = as serde::Deserialize>::deserialize(deserializer)? else { + return Ok(None); + }; + if raw <= 0 { + return Err(serde::de::Error::custom(format!( + "max_tool_iterations must be a positive integer (>= 1), got {raw}" + ))); + } + if raw > i64::from(MAX_TOOL_ITERATIONS) { + return Err(serde::de::Error::custom(format!( + "max_tool_iterations must be <= {MAX_TOOL_ITERATIONS}, got {raw}" + ))); + } + // 1 <= raw <= MAX_TOOL_ITERATIONS, so both conversions are infallible. + let value = u32::try_from(raw) + .map_err(|_| serde::de::Error::custom("max_tool_iterations is out of range"))?; + Ok(NonZeroU32::new(value)) } impl Frontmatter { @@ -173,7 +142,7 @@ impl Frontmatter { /// applies. #[must_use] pub fn max_tool_iterations(&self) -> Option { - self.max_tool_iterations.limit() + self.max_tool_iterations } /// Returns the declared input file, when present. diff --git a/crates/runvil-internal/parser/src/lib.rs b/crates/runvil-internal/parser/src/lib.rs index 735824955..0ebe57cb6 100644 --- a/crates/runvil-internal/parser/src/lib.rs +++ b/crates/runvil-internal/parser/src/lib.rs @@ -42,7 +42,7 @@ use error::Result; pub use error::{Error, ParseError, ParseErrorKind}; #[cfg(test)] use frontmatter::MAX_TOOL_ITERATIONS; -pub use frontmatter::{FileDecl, Frontmatter, MaxToolIterations}; +pub use frontmatter::{FileDecl, Frontmatter}; pub use prompt::{Block, Prompt, Section}; #[cfg(test)] diff --git a/crates/runvil-internal/parser/src/tests/frontmatter.rs b/crates/runvil-internal/parser/src/tests/frontmatter.rs index 66642de94..e497890a3 100644 --- a/crates/runvil-internal/parser/src/tests/frontmatter.rs +++ b/crates/runvil-internal/parser/src/tests/frontmatter.rs @@ -33,18 +33,15 @@ fn unknown_frontmatter_field_is_rejected() { } #[test] -fn max_tool_iterations_parses_positive_and_defaults_when_absent() { +fn max_tool_iterations_parses_positive_and_is_absent_when_unset() { let p = parse_ok(&prompt("max_tool_iterations: 20", &[])); assert_eq!( p.frontmatter.max_tool_iterations, - MaxToolIterations::Limit(std::num::NonZeroU32::new(20).unwrap()) + Some(std::num::NonZeroU32::new(20).unwrap()) ); let p = parse_ok(&prompt("", &[])); - assert_eq!( - p.frontmatter.max_tool_iterations, - MaxToolIterations::Default - ); + assert_eq!(p.frontmatter.max_tool_iterations, None); } #[test] @@ -67,16 +64,7 @@ fn max_tool_iterations_accepts_the_upper_boundary() { )); assert_eq!( p.frontmatter.max_tool_iterations, - MaxToolIterations::Limit(std::num::NonZeroU32::new(MAX_TOOL_ITERATIONS).unwrap()) - ); -} - -#[test] -fn max_tool_iterations_resolve_uses_default_only_when_absent() { - assert_eq!(MaxToolIterations::Default.resolve(24), 24); - assert_eq!( - MaxToolIterations::Limit(std::num::NonZeroU32::new(3).unwrap()).resolve(24), - 3 + Some(std::num::NonZeroU32::new(MAX_TOOL_ITERATIONS).unwrap()) ); } From 95528ac7f69b5f12cebcebbc0d5127d6d2aa9a03 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 1 Nov 2026 05:59:42 -0800 Subject: [PATCH 04/14] Gate the classic-block accessors behind test support No production crate calls `Section::prologue`, `Section::prose`, or `Section::epilog`; the parser's own tests and the Engine's suite are the only callers. Gate all three behind `test-support` so a production build cannot reach them. - `tasks-spawn` dropped its `prologue` and `epilog` checks from the list-section guard. A section parses into `items` only when it holds no Lua block, so `items()` alone answers the question. --- .../engine/src/execute/scheduler/tasks-spawn.rs | 4 +++- crates/runvil-internal/parser/src/prompt.rs | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/runvil-internal/engine/src/execute/scheduler/tasks-spawn.rs b/crates/runvil-internal/engine/src/execute/scheduler/tasks-spawn.rs index b9871246c..a46305bce 100644 --- a/crates/runvil-internal/engine/src/execute/scheduler/tasks-spawn.rs +++ b/crates/runvil-internal/engine/src/execute/scheduler/tasks-spawn.rs @@ -107,7 +107,9 @@ impl Scheduler { let worker = resolved.pos.section(&prompt).ok_or(Error::internal( "a resolved worker is absent from its slice", ))?; - if worker.prologue().is_none() && worker.epilog().is_none() && !worker.items().is_empty() { + // A section is list-only exactly when it parsed into list items, which + // the parser refuses for any section holding a Lua block. + if !worker.items().is_empty() { return Err(Error::lua_message(format!( "section `{}` is a list section, not a worker template", worker.name() diff --git a/crates/runvil-internal/parser/src/prompt.rs b/crates/runvil-internal/parser/src/prompt.rs index 871406ff8..8f78d1f52 100644 --- a/crates/runvil-internal/parser/src/prompt.rs +++ b/crates/runvil-internal/parser/src/prompt.rs @@ -86,6 +86,7 @@ impl Section { } /// Classic leading Lua fence when the first block is Lua. + #[cfg(any(test, feature = "test-support"))] #[must_use] pub fn prologue(&self) -> Option<&LuaProgram> { match self.blocks.first() { @@ -95,6 +96,7 @@ impl Section { } /// Text of the last prose block, or `""` when the section has none. + #[cfg(any(test, feature = "test-support"))] #[must_use] pub fn prose(&self) -> &str { self.blocks @@ -109,6 +111,7 @@ impl Section { /// Classic trailing Lua fence when the last block is Lua and not the sole /// leading prologue (a section that is only one Lua block has no epilog). + #[cfg(any(test, feature = "test-support"))] #[must_use] pub fn epilog(&self) -> Option<&LuaProgram> { match self.blocks.as_slice() { From 26280881c427b539391729a29d4996d22b282a09 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 1 Nov 2026 06:05:30 -0800 Subject: [PATCH 05/14] Keep the review principle and drop the surface rules Remove the Surfaces section. Its rules restated the review principle as seven hard rules that a scan cannot check, and two of them collided on the same item. - The principle stays in Principles: a reviewer reads one crate at a time, and its code, its surface, and the items it names stay small. --- AGENTS.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1da426318..233b09ec9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -70,17 +70,6 @@ Seven terms have exactly one meaning each, everywhere in this repository: code c - Temporary directory names come from the test's name, the process id, and a counter. - A module with more than one file keeps its files in a directory named after it, in the standard module layout (`foo.rs` beside `foo/bar.rs`). A directory may hold any number of files, one included, and a module's unit tests are one of them (`foo/tests.rs`). Never move files between a flat sibling form and a directory because of how many files a group holds, and never change a group's layout only to bring it in line with this rule. Existing `foo-bar.rs` siblings wired with `#[path = "foo-bar.rs"] mod bar;` stay as they are, and new code adds no path attribute for layout. Top-level `tests/` and `benches/` trees follow Cargo target conventions. -## Surfaces - -Test code is `#[cfg(test)]` code, `tests/`, `benches/`, `examples/`, and code gated on the `test-support` or `test-fixtures` feature. For every item in the surface of a crate directly under `crates/`, re-exported items included, a program outside this repository counts as another crate. Add no violation of these rules in a change, and fix an existing violation in a change of its own, so each review covers one purpose. - -- Make an item `pub` only when another crate names it, and gate it behind `test-support` or `test-fixtures` when every crate that names it does so in test code, adding `test-support` to a crate that has neither. Leave `pub` an item that only this crate's own `tests/` names. Make every other item `pub(crate)` or private, and delete what the `dead_code` lint then reports. -- Add no second reader of a field to the surface; a dependent that needs the value in another form derives it at its call site. A reader is the field itself when it is `pub`, or a function whose only parameter is the value holding the field, by value or by reference, and whose result depends on that field alone. Trait impls, and functions a workspace lint requires beside a reader, are not readers. -- Re-export another crate's item only when a rule that `cargo test -p build-xtask` enforces forbids the dependent that needs it from depending on the crate that defines it. -- Remove a dependency that no code in the crate names, and move a dependency to `[dev-dependencies]` when only `#[cfg(test)]` code, `tests/`, `benches/`, or `examples/` names it. Keep a dependency that only sets another dependency's features, with a comment that says so. -- Put new items in a new crate when a crate that will name them names nothing else in the crate you meant to extend. Extend that crate instead when the two would depend on each other. Treat a new crate as a smaller change than widening an existing crate's surface. -- Remove an edge between two crates by moving items, not by merging the crates, because a merge deletes the boundary review relies on. - ## SPA and CSS Rules - No raw color, size, or spacing values in Workshop UI component CSS; use the custom properties defined in `@workshop/look` and `ui/src/tokens/component.css`. From 099a69937459c4ac1cbd307686b4865bbe7f8eb0 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 1 Nov 2026 06:16:43 -0800 Subject: [PATCH 06/14] Rename `runvil-parser` to `runvil-prompt` The crate loads a prompt and compiles every Lua fence at load time, so a loaded `Prompt` is ready to run. The name now matches that, and matches the `runvil::prompt` facade module it backs. - The crate directory, package name, and `runvil_prompt` identifier change; the Engine's `crate::parser` import module becomes `crate::prompt`. - Crate docs, the crate `README.md`, and four Engine doc lines that called this step "the parser" now describe loading rather than parsing. - `public-api.txt` is unchanged: the facade re-exports the same items under the same paths. --- Cargo.lock | 22 ++++++++--------- Cargo.toml | 4 ++-- .../src/product/container_tests.rs | 4 ++-- crates/build-xtask/src/product/tests.rs | 4 ++-- crates/build-xtask/src/tidy-wiring.rs | 2 +- crates/runvil-internal/engine/Cargo.toml | 4 ++-- .../engine/benches/models_loop.rs | 2 +- crates/runvil-internal/engine/src/error.rs | 8 +++---- .../engine/src/error/convert.rs | 16 ++++++------- .../engine/src/error/tests-chain.rs | 2 +- .../runvil-internal/engine/src/error/tests.rs | 2 +- .../engine/src/execute/context-bound.rs | 2 +- .../engine/src/execute/context-tests.rs | 2 +- .../engine/src/execute/context.rs | 4 ++-- .../engine/src/execute/fill.rs | 2 +- .../engine/src/execute/requirements-tests.rs | 2 +- .../engine/src/execute/requirements.rs | 2 +- .../runvil-internal/engine/src/execute/run.rs | 2 +- .../engine/src/execute/run/tests-drops.rs | 2 +- .../engine/src/execute/scheduler.rs | 2 +- .../engine/src/execute/scheduler/chain_ops.rs | 2 +- .../src/execute/scheduler/chain_record.rs | 4 ++-- .../execute/scheduler/chain_record/arena.rs | 2 +- .../engine/src/execute/scheduler/drive.rs | 6 ++--- .../engine/src/execute/scheduler/h1.rs | 4 ++-- .../engine/src/execute/scheduler/step.rs | 2 +- .../engine/src/execute/scheduler/walk.rs | 4 ++-- .../src/execute/section_context-construct.rs | 2 +- .../engine/src/execute/tests.rs | 8 +++---- .../engine/src/execute/tests/adhoc_tools.rs | 2 +- .../engine/src/execute/tests/batch_turn.rs | 2 +- .../engine/src/execute/tests/chat_arm.rs | 2 +- .../src/execute/tests/chat_record_rebuild.rs | 2 +- .../engine/src/execute/tests/chat_scope.rs | 2 +- .../src/execute/tests/compaction-rules.rs | 2 +- .../engine/src/execute/tests/compaction.rs | 2 +- .../engine/src/execute/tests/context.rs | 2 +- .../tests/debug_and_counts-infer-rounds.rs | 2 +- .../src/execute/tests/debug_and_counts.rs | 2 +- .../engine/src/execute/tests/discovery.rs | 2 +- .../engine/src/execute/tests/effects.rs | 2 +- .../engine/src/execute/tests/event_vendors.rs | 2 +- .../engine/src/execute/tests/exec_flow.rs | 2 +- .../engine/src/execute/tests/exit_rules.rs | 2 +- .../engine/src/execute/tests/full_id_calls.rs | 2 +- .../engine/src/execute/tests/live_infer.rs | 2 +- .../execute/tests/model_and_reply-globals.rs | 2 +- .../src/execute/tests/model_and_reply.rs | 4 ++-- .../src/execute/tests/models_compact.rs | 2 +- .../execute/tests/models_loop-arguments.rs | 2 +- .../tests/models_loop-author-shapes.rs | 2 +- .../engine/src/execute/tests/models_loop.rs | 2 +- .../execute/tests/models_loop_compactors.rs | 2 +- .../tests/models_loop_contract-rounds.rs | 2 +- .../src/execute/tests/models_loop_contract.rs | 2 +- .../engine/src/execute/tests/observations.rs | 2 +- .../engine/src/execute/tests/offering.rs | 2 +- .../src/execute/tests/parallel_tool_calls.rs | 2 +- .../src/execute/tests/precheck_anchor.rs | 2 +- .../engine/src/execute/tests/render_effect.rs | 2 +- .../src/execute/tests/rendered_tools.rs | 2 +- .../engine/src/execute/tests/rounds.rs | 2 +- .../src/execute/tests/section_messages.rs | 2 +- .../engine/src/execute/tests/suite.rs | 2 +- .../src/execute/tests/suite/args_surface.rs | 2 +- .../src/execute/tests/suite/exec_flow.rs | 4 ++-- .../tests/suite/exec_flow/run_setup.rs | 2 +- .../engine/src/execute/tests/suite/fanout.rs | 2 +- .../execute/tests/suite/global_metatable.rs | 2 +- .../src/execute/tests/suite/lazy_prose.rs | 2 +- .../engine/src/execute/tests/suite/parsing.rs | 4 ++-- .../engine/src/execute/tests/suite/prepare.rs | 4 ++-- .../engine/src/execute/tests/suite/require.rs | 2 +- .../engine/src/execute/tests/suite/vfs.rs | 4 ++-- .../src/execute/tests/tasks-chain-end.rs | 2 +- .../tests/tool_call_arm-adhoc-handlers.rs | 2 +- .../engine/src/execute/tests/tool_call_arm.rs | 2 +- .../src/execute/tests/tool_call_origins.rs | 2 +- .../engine/src/execute/tests/tool_listings.rs | 2 +- .../engine/src/execute/tests/tool_loop.rs | 2 +- .../engine/src/execute/tests/tool_scoping.rs | 2 +- .../src/execute/tests/unified_pipeline.rs | 2 +- .../engine/src/execute/walk_target-tests.rs | 2 +- .../engine/src/execute/walk_target.rs | 2 +- .../engine/src/heading_address-tests.rs | 20 ++++++++-------- .../engine/src/heading_address.rs | 2 +- crates/runvil-internal/engine/src/lib.rs | 4 ++-- .../runvil-internal/engine/src/lua/tests.rs | 2 +- crates/runvil-internal/engine/src/parser.rs | 23 ------------------ crates/runvil-internal/engine/src/prompt.rs | 24 +++++++++++++++++++ .../engine/src/test_support/scenario.rs | 2 +- crates/runvil-internal/lua/AGENTS.md | 2 +- crates/runvil-internal/parser/README.md | 3 --- .../{parser => prompt}/Cargo.toml | 2 +- crates/runvil-internal/prompt/README.md | 3 +++ .../{parser => prompt}/build.rs | 0 .../{parser => prompt}/src/body_text-tests.rs | 0 .../{parser => prompt}/src/body_text.rs | 0 .../{parser => prompt}/src/contract.rs | 0 .../{parser => prompt}/src/contract/args.rs | 0 .../{parser => prompt}/src/contract/models.rs | 0 .../src/contract/tests-packs.rs | 0 .../{parser => prompt}/src/contract/tests.rs | 0 .../{parser => prompt}/src/detail.rs | 0 .../{parser => prompt}/src/error-tests.rs | 0 .../{parser => prompt}/src/error.rs | 0 .../{parser => prompt}/src/fence.rs | 0 .../{parser => prompt}/src/frontmatter.rs | 0 .../{parser => prompt}/src/lib.rs | 5 ++-- .../{parser => prompt}/src/list.rs | 0 .../{parser => prompt}/src/parse.rs | 0 .../{parser => prompt}/src/prompt.rs | 0 .../{parser => prompt}/src/sections-tests.rs | 0 .../{parser => prompt}/src/sections.rs | 0 .../{parser => prompt}/src/test_support.rs | 0 .../{parser => prompt}/src/tests.rs | 0 .../{parser => prompt}/src/tests/breaks.rs | 0 .../src/tests/frontmatter.rs | 0 .../{parser => prompt}/src/tests/h1_fences.rs | 0 .../{parser => prompt}/src/tests/headings.rs | 0 .../src/tests/line_mapping.rs | 0 .../{parser => prompt}/src/tests/lists.rs | 0 .../src/tests/section_fences.rs | 0 crates/runvil/Cargo.toml | 2 +- crates/runvil/src/lib.rs | 20 ++++++++-------- 125 files changed, 180 insertions(+), 178 deletions(-) delete mode 100644 crates/runvil-internal/engine/src/parser.rs create mode 100644 crates/runvil-internal/engine/src/prompt.rs delete mode 100644 crates/runvil-internal/parser/README.md rename crates/runvil-internal/{parser => prompt}/Cargo.toml (97%) create mode 100644 crates/runvil-internal/prompt/README.md rename crates/runvil-internal/{parser => prompt}/build.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/body_text-tests.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/body_text.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/contract.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/contract/args.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/contract/models.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/contract/tests-packs.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/contract/tests.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/detail.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/error-tests.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/error.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/fence.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/frontmatter.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/lib.rs (87%) rename crates/runvil-internal/{parser => prompt}/src/list.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/parse.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/prompt.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/sections-tests.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/sections.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/test_support.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/tests.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/tests/breaks.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/tests/frontmatter.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/tests/h1_fences.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/tests/headings.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/tests/line_mapping.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/tests/lists.rs (100%) rename crates/runvil-internal/{parser => prompt}/src/tests/section_fences.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 10685000d..08a967d28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4735,7 +4735,7 @@ dependencies = [ "runvil-engine", "runvil-lua", "runvil-model-client", - "runvil-parser", + "runvil-prompt", "runvil-types", "runvil-vfs", "serde_json", @@ -4751,7 +4751,7 @@ dependencies = [ "regex", "runvil-lua", "runvil-model-client", - "runvil-parser", + "runvil-prompt", "runvil-types", "runvil-vfs", "serde", @@ -4786,27 +4786,27 @@ dependencies = [ ] [[package]] -name = "runvil-parser" +name = "runvil-plugin" version = "0.3.0" dependencies = [ "build-ceiling", - "mlua", - "pulldown-cmark", - "runvil-lua", "runvil-types", - "serde", - "serde_yaml_ng", + "runvil-vfs", + "serde_json", "thiserror 2.0.19", ] [[package]] -name = "runvil-plugin" +name = "runvil-prompt" version = "0.3.0" dependencies = [ "build-ceiling", + "mlua", + "pulldown-cmark", + "runvil-lua", "runvil-types", - "runvil-vfs", - "serde_json", + "serde", + "serde_yaml_ng", "thiserror 2.0.19", ] diff --git a/Cargo.toml b/Cargo.toml index 845468f64..bdd54f53d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "3" -members = ["crates/*", "crates/runvil-internal/types", "crates/runvil-internal/engine", "crates/runvil-internal/lua", "crates/runvil-internal/parser", "crates/runvil-internal/vfs", "crates/runvil-internal/model-client", "crates/workshop/desktop", "crates/workshop/server", "crates/workshop/server-api", "crates/workshop/gateway", "crates/workshop/gateway-discovery", "crates/workshop/menu", "crates/workshop/protocol", "crates/workshop/registry", "crates/workshop/status", "crates/workshop/support", "crates/workshop/user-state", "crates/workshop/workspace", "crates/workshop/run-log", "crates/workshop/agents", "crates/harness-internal/runner", "crates/harness-internal/plugin-loader", "crates/harness-internal/test-plugin"] +members = ["crates/*", "crates/runvil-internal/types", "crates/runvil-internal/engine", "crates/runvil-internal/lua", "crates/runvil-internal/prompt", "crates/runvil-internal/vfs", "crates/runvil-internal/model-client", "crates/workshop/desktop", "crates/workshop/server", "crates/workshop/server-api", "crates/workshop/gateway", "crates/workshop/gateway-discovery", "crates/workshop/menu", "crates/workshop/protocol", "crates/workshop/registry", "crates/workshop/status", "crates/workshop/support", "crates/workshop/user-state", "crates/workshop/workspace", "crates/workshop/run-log", "crates/workshop/agents", "crates/harness-internal/runner", "crates/harness-internal/plugin-loader", "crates/harness-internal/test-plugin"] # crates/runvil-internal, crates/workshop, and crates/harness-internal # are manifestless containers holding their families' private crates. # Cargo prunes excluded subtrees from member globs, so a "crates/workshop/*" @@ -41,7 +41,7 @@ plugin-std = { path = "crates/plugin-std" } plugin-mcp = { path = "crates/plugin-mcp" } runvil-lua = { path = "crates/runvil-internal/lua" } runvil-model-client = { path = "crates/runvil-internal/model-client" } -runvil-parser = { path = "crates/runvil-internal/parser" } +runvil-prompt = { path = "crates/runvil-internal/prompt" } runvil-vfs = { path = "crates/runvil-internal/vfs" } workshop-server = { path = "crates/workshop/server" } workshop-server-api = { path = "crates/workshop/server-api" } diff --git a/crates/build-xtask/src/product/container_tests.rs b/crates/build-xtask/src/product/container_tests.rs index 9c2359144..8ad211ffd 100644 --- a/crates/build-xtask/src/product/container_tests.rs +++ b/crates/build-xtask/src/product/container_tests.rs @@ -29,8 +29,8 @@ fn a_container_crate_depending_on_a_container_sibling_passes() { let root = tempfile::TempDir::new().expect("tempdir"); write_crate( root.path(), - "runvil-internal/parser", - "runvil-parser", + "runvil-internal/prompt", + "runvil-prompt", "[dependencies]\nrunvil-lua = { path = \"../lua\" }\n", ); write_crate(root.path(), "runvil-internal/lua", "runvil-lua", ""); diff --git a/crates/build-xtask/src/product/tests.rs b/crates/build-xtask/src/product/tests.rs index e185672dd..7c826572e 100644 --- a/crates/build-xtask/src/product/tests.rs +++ b/crates/build-xtask/src/product/tests.rs @@ -230,10 +230,10 @@ fn dev_build_and_target_dependencies_are_checked() { root.path(), "workshop-server", "workshop-server", - "[dev-dependencies]\nrunvil-parser = { path = \"../runvil-parser\" }\n\ + "[dev-dependencies]\nrunvil-prompt = { path = \"../runvil-prompt\" }\n\ [target.'cfg(windows)'.dependencies]\nharness-runner = { path = \"../harness-runner\" }\n", ); - write_crate(root.path(), "runvil-parser", "runvil-parser", ""); + write_crate(root.path(), "runvil-prompt", "runvil-prompt", ""); write_crate(root.path(), "harness-runner", "harness-runner", ""); let violations = product_boundary_violations(root.path()); assert_eq!( diff --git a/crates/build-xtask/src/tidy-wiring.rs b/crates/build-xtask/src/tidy-wiring.rs index ddc0e4b93..772d31e73 100644 --- a/crates/build-xtask/src/tidy-wiring.rs +++ b/crates/build-xtask/src/tidy-wiring.rs @@ -254,7 +254,7 @@ mod tests { fn a_crate_with_no_package_root_build_script_fails_even_with_a_src_build_module() { let root = tempfile::TempDir::new().expect("tempdir"); write_wired(root.path()); - let name = "runvil-parser"; + let name = "runvil-prompt"; let parser = write_built(root.path(), "parser", name, BUILD_DEPENDENCY, None); let module = parser.join("src").join("build.rs"); std::fs::write(module, CALLS_CHECK).expect("the module writes"); diff --git a/crates/runvil-internal/engine/Cargo.toml b/crates/runvil-internal/engine/Cargo.toml index a43fccf68..9a9738fb5 100644 --- a/crates/runvil-internal/engine/Cargo.toml +++ b/crates/runvil-internal/engine/Cargo.toml @@ -10,7 +10,7 @@ publish = false [dependencies] runvil-lua.workspace = true runvil-model-client.workspace = true -runvil-parser.workspace = true +runvil-prompt.workspace = true runvil-types.workspace = true runvil-vfs.workspace = true regex.workspace = true @@ -31,7 +31,7 @@ test-support = [] [dev-dependencies] criterion.workspace = true runvil-lua = { workspace = true, features = ["test-support"] } -runvil-parser = { workspace = true, features = ["test-support"] } +runvil-prompt = { workspace = true, features = ["test-support"] } # The bench drives runs on the serial test driver and answers their rounds # from the scripted model, so it needs the feature that provides both. diff --git a/crates/runvil-internal/engine/benches/models_loop.rs b/crates/runvil-internal/engine/benches/models_loop.rs index 26c558b1b..796c61382 100644 --- a/crates/runvil-internal/engine/benches/models_loop.rs +++ b/crates/runvil-internal/engine/benches/models_loop.rs @@ -27,7 +27,7 @@ use criterion::{Criterion, criterion_group, criterion_main}; use runvil_engine::test_support::scripted_chat::{ScriptedChat, ScriptedReply}; use runvil_engine::test_support::{drive, perform_locally}; use runvil_engine::{Effect, EffectAnswer, Run, RunConfig, RunOutcome}; -use runvil_parser::Prompt; +use runvil_prompt::Prompt; use runvil_types::models::{ModelCatalog, ModelId, ModelInfo, ModelRole, ThinkingMode}; use runvil_types::seed::RunSeed; use runvil_types::timestamp::Timestamp; diff --git a/crates/runvil-internal/engine/src/error.rs b/crates/runvil-internal/engine/src/error.rs index 04cc36ba6..3a0c5b316 100644 --- a/crates/runvil-internal/engine/src/error.rs +++ b/crates/runvil-internal/engine/src/error.rs @@ -3,7 +3,7 @@ //! [`Error`] is a `pub(crate)` internal error type, never part of the public API. //! A run it ends reports its kind and message in //! [`RunOutcome::Failed`](crate::RunOutcome::Failed), and -//! [`ParseError`](crate::parser::ParseError), +//! [`ParseError`](crate::prompt::ParseError), //! [`CompletionError`](crate::model::CompletionError), and //! [`runvil_types::tools::ToolError`] each reach that outcome through this //! type, which classifies them and keeps their causes. @@ -18,7 +18,7 @@ //! of their own: `Parse`, `Completion`, and `Substitution` hand their whole //! `Display` and `source` to the error they hold. That is the exception to //! the field rule above, and it is why `Error::originating_error` answers -//! `None` for `Completion` and `Substitution` and the parser's own cause for +//! `None` for `Completion` and `Substitution` and the loader's own cause for //! `Parse`. use runvil_types::ids::TaskId; @@ -115,12 +115,12 @@ pub(crate) enum Error { /// The prompt failed to parse: invalid frontmatter YAML or a /// structurally-classified failure. /// - /// Holds the parser's [`ParseError`](crate::parser::ParseError) whole, so + /// Holds the loader's [`ParseError`](crate::prompt::ParseError) whole, so /// its stable kind, its location fields, and the YAML decode failure a /// frontmatter failure keeps as its cause survive until the run's /// failure message is built from them. #[error(transparent)] - Parse(crate::parser::ParseError), + Parse(crate::prompt::ParseError), /// A model round or a catalog fetch failed, as the broker that performed /// it reported. diff --git a/crates/runvil-internal/engine/src/error/convert.rs b/crates/runvil-internal/engine/src/error/convert.rs index d41ee117c..78f1e59fa 100644 --- a/crates/runvil-internal/engine/src/error/convert.rs +++ b/crates/runvil-internal/engine/src/error/convert.rs @@ -6,7 +6,7 @@ use std::borrow::Cow; use runvil_lua::Error as LuaError; -use runvil_parser::Error as ParserError; +use runvil_prompt::Error as ParserError; use runvil_types::ids::TaskId; use runvil_vfs::VfsError; @@ -14,8 +14,8 @@ use super::{BoxedSource, Error}; impl Error { /// Builds a parse failure with a stable classification and no source span. - pub(crate) fn parse(kind: crate::parser::ParseErrorKind, message: impl Into) -> Error { - Error::Parse(crate::parser::detail::parse_error(kind, message)) + pub(crate) fn parse(kind: crate::prompt::ParseErrorKind, message: impl Into) -> Error { + Error::Parse(crate::prompt::detail::parse_error(kind, message)) } /// Stamps the prompt's frontmatter name onto a parse failure raised by @@ -25,7 +25,7 @@ impl Error { pub(crate) fn with_prompt_name(self, name: &str) -> Error { match self { Error::Parse(error) => { - Error::Parse(crate::parser::detail::with_prompt_name(error, name)) + Error::Parse(crate::prompt::detail::with_prompt_name(error, name)) } other => other, } @@ -134,12 +134,12 @@ impl From for Error { /// internal failure maps onto its own variant. The parser crate's error /// type is not `#[non_exhaustive]` (the two crates version together), so /// this match is total. -impl From for Error { - fn from(error: crate::parser::ParseError) -> Self { - match crate::parser::detail::parse_error_into_inner(error) { +impl From for Error { + fn from(error: crate::prompt::ParseError) -> Self { + match crate::prompt::detail::parse_error_into_inner(error) { inner @ (ParserError::ParseFrontmatter { .. } | ParserError::ParseStructured { .. }) => { - Error::Parse(crate::parser::detail::parse_error_from_inner(inner)) + Error::Parse(crate::prompt::detail::parse_error_from_inner(inner)) } ParserError::Lua(lua) => Error::from(lua), ParserError::Internal(message) => Error::internal(message), diff --git a/crates/runvil-internal/engine/src/error/tests-chain.rs b/crates/runvil-internal/engine/src/error/tests-chain.rs index 2be53be4a..daa7902c6 100644 --- a/crates/runvil-internal/engine/src/error/tests-chain.rs +++ b/crates/runvil-internal/engine/src/error/tests-chain.rs @@ -10,7 +10,7 @@ use runvil_types::tools::{ToolError, ToolErrorKind}; use runvil_vfs::VfsError; use super::*; -use crate::parser::Prompt; +use crate::prompt::Prompt; /// The display text of each segment of `error`'s source chain, the error /// itself first. diff --git a/crates/runvil-internal/engine/src/error/tests.rs b/crates/runvil-internal/engine/src/error/tests.rs index 6cdbe787f..b5e70d210 100644 --- a/crates/runvil-internal/engine/src/error/tests.rs +++ b/crates/runvil-internal/engine/src/error/tests.rs @@ -5,7 +5,7 @@ use runvil_lua::Error as LuaError; use super::*; -use crate::parser::Prompt; +use crate::prompt::Prompt; #[path = "tests-chain.rs"] mod chain; diff --git a/crates/runvil-internal/engine/src/execute/context-bound.rs b/crates/runvil-internal/engine/src/execute/context-bound.rs index 761462d90..1e992f45e 100644 --- a/crates/runvil-internal/engine/src/execute/context-bound.rs +++ b/crates/runvil-internal/engine/src/execute/context-bound.rs @@ -11,7 +11,7 @@ use runvil_types::packs::PackId; use runvil_types::tools::ToolCatalog; use crate::lua::{RosterEntry, ToolSet}; -use crate::parser::Prompt; +use crate::prompt::Prompt; /// Builds the run's shared tool set: the frontmatter's declared Packs, /// and the run catalog and roster, all moved out of the config. Nothing diff --git a/crates/runvil-internal/engine/src/execute/context-tests.rs b/crates/runvil-internal/engine/src/execute/context-tests.rs index ef121ffca..08b95043d 100644 --- a/crates/runvil-internal/engine/src/execute/context-tests.rs +++ b/crates/runvil-internal/engine/src/execute/context-tests.rs @@ -69,7 +69,7 @@ fn derived_values_come_from_the_prompt_and_limits() { let ctx = test_state(&prompt); assert_eq!( ctx.section_count(), - runvil_parser::detail::sections(&prompt).len() + runvil_prompt::detail::sections(&prompt).len() ); assert_eq!(ctx.max_tool_iterations(), 24); } diff --git a/crates/runvil-internal/engine/src/execute/context.rs b/crates/runvil-internal/engine/src/execute/context.rs index 4ce2c168d..cfb8aef9e 100644 --- a/crates/runvil-internal/engine/src/execute/context.rs +++ b/crates/runvil-internal/engine/src/execute/context.rs @@ -23,7 +23,7 @@ use runvil_vfs::{Access, VfsRef}; use crate::lua::{Argv, ToolSet}; use crate::model::{ModelBinding, ModelRole, ModelSet}; -use crate::parser::Prompt; +use crate::prompt::Prompt; use super::config::{RunConfig, RunLimits}; use super::fill; @@ -345,7 +345,7 @@ impl ChainState { /// The run's top-level section count, reported as `sys.section_count`. fn section_count(&self) -> usize { - crate::parser::detail::sections(&self.shared.prompt).len() + crate::prompt::detail::sections(&self.shared.prompt).len() } /// The H1-to-walk handoff: the `argv` H1 left behind at the freeze, diff --git a/crates/runvil-internal/engine/src/execute/fill.rs b/crates/runvil-internal/engine/src/execute/fill.rs index 5db6f4837..b33bf9b03 100644 --- a/crates/runvil-internal/engine/src/execute/fill.rs +++ b/crates/runvil-internal/engine/src/execute/fill.rs @@ -4,7 +4,7 @@ use std::collections::BTreeMap; use crate::model::{ModelBinding, ModelInfo, ModelInvocation, ModelRole, ModelSet, ThinkingMode}; -use crate::parser::{Prompt, RoleRequirements}; +use crate::prompt::{Prompt, RoleRequirements}; use super::requirements::{RequirementCheck, Requirements, UnmetRequirement}; diff --git a/crates/runvil-internal/engine/src/execute/requirements-tests.rs b/crates/runvil-internal/engine/src/execute/requirements-tests.rs index 16fe40e23..07043fb08 100644 --- a/crates/runvil-internal/engine/src/execute/requirements-tests.rs +++ b/crates/runvil-internal/engine/src/execute/requirements-tests.rs @@ -14,7 +14,7 @@ use serde_json::json; use crate::execute::{Run, RunConfig, RunErrorKind, RunOutcome, Step}; use crate::lua::RosterEntry; -use crate::parser::Prompt; +use crate::prompt::Prompt; use crate::test_support::scenario::{TEST_SEED, test_model_with}; /// The notice line of a run whose catalog or roster names the reserved diff --git a/crates/runvil-internal/engine/src/execute/requirements.rs b/crates/runvil-internal/engine/src/execute/requirements.rs index 56267e644..0bb2661d7 100644 --- a/crates/runvil-internal/engine/src/execute/requirements.rs +++ b/crates/runvil-internal/engine/src/execute/requirements.rs @@ -8,7 +8,7 @@ use runvil_types::packs::PackId; use runvil_types::tools::ToolCatalog; use crate::lua::RosterEntry; -use crate::parser::Prompt; +use crate::prompt::Prompt; #[cfg(test)] #[path = "requirements-tests.rs"] diff --git a/crates/runvil-internal/engine/src/execute/run.rs b/crates/runvil-internal/engine/src/execute/run.rs index 60a9e8a8b..4cf94a28f 100644 --- a/crates/runvil-internal/engine/src/execute/run.rs +++ b/crates/runvil-internal/engine/src/execute/run.rs @@ -22,7 +22,7 @@ mod effect; pub(crate) use effect::{AnswerRecord, ChatAnswerRecord, EffectRecord}; pub use effect::{Effect, EffectAnswer, EffectId, PackToolSets, Round, ToolCallOrigin, ToolCaller}; -use crate::parser::Prompt; +use crate::prompt::Prompt; use super::RunOutcome; use super::config::RunConfig; diff --git a/crates/runvil-internal/engine/src/execute/run/tests-drops.rs b/crates/runvil-internal/engine/src/execute/run/tests-drops.rs index 6178f3604..11fd17862 100644 --- a/crates/runvil-internal/engine/src/execute/run/tests-drops.rs +++ b/crates/runvil-internal/engine/src/execute/run/tests-drops.rs @@ -22,7 +22,7 @@ use crate::execute::run::{ AnswerRecord, Effect, EffectAnswer, EffectId, EffectRecord, Round, Run, Step, }; use crate::model::{Completion, CompletionResult}; -use crate::parser::Prompt; +use crate::prompt::Prompt; use crate::test_support::scenario::{chat_answer, only_effect, test_model_with}; use crate::test_support::scripted_chat::text; diff --git a/crates/runvil-internal/engine/src/execute/scheduler.rs b/crates/runvil-internal/engine/src/execute/scheduler.rs index 193a62ea5..607448144 100644 --- a/crates/runvil-internal/engine/src/execute/scheduler.rs +++ b/crates/runvil-internal/engine/src/execute/scheduler.rs @@ -95,7 +95,7 @@ use runvil_vfs::Origin; use runvil_vfs::detail::ScopeHandle; use crate::lua::Answer; -use crate::parser::{Block, Prompt}; +use crate::prompt::{Block, Prompt}; use crate::{Error, Result}; use super::context::ChainState; diff --git a/crates/runvil-internal/engine/src/execute/scheduler/chain_ops.rs b/crates/runvil-internal/engine/src/execute/scheduler/chain_ops.rs index 8470efefb..c6e66b7b1 100644 --- a/crates/runvil-internal/engine/src/execute/scheduler/chain_ops.rs +++ b/crates/runvil-internal/engine/src/execute/scheduler/chain_ops.rs @@ -16,7 +16,7 @@ use runvil_types::event::ReplyOrigin; use crate::execute::run::{Effect, EffectId, Round}; use crate::lua::Answer; -use crate::parser::Prompt; +use crate::prompt::Prompt; use crate::{Error, Result}; use super::{Chain, ChainIndex, Continuation, EffectTable, Scheduler}; diff --git a/crates/runvil-internal/engine/src/execute/scheduler/chain_record.rs b/crates/runvil-internal/engine/src/execute/scheduler/chain_record.rs index cc6a42b9e..ad2e2d3ee 100644 --- a/crates/runvil-internal/engine/src/execute/scheduler/chain_record.rs +++ b/crates/runvil-internal/engine/src/execute/scheduler/chain_record.rs @@ -17,7 +17,7 @@ use crate::execute::section_context::{SectionContext, TaskSeed}; use crate::execute::walk_target::JumpTarget; use crate::heading_address::SectionPos; use crate::lua::{Answer, Blocked}; -use crate::parser::{Block, BodyText, Prompt, Section}; +use crate::prompt::{Block, BodyText, Prompt, Section}; use crate::{Error, Result}; mod anchor; @@ -474,7 +474,7 @@ impl Chain { /// result outlives a mutable borrow of the chain. pub(super) fn blocks<'p>(&self, prompt: &'p Prompt) -> &'p [Block] { if self.kind.is_h1() { - crate::parser::detail::h1_blocks(prompt) + crate::prompt::detail::h1_blocks(prompt) } else { self.walk .current() diff --git a/crates/runvil-internal/engine/src/execute/scheduler/chain_record/arena.rs b/crates/runvil-internal/engine/src/execute/scheduler/chain_record/arena.rs index 52b5434f2..84f578174 100644 --- a/crates/runvil-internal/engine/src/execute/scheduler/chain_record/arena.rs +++ b/crates/runvil-internal/engine/src/execute/scheduler/chain_record/arena.rs @@ -54,7 +54,7 @@ impl Chain { use crate::execute::context::ChainState; use crate::execute::section_context::TaskSeed; use crate::heading_address::{SectionIndex, SectionPos}; - use crate::parser::Prompt; + use crate::prompt::Prompt; use crate::test_support::scenario::{TEST_SEED, TEST_STARTED_AT}; let source = concat!( diff --git a/crates/runvil-internal/engine/src/execute/scheduler/drive.rs b/crates/runvil-internal/engine/src/execute/scheduler/drive.rs index 16082bff2..927715ac1 100644 --- a/crates/runvil-internal/engine/src/execute/scheduler/drive.rs +++ b/crates/runvil-internal/engine/src/execute/scheduler/drive.rs @@ -15,7 +15,7 @@ use runvil_vfs::Origin; use crate::execute::RunOutcome; use crate::execute::run::{EffectAnswer, EffectId, Step}; -use crate::parser::ParseErrorKind; +use crate::prompt::ParseErrorKind; use crate::{Error, Result}; use super::chain::ChainStep; @@ -167,8 +167,8 @@ impl Scheduler { /// with the generic completion. fn start(&mut self) -> Result<()> { let prompt = self.prompt(); - if crate::parser::detail::h1_blocks(&prompt).is_empty() { - if crate::parser::detail::sections(&prompt).is_empty() { + if crate::prompt::detail::h1_blocks(&prompt).is_empty() { + if crate::prompt::detail::sections(&prompt).is_empty() { self.end(Ok(GENERIC_COMPLETION.to_owned())); return Ok(()); } diff --git a/crates/runvil-internal/engine/src/execute/scheduler/h1.rs b/crates/runvil-internal/engine/src/execute/scheduler/h1.rs index 1c1df706f..775f95977 100644 --- a/crates/runvil-internal/engine/src/execute/scheduler/h1.rs +++ b/crates/runvil-internal/engine/src/execute/scheduler/h1.rs @@ -40,7 +40,7 @@ impl Scheduler { let origin = prompt_origin( self.ctx.prompt(), self.ctx.prompt().title(), - crate::parser::detail::h1_blocks(self.ctx.prompt()), + crate::prompt::detail::h1_blocks(self.ctx.prompt()), ); let access = self.ctx.vfs().acquire(origin).map_err(Error::store)?; self.scope = Some(runvil_vfs::detail::scope_handle(&access)); @@ -99,7 +99,7 @@ impl Scheduler { let concurrency = chain.concurrency; // The walk starts on the model the pass ended on. let model = chain.current_model()?; - if crate::parser::detail::sections(self.ctx.prompt()).is_empty() { + if crate::prompt::detail::sections(self.ctx.prompt()).is_empty() { // No walk follows, so the pass's end is the run's end: a task // the pass spawned and left live ends here under the same // rules as a finishing chain. diff --git a/crates/runvil-internal/engine/src/execute/scheduler/step.rs b/crates/runvil-internal/engine/src/execute/scheduler/step.rs index b0809adc6..fce158ebe 100644 --- a/crates/runvil-internal/engine/src/execute/scheduler/step.rs +++ b/crates/runvil-internal/engine/src/execute/scheduler/step.rs @@ -10,7 +10,7 @@ use runvil_types::event::lifecycle; use crate::execute::requirements::NOTICE_HEADER; use crate::heading_address::{SectionIndex, SectionPos}; use crate::lua::{Answer, CoroStep, LuaBlockResult, Request, YieldParse}; -use crate::parser::{Block, BodyText}; +use crate::prompt::{Block, BodyText}; use crate::{Error, Result}; use super::chain::ChainStep; diff --git a/crates/runvil-internal/engine/src/execute/scheduler/walk.rs b/crates/runvil-internal/engine/src/execute/scheduler/walk.rs index 43cefe92a..6e7b35af9 100644 --- a/crates/runvil-internal/engine/src/execute/scheduler/walk.rs +++ b/crates/runvil-internal/engine/src/execute/scheduler/walk.rs @@ -17,7 +17,7 @@ use crate::execute::walk_target::{ JumpTarget, h1_visible_sections, resolve_jump_target, section_position, }; use crate::heading_address::{self, SectionIndex, SectionPos, Visible}; -use crate::parser::Block; +use crate::prompt::Block; use crate::{Error, Result}; use super::chain::ChainStep; @@ -48,7 +48,7 @@ impl Scheduler { // where the walk starts. let prompt = self.ctx.prompt(); let blocks: &[Block] = - crate::parser::detail::entry(prompt).map_or(&[], |section| section.blocks()); + crate::prompt::detail::entry(prompt).map_or(&[], |section| section.blocks()); let origin = prompt_origin(prompt, prompt.title(), blocks); let access = self.ctx.vfs().acquire(origin).map_err(Error::store)?; self.scope = Some(runvil_vfs::detail::scope_handle(&access)); diff --git a/crates/runvil-internal/engine/src/execute/section_context-construct.rs b/crates/runvil-internal/engine/src/execute/section_context-construct.rs index cd8f1720a..c293fbb6e 100644 --- a/crates/runvil-internal/engine/src/execute/section_context-construct.rs +++ b/crates/runvil-internal/engine/src/execute/section_context-construct.rs @@ -19,7 +19,7 @@ use crate::execute::walk_target::{self, RequireOrigin}; use crate::heading_address::{SectionPos, Visible}; use crate::lua::{Library, LibraryKey, SectionVm, Uninjected}; use crate::model::ModelBinding; -use crate::parser::Prompt; +use crate::prompt::Prompt; use crate::{Error, Result}; use super::{SectionContext, SectionSys, TaskSeed}; diff --git a/crates/runvil-internal/engine/src/execute/tests.rs b/crates/runvil-internal/engine/src/execute/tests.rs index 0bad22a78..fdbe91fcc 100644 --- a/crates/runvil-internal/engine/src/execute/tests.rs +++ b/crates/runvil-internal/engine/src/execute/tests.rs @@ -17,8 +17,8 @@ use super::*; use crate::Error; use crate::lua::{SectionVm, current_static_set}; use crate::model::{ModelInfo, ModelSet}; -use crate::parser::ParseErrorKind; -use crate::parser::Prompt; +use crate::prompt::ParseErrorKind; +use crate::prompt::Prompt; use crate::test_support::recording::{Observation, Observer, detail, null_emitter}; use crate::test_support::scenario::{Scenario, Trace}; use crate::test_support::scripted_chat::{ @@ -50,7 +50,7 @@ fn calls_slow_tool(effect: &Effect) -> bool { #[test] fn run_with_a_pre_cancelled_handle_fails_as_cancelled() { - use runvil_parser::test_support::prompt; + use runvil_prompt::test_support::prompt; use runvil_types::cancel::CancelHandle; // The explicit-cancel wiring of the public entry point: a handle passed @@ -111,7 +111,7 @@ fn tool_turn_nonces(bodies: &[ScriptedCall]) -> Vec { #[test] fn untrusted_nonce_differs_across_runs_under_different_seeds() { - use runvil_parser::test_support::PromptSource; + use runvil_prompt::test_support::PromptSource; // The nonce is the run seed's: two runs of the same prompt under // different caller-drawn seeds wrap the same untrusted tool result under diff --git a/crates/runvil-internal/engine/src/execute/tests/adhoc_tools.rs b/crates/runvil-internal/engine/src/execute/tests/adhoc_tools.rs index fd32d95b0..a223b1c66 100644 --- a/crates/runvil-internal/engine/src/execute/tests/adhoc_tools.rs +++ b/crates/runvil-internal/engine/src/execute/tests/adhoc_tools.rs @@ -8,7 +8,7 @@ //! only when offered. The refusal of an offer once the section's tool list //! is rendered sits in `rendered_tools`. -use runvil_parser::test_support::{one_section, prompt}; +use runvil_prompt::test_support::{one_section, prompt}; use super::context::Recorder; use super::fixtures::scoped_tool; diff --git a/crates/runvil-internal/engine/src/execute/tests/batch_turn.rs b/crates/runvil-internal/engine/src/execute/tests/batch_turn.rs index 3879489a4..d5d0705fb 100644 --- a/crates/runvil-internal/engine/src/execute/tests/batch_turn.rs +++ b/crates/runvil-internal/engine/src/execute/tests/batch_turn.rs @@ -7,7 +7,7 @@ //! A handler or a compactor cannot start its own loop while a loop runs in //! the section; once no loop runs, the next one starts normally. -use runvil_parser::test_support::{one_section, prompt}; +use runvil_prompt::test_support::{one_section, prompt}; use runvil_types::event::Event; use super::fixtures::echo_tool; diff --git a/crates/runvil-internal/engine/src/execute/tests/chat_arm.rs b/crates/runvil-internal/engine/src/execute/tests/chat_arm.rs index 34a9b5941..7ac632dc9 100644 --- a/crates/runvil-internal/engine/src/execute/tests/chat_arm.rs +++ b/crates/runvil-internal/engine/src/execute/tests/chat_arm.rs @@ -6,7 +6,7 @@ //! shim's exit rules and compactor then act on. The round's tool scope is //! covered in `chat_scope`. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use runvil_types::event::{Event, ReplyOrigin}; use runvil_types::metrics::{CallMetrics, Usage}; diff --git a/crates/runvil-internal/engine/src/execute/tests/chat_record_rebuild.rs b/crates/runvil-internal/engine/src/execute/tests/chat_record_rebuild.rs index edf64e769..a49e230bf 100644 --- a/crates/runvil-internal/engine/src/execute/tests/chat_record_rebuild.rs +++ b/crates/runvil-internal/engine/src/execute/tests/chat_record_rebuild.rs @@ -8,7 +8,7 @@ use std::cell::RefCell; use std::collections::BTreeMap; use std::rc::Rc; -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use runvil_types::ids::RoundId; use super::fixtures::echo_tool; diff --git a/crates/runvil-internal/engine/src/execute/tests/chat_scope.rs b/crates/runvil-internal/engine/src/execute/tests/chat_scope.rs index 424815ce2..a46a8a671 100644 --- a/crates/runvil-internal/engine/src/execute/tests/chat_scope.rs +++ b/crates/runvil-internal/engine/src/execute/tests/chat_scope.rs @@ -2,7 +2,7 @@ //! sends the section's Static Set, its offered catalog tools and adhoc //! tools as the first round froze them. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use super::fixtures::echo_tool; use super::models_loop::loop_scenario; diff --git a/crates/runvil-internal/engine/src/execute/tests/compaction-rules.rs b/crates/runvil-internal/engine/src/execute/tests/compaction-rules.rs index 9a595d665..23b105b3b 100644 --- a/crates/runvil-internal/engine/src/execute/tests/compaction-rules.rs +++ b/crates/runvil-internal/engine/src/execute/tests/compaction-rules.rs @@ -2,7 +2,7 @@ //! structural rules, the shrink rule, and the retried round's precheck. //! Each broken rule raises its error and puts the records back. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use super::*; diff --git a/crates/runvil-internal/engine/src/execute/tests/compaction.rs b/crates/runvil-internal/engine/src/execute/tests/compaction.rs index 87e3a0ae9..45de2489a 100644 --- a/crates/runvil-internal/engine/src/execute/tests/compaction.rs +++ b/crates/runvil-internal/engine/src/execute/tests/compaction.rs @@ -5,7 +5,7 @@ //! `models.compact`, the same compaction outside a loop, in //! `models_compact`. -use runvil_parser::test_support::{one_section, prompt}; +use runvil_prompt::test_support::{one_section, prompt}; use super::fixtures::echo_tool; use super::gateway::overflowing; diff --git a/crates/runvil-internal/engine/src/execute/tests/context.rs b/crates/runvil-internal/engine/src/execute/tests/context.rs index 120192720..6868307fa 100644 --- a/crates/runvil-internal/engine/src/execute/tests/context.rs +++ b/crates/runvil-internal/engine/src/execute/tests/context.rs @@ -1,7 +1,7 @@ //! Shared config builders, prompt sources, parsers, stores, task ids, and //! recorders for the execution suites. -use runvil_parser::test_support::PromptSource; +use runvil_prompt::test_support::PromptSource; use runvil_types::ids::TaskId; use super::*; diff --git a/crates/runvil-internal/engine/src/execute/tests/debug_and_counts-infer-rounds.rs b/crates/runvil-internal/engine/src/execute/tests/debug_and_counts-infer-rounds.rs index f65876666..5248c530c 100644 --- a/crates/runvil-internal/engine/src/execute/tests/debug_and_counts-infer-rounds.rs +++ b/crates/runvil-internal/engine/src/execute/tests/debug_and_counts-infer-rounds.rs @@ -2,7 +2,7 @@ //! `model_turn_completed`, then `thinking` when the backend supplied //! reasoning, then one `assistant_reply` with `origin = Infer`. -use runvil_parser::test_support::PromptSource; +use runvil_prompt::test_support::PromptSource; use super::*; use crate::test_support::recording::{ReplyReport, ThinkingReport}; diff --git a/crates/runvil-internal/engine/src/execute/tests/debug_and_counts.rs b/crates/runvil-internal/engine/src/execute/tests/debug_and_counts.rs index 72df3675d..4260d7a21 100644 --- a/crates/runvil-internal/engine/src/execute/tests/debug_and_counts.rs +++ b/crates/runvil-internal/engine/src/execute/tests/debug_and_counts.rs @@ -1,7 +1,7 @@ //! Tests for debug capture delivery and the `tools.stats` counters. The //! infer-round reporting cases sit in `infer_rounds`. -use runvil_parser::test_support::{PromptSource, one_section}; +use runvil_prompt::test_support::{PromptSource, one_section}; use super::context::arm_fanout; use super::fixtures::{echo_tool, failing_tool, scoped_tool}; diff --git a/crates/runvil-internal/engine/src/execute/tests/discovery.rs b/crates/runvil-internal/engine/src/execute/tests/discovery.rs index e69d9c9e4..7b1d4ee9a 100644 --- a/crates/runvil-internal/engine/src/execute/tests/discovery.rs +++ b/crates/runvil-internal/engine/src/execute/tests/discovery.rs @@ -5,7 +5,7 @@ //! batch, and every discovery call counts. The refusals sit in the //! `refusals` child. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use runvil_types::event::Event; use super::fixtures::{echo_tool, scoped_tool}; diff --git a/crates/runvil-internal/engine/src/execute/tests/effects.rs b/crates/runvil-internal/engine/src/execute/tests/effects.rs index d89e6105f..51bcbe852 100644 --- a/crates/runvil-internal/engine/src/execute/tests/effects.rs +++ b/crates/runvil-internal/engine/src/execute/tests/effects.rs @@ -6,7 +6,7 @@ //! origin and a nested infer round the `Infer` origin. The run's answer //! rules (a drop, an orphan, a wrong kind) are pinned beside `Run` itself. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use runvil_types::event::ReplyOrigin; use runvil_types::ids::RoundId; use runvil_types::packs::PackId; diff --git a/crates/runvil-internal/engine/src/execute/tests/event_vendors.rs b/crates/runvil-internal/engine/src/execute/tests/event_vendors.rs index 6c8b21da0..3e2c909a4 100644 --- a/crates/runvil-internal/engine/src/execute/tests/event_vendors.rs +++ b/crates/runvil-internal/engine/src/execute/tests/event_vendors.rs @@ -3,7 +3,7 @@ //! a script call to a catalog tool, and none for an adhoc call or a run //! given no roster. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use runvil_types::event::Event; use runvil_types::packs::PackId; diff --git a/crates/runvil-internal/engine/src/execute/tests/exec_flow.rs b/crates/runvil-internal/engine/src/execute/tests/exec_flow.rs index c8bb5b97f..fb55667ac 100644 --- a/crates/runvil-internal/engine/src/execute/tests/exec_flow.rs +++ b/crates/runvil-internal/engine/src/execute/tests/exec_flow.rs @@ -1,6 +1,6 @@ //! Tests for section walk control flow: `call`, `jump`, `fanout`, the `prompt` reads, and `var`. -use runvil_parser::test_support::{PromptSource, synthetic_section}; +use runvil_prompt::test_support::{PromptSource, synthetic_section}; use super::context::arm_fanout; use super::*; diff --git a/crates/runvil-internal/engine/src/execute/tests/exit_rules.rs b/crates/runvil-internal/engine/src/execute/tests/exit_rules.rs index a18deee98..703597438 100644 --- a/crates/runvil-internal/engine/src/execute/tests/exit_rules.rs +++ b/crates/runvil-internal/engine/src/execute/tests/exit_rules.rs @@ -4,7 +4,7 @@ //! model's clean empty exit after tool work, and the empty rounds that //! raise `empty_model_reply`). -use runvil_parser::test_support::{PromptSource, one_section, prompt}; +use runvil_prompt::test_support::{PromptSource, one_section, prompt}; use super::context::Recorder; use super::fixtures::echo_tool; diff --git a/crates/runvil-internal/engine/src/execute/tests/full_id_calls.rs b/crates/runvil-internal/engine/src/execute/tests/full_id_calls.rs index 7fa9c8bae..a74c2b8e9 100644 --- a/crates/runvil-internal/engine/src/execute/tests/full_id_calls.rs +++ b/crates/runvil-internal/engine/src/execute/tests/full_id_calls.rs @@ -4,7 +4,7 @@ //! A call binds no global and advertises nothing; a wire name is the //! model's alone, and a model-issued call never resolves an id. -use runvil_parser::test_support::{one_section, prompt}; +use runvil_prompt::test_support::{one_section, prompt}; use runvil_types::event::Event; use super::fixtures::{echo_tool, scoped_tool}; diff --git a/crates/runvil-internal/engine/src/execute/tests/live_infer.rs b/crates/runvil-internal/engine/src/execute/tests/live_infer.rs index 2f5f40561..08cfcfd73 100644 --- a/crates/runvil-internal/engine/src/execute/tests/live_infer.rs +++ b/crates/runvil-internal/engine/src/execute/tests/live_infer.rs @@ -1,6 +1,6 @@ //! Tests for live `models.infer` against a scripted gateway, including H1 chunks and required libraries. -use runvil_parser::test_support::PromptSource; +use runvil_prompt::test_support::PromptSource; use runvil_types::cancel::CancelHandle; use super::fixtures::echo_tool; diff --git a/crates/runvil-internal/engine/src/execute/tests/model_and_reply-globals.rs b/crates/runvil-internal/engine/src/execute/tests/model_and_reply-globals.rs index 654aadd82..c516b1006 100644 --- a/crates/runvil-internal/engine/src/execute/tests/model_and_reply-globals.rs +++ b/crates/runvil-internal/engine/src/execute/tests/model_and_reply-globals.rs @@ -5,7 +5,7 @@ //! member substitutes as compact JSON; and `reply` is not a global: it //! reads nil in Lua and fails as an unknown global in prose. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use super::*; use crate::execute::tests::fixtures::echo_tool; diff --git a/crates/runvil-internal/engine/src/execute/tests/model_and_reply.rs b/crates/runvil-internal/engine/src/execute/tests/model_and_reply.rs index 0b0c60090..8db8891e9 100644 --- a/crates/runvil-internal/engine/src/execute/tests/model_and_reply.rs +++ b/crates/runvil-internal/engine/src/execute/tests/model_and_reply.rs @@ -2,7 +2,7 @@ //! The `sys.model`, `item`, and `reply` reads sit in `globals`, and the //! `models.get` and handle `infer` cases in `handles`. -use runvil_parser::test_support::{PromptSource, parse_ok}; +use runvil_prompt::test_support::{PromptSource, parse_ok}; use crate::model::ModelRole; use crate::test_support::scenario::{Trace, test_model}; @@ -133,7 +133,7 @@ fn epilog_runs_after_prose_and_can_return() { return 'epilog result'", ); let prompt = parse_ok(&source); - let entry = runvil_parser::detail::entry(&prompt).expect("fixture has sections"); + let entry = runvil_prompt::detail::entry(&prompt).expect("fixture has sections"); assert!(entry.prologue().is_none()); assert!(entry.epilog().is_some()); diff --git a/crates/runvil-internal/engine/src/execute/tests/models_compact.rs b/crates/runvil-internal/engine/src/execute/tests/models_compact.rs index 6cb0b8c14..540785fcd 100644 --- a/crates/runvil-internal/engine/src/execute/tests/models_compact.rs +++ b/crates/runvil-internal/engine/src/execute/tests/models_compact.rs @@ -5,7 +5,7 @@ //! loop or a compactor runs. Compaction after an overflow sits in //! `compaction`. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use super::models_loop::loop_scenario; use super::*; diff --git a/crates/runvil-internal/engine/src/execute/tests/models_loop-arguments.rs b/crates/runvil-internal/engine/src/execute/tests/models_loop-arguments.rs index 1466a42ae..e4cf3a0d0 100644 --- a/crates/runvil-internal/engine/src/execute/tests/models_loop-arguments.rs +++ b/crates/runvil-internal/engine/src/execute/tests/models_loop-arguments.rs @@ -2,7 +2,7 @@ //! runs over the section's `messages` list, and the argument errors are //! raised in order before any request leaves. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use super::*; diff --git a/crates/runvil-internal/engine/src/execute/tests/models_loop-author-shapes.rs b/crates/runvil-internal/engine/src/execute/tests/models_loop-author-shapes.rs index 2f7c95479..80e44d1fa 100644 --- a/crates/runvil-internal/engine/src/execute/tests/models_loop-author-shapes.rs +++ b/crates/runvil-internal/engine/src/execute/tests/models_loop-author-shapes.rs @@ -2,7 +2,7 @@ //! against a canned model: the `prompts/research-person.md` section and //! the shipped chat agent's turn loop. -use runvil_parser::test_support::{PromptSource, one_section}; +use runvil_prompt::test_support::{PromptSource, one_section}; use super::*; use crate::model::{ModelBinding, ModelId, ModelInvocation}; diff --git a/crates/runvil-internal/engine/src/execute/tests/models_loop.rs b/crates/runvil-internal/engine/src/execute/tests/models_loop.rs index 8c53e5f80..58a8c9715 100644 --- a/crates/runvil-internal/engine/src/execute/tests/models_loop.rs +++ b/crates/runvil-internal/engine/src/execute/tests/models_loop.rs @@ -12,7 +12,7 @@ //! loop's exit rules in `exit_rules`; its cap, scope gate, and //! result-record trust in `tool_loop`. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use super::context::Recorder; use super::fixtures::echo_tool; diff --git a/crates/runvil-internal/engine/src/execute/tests/models_loop_compactors.rs b/crates/runvil-internal/engine/src/execute/tests/models_loop_compactors.rs index cbe8e92ca..0060adc8f 100644 --- a/crates/runvil-internal/engine/src/execute/tests/models_loop_compactors.rs +++ b/crates/runvil-internal/engine/src/execute/tests/models_loop_compactors.rs @@ -8,7 +8,7 @@ //! retry and restore in `compaction`; and `models.compact` in //! `models_compact`. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use super::gateway::overflowing; use super::models_loop::loop_scenario; diff --git a/crates/runvil-internal/engine/src/execute/tests/models_loop_contract-rounds.rs b/crates/runvil-internal/engine/src/execute/tests/models_loop_contract-rounds.rs index e336b0867..c196dd731 100644 --- a/crates/runvil-internal/engine/src/execute/tests/models_loop_contract-rounds.rs +++ b/crates/runvil-internal/engine/src/execute/tests/models_loop_contract-rounds.rs @@ -3,7 +3,7 @@ //! call, an adhoc tool's return and its cancellation, and the full trace a //! run reports for the loop. -use runvil_parser::test_support::{PromptSource, one_section}; +use runvil_prompt::test_support::{PromptSource, one_section}; use super::*; use crate::execute::run::{Effect, EffectAnswer, Step}; diff --git a/crates/runvil-internal/engine/src/execute/tests/models_loop_contract.rs b/crates/runvil-internal/engine/src/execute/tests/models_loop_contract.rs index 36f080037..4d1fd034a 100644 --- a/crates/runvil-internal/engine/src/execute/tests/models_loop_contract.rs +++ b/crates/runvil-internal/engine/src/execute/tests/models_loop_contract.rs @@ -6,7 +6,7 @@ //! compactor call sit here; the exit rules, the batch rule, tool results, //! and the event trace sit in `rounds`. -use runvil_parser::test_support::{one_section, prompt}; +use runvil_prompt::test_support::{one_section, prompt}; use super::fixtures::structured_tool; use super::gateway::overflowing; diff --git a/crates/runvil-internal/engine/src/execute/tests/observations.rs b/crates/runvil-internal/engine/src/execute/tests/observations.rs index ba2c8eff8..c798a0705 100644 --- a/crates/runvil-internal/engine/src/execute/tests/observations.rs +++ b/crates/runvil-internal/engine/src/execute/tests/observations.rs @@ -1,6 +1,6 @@ //! Tests for the observation event sequence a run reports across its lifecycle. -use runvil_parser::test_support::{one_section, prompt}; +use runvil_prompt::test_support::{one_section, prompt}; use runvil_types::event::Event; use super::context::{Recorder, sequence}; diff --git a/crates/runvil-internal/engine/src/execute/tests/offering.rs b/crates/runvil-internal/engine/src/execute/tests/offering.rs index 5c8416104..da5c15456 100644 --- a/crates/runvil-internal/engine/src/execute/tests/offering.rs +++ b/crates/runvil-internal/engine/src/execute/tests/offering.rs @@ -3,7 +3,7 @@ //! reaches by wire name in its section's frozen sets, and a script call //! reaches by id. -use runvil_parser::test_support::{PromptSource, one_section, prompt}; +use runvil_prompt::test_support::{PromptSource, one_section, prompt}; use super::fixtures::{echo_tool, model_only_tool}; use super::models_loop::{loop_model_name, loop_scenario}; diff --git a/crates/runvil-internal/engine/src/execute/tests/parallel_tool_calls.rs b/crates/runvil-internal/engine/src/execute/tests/parallel_tool_calls.rs index e33f4544e..fb19d73f7 100644 --- a/crates/runvil-internal/engine/src/execute/tests/parallel_tool_calls.rs +++ b/crates/runvil-internal/engine/src/execute/tests/parallel_tool_calls.rs @@ -9,7 +9,7 @@ //! by hand, so it sees exactly what each step issued and withdrew, and //! answers each section's render with its tools unchanged. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use runvil_types::event::{Event, ReplyOrigin}; use super::fixtures::echo_tool; diff --git a/crates/runvil-internal/engine/src/execute/tests/precheck_anchor.rs b/crates/runvil-internal/engine/src/execute/tests/precheck_anchor.rs index b73c0ad2d..ede8aa40d 100644 --- a/crates/runvil-internal/engine/src/execute/tests/precheck_anchor.rs +++ b/crates/runvil-internal/engine/src/execute/tests/precheck_anchor.rs @@ -7,7 +7,7 @@ //! compactor that receives it in `models_loop_compactors`. What a refused //! send leaves in the run log is in `resend`. -use runvil_parser::test_support::{one_section, prompt}; +use runvil_prompt::test_support::{one_section, prompt}; use runvil_types::metrics::{CallMetrics, Usage}; use super::models_loop::{loop_models, loop_scenario}; diff --git a/crates/runvil-internal/engine/src/execute/tests/render_effect.rs b/crates/runvil-internal/engine/src/execute/tests/render_effect.rs index 96b8e22d0..520ae4e4c 100644 --- a/crates/runvil-internal/engine/src/execute/tests/render_effect.rs +++ b/crates/runvil-internal/engine/src/execute/tests/render_effect.rs @@ -6,7 +6,7 @@ //! sending the same tools, and a dropped or broken answer ending the round //! with nothing fixed. -use runvil_parser::test_support::{one_section, prompt}; +use runvil_prompt::test_support::{one_section, prompt}; use runvil_types::packs::PackId; use runvil_types::tools::ToolInfo; diff --git a/crates/runvil-internal/engine/src/execute/tests/rendered_tools.rs b/crates/runvil-internal/engine/src/execute/tests/rendered_tools.rs index 37623a23a..40bb1075b 100644 --- a/crates/runvil-internal/engine/src/execute/tests/rendered_tools.rs +++ b/crates/runvil-internal/engine/src/execute/tests/rendered_tools.rs @@ -5,7 +5,7 @@ //! whose merge fails, freezes nothing, one section's sets never reach //! another, and `models.infer` sends no tools either side of the freeze. -use runvil_parser::test_support::{one_section, prompt}; +use runvil_prompt::test_support::{one_section, prompt}; use super::fixtures::{echo_tool, scoped_tool}; use super::models_loop::{loop_models, loop_scenario}; diff --git a/crates/runvil-internal/engine/src/execute/tests/rounds.rs b/crates/runvil-internal/engine/src/execute/tests/rounds.rs index 5c600a7a9..511f4b6ac 100644 --- a/crates/runvil-internal/engine/src/execute/tests/rounds.rs +++ b/crates/runvil-internal/engine/src/execute/tests/rounds.rs @@ -3,7 +3,7 @@ //! every content event a round's answer reports - its thinking, its reply, //! its tool-call batch - holds the id its `Chat` effect held. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use runvil_types::event::{Event, ReplyOrigin}; use runvil_types::ids::RoundId; diff --git a/crates/runvil-internal/engine/src/execute/tests/section_messages.rs b/crates/runvil-internal/engine/src/execute/tests/section_messages.rs index 8cdaa91aa..4abc3137f 100644 --- a/crates/runvil-internal/engine/src/execute/tests/section_messages.rs +++ b/crates/runvil-internal/engine/src/execute/tests/section_messages.rs @@ -6,7 +6,7 @@ //! and take only `(compactor?)`. A record's `meta` stays in the list and //! reaches tools, but never the model. -use runvil_parser::test_support::PromptSource; +use runvil_prompt::test_support::PromptSource; use super::models_loop::loop_scenario; use super::*; diff --git a/crates/runvil-internal/engine/src/execute/tests/suite.rs b/crates/runvil-internal/engine/src/execute/tests/suite.rs index 85a9336bb..a98e9f62c 100644 --- a/crates/runvil-internal/engine/src/execute/tests/suite.rs +++ b/crates/runvil-internal/engine/src/execute/tests/suite.rs @@ -6,7 +6,7 @@ //! checks - over the run-level //! [`Scenario`](crate::test_support::scenario::Scenario). Its //! cases reach engine-only items (the test drivers and recorders, the store -//! facade, the parser's Lua programs), so they run here rather than against +//! facade, the loader's Lua programs), so they run here rather than against //! the `runvil` facade, whose own suite holds the rest. mod args_surface; diff --git a/crates/runvil-internal/engine/src/execute/tests/suite/args_surface.rs b/crates/runvil-internal/engine/src/execute/tests/suite/args_surface.rs index 8272dc0c4..e288b1f55 100644 --- a/crates/runvil-internal/engine/src/execute/tests/suite/args_surface.rs +++ b/crates/runvil-internal/engine/src/execute/tests/suite/args_surface.rs @@ -5,7 +5,7 @@ //! so an H1 repair reaches every downstream section while an H2 assignment //! is an error; and `{{ argv }}` joins the prose substitution namespaces. -use runvil_parser::test_support::{PromptSource, prompt}; +use runvil_prompt::test_support::{PromptSource, prompt}; use crate::test_support::scenario::Scenario; diff --git a/crates/runvil-internal/engine/src/execute/tests/suite/exec_flow.rs b/crates/runvil-internal/engine/src/execute/tests/suite/exec_flow.rs index 21098aaaa..49148233e 100644 --- a/crates/runvil-internal/engine/src/execute/tests/suite/exec_flow.rs +++ b/crates/runvil-internal/engine/src/execute/tests/suite/exec_flow.rs @@ -1,9 +1,9 @@ //! Section walk control flow over the offline fixtures: `call`, `jump`, //! `fanout`, the `prompt` reads, and `var`. Each submodule holds one topic -//! and builds its prompts with the parser's source builder and runs them as +//! and builds its prompts with the loader's source builder and runs them as //! a [`Scenario`]. -use runvil_parser::test_support::{PromptSource, one_section, prompt}; +use runvil_prompt::test_support::{PromptSource, one_section, prompt}; use crate::test_support::scenario::Scenario; diff --git a/crates/runvil-internal/engine/src/execute/tests/suite/exec_flow/run_setup.rs b/crates/runvil-internal/engine/src/execute/tests/suite/exec_flow/run_setup.rs index b2402cb11..999e9f108 100644 --- a/crates/runvil-internal/engine/src/execute/tests/suite/exec_flow/run_setup.rs +++ b/crates/runvil-internal/engine/src/execute/tests/suite/exec_flow/run_setup.rs @@ -54,7 +54,7 @@ impl runvil_vfs::Vfs for RefusingStore { #[test] fn a_store_backend_that_refuses_its_session_fails_the_run_at_the_first_step() { let source = one_section("return 'never runs'"); - let (prompt, _) = crate::parser::Prompt::parse(&source, EXECUTION); + let (prompt, _) = crate::prompt::Prompt::parse(&source, EXECUTION); let vfs = VfsRef::builder().store("/", RefusingStore).build(); let mut run = crate::execute::run::Run::new( Arc::new(prompt.expect("the fixture parses")), diff --git a/crates/runvil-internal/engine/src/execute/tests/suite/fanout.rs b/crates/runvil-internal/engine/src/execute/tests/suite/fanout.rs index 168849230..0ba487580 100644 --- a/crates/runvil-internal/engine/src/execute/tests/suite/fanout.rs +++ b/crates/runvil-internal/engine/src/execute/tests/suite/fanout.rs @@ -1,7 +1,7 @@ //! Concurrent fanout: per-arm start/terminal accounting, store writes across //! arms, the propagated arm-failure error contract, and encoding a result. -use runvil_parser::test_support::prompt; +use runvil_prompt::test_support::prompt; use crate::Error; use crate::RunErrorKind; diff --git a/crates/runvil-internal/engine/src/execute/tests/suite/global_metatable.rs b/crates/runvil-internal/engine/src/execute/tests/suite/global_metatable.rs index 96cd3cfd6..67fc2c4e2 100644 --- a/crates/runvil-internal/engine/src/execute/tests/suite/global_metatable.rs +++ b/crates/runvil-internal/engine/src/execute/tests/suite/global_metatable.rs @@ -4,7 +4,7 @@ //! `argv`, `prose`, and `messages` stay guarded whatever author code does //! to `_G`'s metatable. -use runvil_parser::test_support::PromptSource; +use runvil_prompt::test_support::PromptSource; use crate::test_support::scenario::Scenario; diff --git a/crates/runvil-internal/engine/src/execute/tests/suite/lazy_prose.rs b/crates/runvil-internal/engine/src/execute/tests/suite/lazy_prose.rs index 6242fc036..73ef1034d 100644 --- a/crates/runvil-internal/engine/src/execute/tests/suite/lazy_prose.rs +++ b/crates/runvil-internal/engine/src/execute/tests/suite/lazy_prose.rs @@ -5,7 +5,7 @@ //! at section end, and no `reply` global or automatic result handoff //! crosses a section boundary. -use runvil_parser::test_support::{PromptSource, one_section, prompt}; +use runvil_prompt::test_support::{PromptSource, one_section, prompt}; use crate::test_support::scenario::Scenario; diff --git a/crates/runvil-internal/engine/src/execute/tests/suite/parsing.rs b/crates/runvil-internal/engine/src/execute/tests/suite/parsing.rs index 7a1ff7eb6..d57a2bfa4 100644 --- a/crates/runvil-internal/engine/src/execute/tests/suite/parsing.rs +++ b/crates/runvil-internal/engine/src/execute/tests/suite/parsing.rs @@ -8,9 +8,9 @@ use std::num::NonZeroU32; use std::path::{Path, PathBuf}; use runvil_lua::LuaProgram; -use runvil_parser::detail; +use runvil_prompt::detail; -use crate::parser::Prompt; +use crate::prompt::Prompt; struct ValidFixture { name: &'static str, diff --git a/crates/runvil-internal/engine/src/execute/tests/suite/prepare.rs b/crates/runvil-internal/engine/src/execute/tests/suite/prepare.rs index 7fe48b49b..33e19e531 100644 --- a/crates/runvil-internal/engine/src/execute/tests/suite/prepare.rs +++ b/crates/runvil-internal/engine/src/execute/tests/suite/prepare.rs @@ -13,11 +13,11 @@ use crate::execute::context::ChainState; use crate::execute::tests::context::test_config; use crate::execute::{Effect, EffectAnswer, RunConfig}; use crate::model::ModelBinding; -use crate::parser::Prompt; +use crate::prompt::Prompt; use crate::test_support::scenario::Scenario; use crate::test_support::scripted_chat::{ScriptedChat, text}; use runvil_model_client::client::{Completion, CompletionResult}; -use runvil_parser::test_support::{PromptSource, parse_ok}; +use runvil_prompt::test_support::{PromptSource, parse_ok}; use runvil_types::event::Event; use runvil_types::models::{ModelId, ModelInfo, ModelRole, ThinkingMode}; use runvil_vfs::Origin; diff --git a/crates/runvil-internal/engine/src/execute/tests/suite/require.rs b/crates/runvil-internal/engine/src/execute/tests/suite/require.rs index 058224337..1142c15b1 100644 --- a/crates/runvil-internal/engine/src/execute/tests/suite/require.rs +++ b/crates/runvil-internal/engine/src/execute/tests/suite/require.rs @@ -6,7 +6,7 @@ //! fixture tool. Each submodule holds one topic and reaches the support //! below through `super`. -use runvil_parser::test_support::{PromptSource, prompt}; +use runvil_prompt::test_support::{PromptSource, prompt}; use crate::test_support::scenario::{Record, Scenario}; diff --git a/crates/runvil-internal/engine/src/execute/tests/suite/vfs.rs b/crates/runvil-internal/engine/src/execute/tests/suite/vfs.rs index df2eb85fd..2118e9e94 100644 --- a/crates/runvil-internal/engine/src/execute/tests/suite/vfs.rs +++ b/crates/runvil-internal/engine/src/execute/tests/suite/vfs.rs @@ -12,11 +12,11 @@ use std::sync::Arc; use crate::execute::RunOutcome; use crate::execute::run::{Effect, IssuedEffect, Run, Step}; use crate::execute::tests::context::{parse, test_config}; -use crate::parser::Prompt; +use crate::prompt::Prompt; use crate::test_support::perform_locally; use crate::test_support::scenario::{EXECUTION, Scenario}; use crate::test_support::temp_root::TempRoot; -use runvil_parser::test_support::{parse_ok, prompt}; +use runvil_prompt::test_support::{parse_ok, prompt}; use runvil_vfs::{Access, Origin, RealBackend, VfsError, VfsRef}; const ROUND_TRIP_PROMPT: &str = concat!( diff --git a/crates/runvil-internal/engine/src/execute/tests/tasks-chain-end.rs b/crates/runvil-internal/engine/src/execute/tests/tasks-chain-end.rs index f080da2c8..38ee76246 100644 --- a/crates/runvil-internal/engine/src/execute/tests/tasks-chain-end.rs +++ b/crates/runvil-internal/engine/src/execute/tests/tasks-chain-end.rs @@ -5,7 +5,7 @@ //! or exhausted its tool loop); a task spawned in H1 belongs to the walk //! after the hand-off; an aborted chain's owned tasks abort with it. -use runvil_parser::test_support::prompt; +use runvil_prompt::test_support::prompt; use runvil_types::ids::AbandonReason; use super::*; diff --git a/crates/runvil-internal/engine/src/execute/tests/tool_call_arm-adhoc-handlers.rs b/crates/runvil-internal/engine/src/execute/tests/tool_call_arm-adhoc-handlers.rs index f811826dc..50dd188b0 100644 --- a/crates/runvil-internal/engine/src/execute/tests/tool_call_arm-adhoc-handlers.rs +++ b/crates/runvil-internal/engine/src/execute/tests/tool_call_arm-adhoc-handlers.rs @@ -7,7 +7,7 @@ //! survives. A model-issued call resolves in the section's frozen sets, //! so those cases run one loop round first. -use runvil_parser::test_support::prompt; +use runvil_prompt::test_support::prompt; use super::*; use crate::execute::tests::fixtures::{echo_tool, failing_tool, untrusted_echo_tool}; diff --git a/crates/runvil-internal/engine/src/execute/tests/tool_call_arm.rs b/crates/runvil-internal/engine/src/execute/tests/tool_call_arm.rs index e4ae9a144..3d86d329d 100644 --- a/crates/runvil-internal/engine/src/execute/tests/tool_call_arm.rs +++ b/crates/runvil-internal/engine/src/execute/tests/tool_call_arm.rs @@ -12,7 +12,7 @@ //! sets, so those cases run one loop round first. //! The adhoc tool cases sit in `adhoc_handlers`. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use super::fixtures::{echo_tool, failing_tool}; use super::models_loop::loop_scenario; diff --git a/crates/runvil-internal/engine/src/execute/tests/tool_call_origins.rs b/crates/runvil-internal/engine/src/execute/tests/tool_call_origins.rs index e800f2fce..7394219f7 100644 --- a/crates/runvil-internal/engine/src/execute/tests/tool_call_origins.rs +++ b/crates/runvil-internal/engine/src/execute/tests/tool_call_origins.rs @@ -4,7 +4,7 @@ //! it, which the section's message list recorded when the round was sent. //! A script call names no model. -use runvil_parser::test_support::{one_section, prompt}; +use runvil_prompt::test_support::{one_section, prompt}; use super::fixtures::echo_tool; use super::models_loop::{loop_model_name, loop_scenario}; diff --git a/crates/runvil-internal/engine/src/execute/tests/tool_listings.rs b/crates/runvil-internal/engine/src/execute/tests/tool_listings.rs index ee4895e25..9cec22a24 100644 --- a/crates/runvil-internal/engine/src/execute/tests/tool_listings.rs +++ b/crates/runvil-internal/engine/src/execute/tests/tool_listings.rs @@ -5,7 +5,7 @@ //! with the run's nonce. A listing kept from outside the adhoc call, a //! failed listing call, and a trusted listing mark nothing. -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use runvil_types::event::Event; use super::models_loop::loop_scenario; diff --git a/crates/runvil-internal/engine/src/execute/tests/tool_loop.rs b/crates/runvil-internal/engine/src/execute/tests/tool_loop.rs index 6e21dfe42..2466407e0 100644 --- a/crates/runvil-internal/engine/src/execute/tests/tool_loop.rs +++ b/crates/runvil-internal/engine/src/execute/tests/tool_loop.rs @@ -8,7 +8,7 @@ //! calls in `models_loop`; its compactor paths in `models_loop_compactors` //! and `compaction`. -use runvil_parser::test_support::{one_section, prompt}; +use runvil_prompt::test_support::{one_section, prompt}; use runvil_types::event::Event; use super::context::Recorder; diff --git a/crates/runvil-internal/engine/src/execute/tests/tool_scoping.rs b/crates/runvil-internal/engine/src/execute/tests/tool_scoping.rs index 43ee240e1..ebe91daef 100644 --- a/crates/runvil-internal/engine/src/execute/tests/tool_scoping.rs +++ b/crates/runvil-internal/engine/src/execute/tests/tool_scoping.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; -use runvil_parser::test_support::one_section; +use runvil_prompt::test_support::one_section; use runvil_types::tools::ToolCatalog; use super::fixtures::scoped_tool; diff --git a/crates/runvil-internal/engine/src/execute/tests/unified_pipeline.rs b/crates/runvil-internal/engine/src/execute/tests/unified_pipeline.rs index 8728c73ac..3f9c5864a 100644 --- a/crates/runvil-internal/engine/src/execute/tests/unified_pipeline.rs +++ b/crates/runvil-internal/engine/src/execute/tests/unified_pipeline.rs @@ -4,7 +4,7 @@ //! synchronous `call`, driven //! through a run against a scripted gateway at `runvil: 0`. -use runvil_parser::test_support::PromptSource; +use runvil_prompt::test_support::PromptSource; use super::fixtures::echo_tool; use super::*; diff --git a/crates/runvil-internal/engine/src/execute/walk_target-tests.rs b/crates/runvil-internal/engine/src/execute/walk_target-tests.rs index faff648e8..cadf138b5 100644 --- a/crates/runvil-internal/engine/src/execute/walk_target-tests.rs +++ b/crates/runvil-internal/engine/src/execute/walk_target-tests.rs @@ -2,7 +2,7 @@ //! the key naming its position, and the reach a keyed lookup resolves from. use runvil_lua::LuaProgram; -use runvil_parser::test_support::{prose_block, synthetic_section}; +use runvil_prompt::test_support::{prose_block, synthetic_section}; use super::*; diff --git a/crates/runvil-internal/engine/src/execute/walk_target.rs b/crates/runvil-internal/engine/src/execute/walk_target.rs index 7c39402a3..bf74a562f 100644 --- a/crates/runvil-internal/engine/src/execute/walk_target.rs +++ b/crates/runvil-internal/engine/src/execute/walk_target.rs @@ -16,7 +16,7 @@ use crate::heading_address::{self, Reachable, SectionIndex, SectionPos, SlicePath, Visible}; use crate::lua::{Library, LibraryBlock, LibraryKey, SectionText}; -use crate::parser::{Block, Prompt, Section, detail}; +use crate::prompt::{Block, Prompt, Section, detail}; use crate::{Error, Result}; /// The position of `target` in `slice`, matched on the parser-unique diff --git a/crates/runvil-internal/engine/src/heading_address-tests.rs b/crates/runvil-internal/engine/src/heading_address-tests.rs index 277d85386..2dbcfab1e 100644 --- a/crates/runvil-internal/engine/src/heading_address-tests.rs +++ b/crates/runvil-internal/engine/src/heading_address-tests.rs @@ -26,10 +26,10 @@ fn resolve_visible_bare_name_errors() { } fn sibling(name: &str, level: u8) -> Section { - runvil_parser::test_support::synthetic_section( + runvil_prompt::test_support::synthetic_section( name, level, - vec![runvil_parser::test_support::prose_block(String::new())], + vec![runvil_prompt::test_support::prose_block(String::new())], ) } @@ -110,8 +110,8 @@ fn self_pos() -> SectionPos { .child(SectionIndex::new(1)) } -fn tree() -> crate::parser::Prompt { - crate::parser::Prompt::parse(TREE, "reach-test") +fn tree() -> crate::prompt::Prompt { + crate::prompt::Prompt::parse(TREE, "reach-test") .0 .expect("the tree parses") } @@ -124,7 +124,7 @@ fn from_self<'a>(root: &'a [Section], pos: &'a SectionPos) -> Reachable<'a> { #[test] fn a_nested_section_reaches_its_children_siblings_and_each_ancestors_siblings() { let prompt = tree(); - let root = runvil_parser::detail::sections(&prompt); + let root = runvil_prompt::detail::sections(&prompt); let pos = self_pos(); let expected: [(&str, &[usize], usize); 5] = [ ("##### Child", &[1, 1, 1], 0), @@ -148,7 +148,7 @@ fn a_nested_section_reaches_its_children_siblings_and_each_ancestors_siblings() #[test] fn a_nested_section_reaches_no_grandchild_niece_cousin_itself_or_ancestor() { let prompt = tree(); - let root = runvil_parser::detail::sections(&prompt); + let root = runvil_prompt::detail::sections(&prompt); let pos = self_pos(); for heading in [ "###### Grandchild", @@ -166,7 +166,7 @@ fn a_nested_section_reaches_no_grandchild_niece_cousin_itself_or_ancestor() { #[test] fn the_h1_reaches_every_top_level_section_and_no_nested_one() { let prompt = tree(); - let root = runvil_parser::detail::sections(&prompt); + let root = runvil_prompt::detail::sections(&prompt); for heading in ["## Top", "## Grand", "## Last"] { let reached = resolve_reachable(heading, Reachable::top(root)) .unwrap_or_else(|error| panic!("{heading} must resolve: {error}")); @@ -185,7 +185,7 @@ fn the_h1_reaches_every_top_level_section_and_no_nested_one() { #[test] fn a_reachable_name_at_another_level_does_not_resolve() { let prompt = tree(); - let root = runvil_parser::detail::sections(&prompt); + let root = runvil_prompt::detail::sections(&prompt); let pos = self_pos(); for heading in ["### Sibling", "## Uncle", "#### Child", "### Top"] { let found = find_reachable(heading, from_self(root, &pos)).expect("a miss is not an error"); @@ -199,7 +199,7 @@ fn a_reachable_name_at_another_level_does_not_resolve() { #[test] fn a_reach_miss_lists_only_the_reachable_sections() { let prompt = tree(); - let root = runvil_parser::detail::sections(&prompt); + let root = runvil_prompt::detail::sections(&prompt); let pos = self_pos(); let error = resolve_reachable("## Missing", from_self(root, &pos)) .expect_err("an unknown heading must error"); @@ -213,7 +213,7 @@ fn a_reach_miss_lists_only_the_reachable_sections() { #[test] fn the_visible_set_stays_narrower_than_the_reach() { let prompt = tree(); - let root = runvil_parser::detail::sections(&prompt); + let root = runvil_prompt::detail::sections(&prompt); let slice = root[1].children()[1].children(); let visible = find_visible("### Uncle", Visible::of(slice, SectionIndex::new(1))) .expect("a miss is not an error"); diff --git a/crates/runvil-internal/engine/src/heading_address.rs b/crates/runvil-internal/engine/src/heading_address.rs index 448a43f4c..d7d91657c 100644 --- a/crates/runvil-internal/engine/src/heading_address.rs +++ b/crates/runvil-internal/engine/src/heading_address.rs @@ -21,7 +21,7 @@ //! A chain and a section's `prompt` reads find their slice by path and //! read it in place. -use crate::parser::{Prompt, Section, detail}; +use crate::prompt::{Prompt, Section, detail}; use crate::{Error, Result}; /// Where a sibling slice sits in the prompt tree: the index of each diff --git a/crates/runvil-internal/engine/src/lib.rs b/crates/runvil-internal/engine/src/lib.rs index 5865469c8..26f60e076 100644 --- a/crates/runvil-internal/engine/src/lib.rs +++ b/crates/runvil-internal/engine/src/lib.rs @@ -11,7 +11,7 @@ //! ## Invariants //! //! - May depend on: `runvil-types`, `runvil-lua`, -//! `runvil-parser`, `runvil-vfs`, and +//! `runvil-prompt`, `runvil-vfs`, and //! `runvil-model-client`. `cargo test -p build-xtask` enforces the //! product and container boundaries. @@ -20,7 +20,7 @@ mod execute; mod heading_address; mod lua; mod model; -mod parser; +mod prompt; mod subst; #[cfg(any(test, feature = "test-support"))] pub mod test_support; diff --git a/crates/runvil-internal/engine/src/lua/tests.rs b/crates/runvil-internal/engine/src/lua/tests.rs index 5b5651649..787d47db4 100644 --- a/crates/runvil-internal/engine/src/lua/tests.rs +++ b/crates/runvil-internal/engine/src/lua/tests.rs @@ -194,7 +194,7 @@ fn resume_with(vm: &SectionVm, thread: &Thread, answer: Answer) -> MultiV .expect("the coroutine accepts the answer") } -/// Compiles one author block the way the parser's prologue chunks are +/// Compiles one author block the way the loader's prologue chunks are /// compiled. #[track_caller] fn compile_block(source: &str) -> LuaProgram { diff --git a/crates/runvil-internal/engine/src/parser.rs b/crates/runvil-internal/engine/src/parser.rs deleted file mode 100644 index ad565ed72..000000000 --- a/crates/runvil-internal/engine/src/parser.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! The executor's import surface for `runvil-parser`, which parses a -//! prompt file into a [`Prompt`] tree; that crate documents the prompt -//! format. -//! -//! This module is the one surface production code imports that crate -//! through: an item or helper the Engine needs is re-exported here, and a -//! production module names `crate::parser` rather than `runvil_parser`. The -//! `detail` module is re-exported whole, since its helpers are the parser's -//! own algorithms, called where the rule each one implements is documented. -//! -//! Three groups still name `runvil_parser` itself: -//! -//! - `error::convert`, the error bridge, which maps the parser's error type -//! variant for variant. -//! - The test suites, which import the prompt fixtures under -//! `runvil_parser::test_support` and call a `detail` helper directly. -//! - The crate's bench, `benches/models_loop.rs`, a separate crate that -//! cannot see a `pub(crate)` surface. - -pub(crate) use runvil_parser::detail; -pub(crate) use runvil_parser::{ - Block, BodyText, ParseError, ParseErrorKind, Prompt, RoleRequirements, Section, -}; diff --git a/crates/runvil-internal/engine/src/prompt.rs b/crates/runvil-internal/engine/src/prompt.rs new file mode 100644 index 000000000..beede9a97 --- /dev/null +++ b/crates/runvil-internal/engine/src/prompt.rs @@ -0,0 +1,24 @@ +//! The executor's import surface for `runvil-prompt`, which loads a prompt +//! file into a ready-to-run [`Prompt`] tree; that crate documents the prompt +//! format. +//! +//! This module is the one surface production code imports that crate +//! through: an item or helper the Engine needs is re-exported here, and a +//! production module names `crate::prompt` rather than `runvil_prompt`. The +//! `detail` module is re-exported whole, since its helpers are the loaded +//! crate's own algorithms, called where the rule each one implements is +//! documented. +//! +//! Three groups still name `runvil_prompt` itself: +//! +//! - `error::convert`, the error bridge, which maps the loader's error type +//! variant for variant. +//! - The test suites, which import the prompt fixtures under +//! `runvil_prompt::test_support` and call a `detail` helper directly. +//! - The crate's bench, `benches/models_loop.rs`, a separate crate that +//! cannot see a `pub(crate)` surface. + +pub(crate) use runvil_prompt::detail; +pub(crate) use runvil_prompt::{ + Block, BodyText, ParseError, ParseErrorKind, Prompt, RoleRequirements, Section, +}; diff --git a/crates/runvil-internal/engine/src/test_support/scenario.rs b/crates/runvil-internal/engine/src/test_support/scenario.rs index ba796e321..3b4fd372c 100644 --- a/crates/runvil-internal/engine/src/test_support/scenario.rs +++ b/crates/runvil-internal/engine/src/test_support/scenario.rs @@ -36,7 +36,7 @@ use crate::lua::ToolBinding; use crate::model::{ CompletionErrorKind, ModelBinding, ModelId, ModelInfo, ModelRole, ModelSet, ThinkingMode, }; -use crate::parser::Prompt; +use crate::prompt::Prompt; #[path = "scenario-driving.rs"] mod driving; diff --git a/crates/runvil-internal/lua/AGENTS.md b/crates/runvil-internal/lua/AGENTS.md index fe2d3d81d..d2a0598fb 100644 --- a/crates/runvil-internal/lua/AGENTS.md +++ b/crates/runvil-internal/lua/AGENTS.md @@ -1,4 +1,4 @@ # runvil-lua -- Engine functions that would create a parser-to-Lua dependency cycle stay in this crate rather than `runvil-parser`. +- Engine functions that would create a parser-to-Lua dependency cycle stay in this crate rather than `runvil-prompt`. - `prepare_dispatch` is the single tool-dispatch body used by every executor: synchronous, it applies the succeeded/failed observation, trust classification, the nonce wrap, and the `ToolResult` report to a bound tool's answer; the executor counts each call when it issues it, and adhoc and discovery tools report in the executor. Nothing in this crate performs a tool call: the executor issues the call as an effect, the Harness performs it, and `prepare_dispatch` (or `prepare_model_dispatch` under the model-issued rule) applies the rules when the answer lands. diff --git a/crates/runvil-internal/parser/README.md b/crates/runvil-internal/parser/README.md deleted file mode 100644 index 9b283a352..000000000 --- a/crates/runvil-internal/parser/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Prompt parser - -This crate reads a Markdown prompt into a validated tree of frontmatter, sections, and compiled Lua code, and it reports each failure with a classified error. It does no execution. diff --git a/crates/runvil-internal/parser/Cargo.toml b/crates/runvil-internal/prompt/Cargo.toml similarity index 97% rename from crates/runvil-internal/parser/Cargo.toml rename to crates/runvil-internal/prompt/Cargo.toml index d50dabbf9..6344209f9 100644 --- a/crates/runvil-internal/parser/Cargo.toml +++ b/crates/runvil-internal/prompt/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "runvil-parser" +name = "runvil-prompt" version.workspace = true edition.workspace = true rust-version.workspace = true diff --git a/crates/runvil-internal/prompt/README.md b/crates/runvil-internal/prompt/README.md new file mode 100644 index 000000000..ece6bf035 --- /dev/null +++ b/crates/runvil-internal/prompt/README.md @@ -0,0 +1,3 @@ +# Prompt loader + +This crate loads a Markdown prompt into a ready-to-run tree of frontmatter, sections, and compiled Lua code, and it reports each failure with a classified error. Every Lua fence compiles here, so a loaded prompt cannot fail on a syntax error partway through a run. It does no execution. diff --git a/crates/runvil-internal/parser/build.rs b/crates/runvil-internal/prompt/build.rs similarity index 100% rename from crates/runvil-internal/parser/build.rs rename to crates/runvil-internal/prompt/build.rs diff --git a/crates/runvil-internal/parser/src/body_text-tests.rs b/crates/runvil-internal/prompt/src/body_text-tests.rs similarity index 100% rename from crates/runvil-internal/parser/src/body_text-tests.rs rename to crates/runvil-internal/prompt/src/body_text-tests.rs diff --git a/crates/runvil-internal/parser/src/body_text.rs b/crates/runvil-internal/prompt/src/body_text.rs similarity index 100% rename from crates/runvil-internal/parser/src/body_text.rs rename to crates/runvil-internal/prompt/src/body_text.rs diff --git a/crates/runvil-internal/parser/src/contract.rs b/crates/runvil-internal/prompt/src/contract.rs similarity index 100% rename from crates/runvil-internal/parser/src/contract.rs rename to crates/runvil-internal/prompt/src/contract.rs diff --git a/crates/runvil-internal/parser/src/contract/args.rs b/crates/runvil-internal/prompt/src/contract/args.rs similarity index 100% rename from crates/runvil-internal/parser/src/contract/args.rs rename to crates/runvil-internal/prompt/src/contract/args.rs diff --git a/crates/runvil-internal/parser/src/contract/models.rs b/crates/runvil-internal/prompt/src/contract/models.rs similarity index 100% rename from crates/runvil-internal/parser/src/contract/models.rs rename to crates/runvil-internal/prompt/src/contract/models.rs diff --git a/crates/runvil-internal/parser/src/contract/tests-packs.rs b/crates/runvil-internal/prompt/src/contract/tests-packs.rs similarity index 100% rename from crates/runvil-internal/parser/src/contract/tests-packs.rs rename to crates/runvil-internal/prompt/src/contract/tests-packs.rs diff --git a/crates/runvil-internal/parser/src/contract/tests.rs b/crates/runvil-internal/prompt/src/contract/tests.rs similarity index 100% rename from crates/runvil-internal/parser/src/contract/tests.rs rename to crates/runvil-internal/prompt/src/contract/tests.rs diff --git a/crates/runvil-internal/parser/src/detail.rs b/crates/runvil-internal/prompt/src/detail.rs similarity index 100% rename from crates/runvil-internal/parser/src/detail.rs rename to crates/runvil-internal/prompt/src/detail.rs diff --git a/crates/runvil-internal/parser/src/error-tests.rs b/crates/runvil-internal/prompt/src/error-tests.rs similarity index 100% rename from crates/runvil-internal/parser/src/error-tests.rs rename to crates/runvil-internal/prompt/src/error-tests.rs diff --git a/crates/runvil-internal/parser/src/error.rs b/crates/runvil-internal/prompt/src/error.rs similarity index 100% rename from crates/runvil-internal/parser/src/error.rs rename to crates/runvil-internal/prompt/src/error.rs diff --git a/crates/runvil-internal/parser/src/fence.rs b/crates/runvil-internal/prompt/src/fence.rs similarity index 100% rename from crates/runvil-internal/parser/src/fence.rs rename to crates/runvil-internal/prompt/src/fence.rs diff --git a/crates/runvil-internal/parser/src/frontmatter.rs b/crates/runvil-internal/prompt/src/frontmatter.rs similarity index 100% rename from crates/runvil-internal/parser/src/frontmatter.rs rename to crates/runvil-internal/prompt/src/frontmatter.rs diff --git a/crates/runvil-internal/parser/src/lib.rs b/crates/runvil-internal/prompt/src/lib.rs similarity index 87% rename from crates/runvil-internal/parser/src/lib.rs rename to crates/runvil-internal/prompt/src/lib.rs index 0ebe57cb6..393fc06bc 100644 --- a/crates/runvil-internal/parser/src/lib.rs +++ b/crates/runvil-internal/prompt/src/lib.rs @@ -1,4 +1,4 @@ -//! Prompt file parser. +//! Loads a prompt file: its frontmatter, its Markdown structure, and its Lua. //! //! A prompt is one markdown file: YAML frontmatter, a required H1, optional H1 //! blocks, then H2 sections. @@ -14,7 +14,8 @@ //! normally. Markdown left after the final `lua` fence is inert trailing //! commentary, never an error. //! -//! The parser turns bytes into a [`Prompt`] tree without executing any of it. +//! Every `lua` fence compiles at load time, so a loaded [`Prompt`] is ready to +//! run: a syntax error surfaces here, never partway through a run. //! //! ## Invariants //! diff --git a/crates/runvil-internal/parser/src/list.rs b/crates/runvil-internal/prompt/src/list.rs similarity index 100% rename from crates/runvil-internal/parser/src/list.rs rename to crates/runvil-internal/prompt/src/list.rs diff --git a/crates/runvil-internal/parser/src/parse.rs b/crates/runvil-internal/prompt/src/parse.rs similarity index 100% rename from crates/runvil-internal/parser/src/parse.rs rename to crates/runvil-internal/prompt/src/parse.rs diff --git a/crates/runvil-internal/parser/src/prompt.rs b/crates/runvil-internal/prompt/src/prompt.rs similarity index 100% rename from crates/runvil-internal/parser/src/prompt.rs rename to crates/runvil-internal/prompt/src/prompt.rs diff --git a/crates/runvil-internal/parser/src/sections-tests.rs b/crates/runvil-internal/prompt/src/sections-tests.rs similarity index 100% rename from crates/runvil-internal/parser/src/sections-tests.rs rename to crates/runvil-internal/prompt/src/sections-tests.rs diff --git a/crates/runvil-internal/parser/src/sections.rs b/crates/runvil-internal/prompt/src/sections.rs similarity index 100% rename from crates/runvil-internal/parser/src/sections.rs rename to crates/runvil-internal/prompt/src/sections.rs diff --git a/crates/runvil-internal/parser/src/test_support.rs b/crates/runvil-internal/prompt/src/test_support.rs similarity index 100% rename from crates/runvil-internal/parser/src/test_support.rs rename to crates/runvil-internal/prompt/src/test_support.rs diff --git a/crates/runvil-internal/parser/src/tests.rs b/crates/runvil-internal/prompt/src/tests.rs similarity index 100% rename from crates/runvil-internal/parser/src/tests.rs rename to crates/runvil-internal/prompt/src/tests.rs diff --git a/crates/runvil-internal/parser/src/tests/breaks.rs b/crates/runvil-internal/prompt/src/tests/breaks.rs similarity index 100% rename from crates/runvil-internal/parser/src/tests/breaks.rs rename to crates/runvil-internal/prompt/src/tests/breaks.rs diff --git a/crates/runvil-internal/parser/src/tests/frontmatter.rs b/crates/runvil-internal/prompt/src/tests/frontmatter.rs similarity index 100% rename from crates/runvil-internal/parser/src/tests/frontmatter.rs rename to crates/runvil-internal/prompt/src/tests/frontmatter.rs diff --git a/crates/runvil-internal/parser/src/tests/h1_fences.rs b/crates/runvil-internal/prompt/src/tests/h1_fences.rs similarity index 100% rename from crates/runvil-internal/parser/src/tests/h1_fences.rs rename to crates/runvil-internal/prompt/src/tests/h1_fences.rs diff --git a/crates/runvil-internal/parser/src/tests/headings.rs b/crates/runvil-internal/prompt/src/tests/headings.rs similarity index 100% rename from crates/runvil-internal/parser/src/tests/headings.rs rename to crates/runvil-internal/prompt/src/tests/headings.rs diff --git a/crates/runvil-internal/parser/src/tests/line_mapping.rs b/crates/runvil-internal/prompt/src/tests/line_mapping.rs similarity index 100% rename from crates/runvil-internal/parser/src/tests/line_mapping.rs rename to crates/runvil-internal/prompt/src/tests/line_mapping.rs diff --git a/crates/runvil-internal/parser/src/tests/lists.rs b/crates/runvil-internal/prompt/src/tests/lists.rs similarity index 100% rename from crates/runvil-internal/parser/src/tests/lists.rs rename to crates/runvil-internal/prompt/src/tests/lists.rs diff --git a/crates/runvil-internal/parser/src/tests/section_fences.rs b/crates/runvil-internal/prompt/src/tests/section_fences.rs similarity index 100% rename from crates/runvil-internal/parser/src/tests/section_fences.rs rename to crates/runvil-internal/prompt/src/tests/section_fences.rs diff --git a/crates/runvil/Cargo.toml b/crates/runvil/Cargo.toml index 3f999664b..5ab9ab071 100644 --- a/crates/runvil/Cargo.toml +++ b/crates/runvil/Cargo.toml @@ -13,7 +13,7 @@ publish = false runvil-engine.workspace = true runvil-lua.workspace = true runvil-model-client.workspace = true -runvil-parser.workspace = true +runvil-prompt.workspace = true runvil-types.workspace = true runvil-vfs.workspace = true diff --git a/crates/runvil/src/lib.rs b/crates/runvil/src/lib.rs index 8714b0960..83ce27526 100644 --- a/crates/runvil/src/lib.rs +++ b/crates/runvil/src/lib.rs @@ -20,9 +20,9 @@ pub use runvil_engine::RunErrorKind; pub use runvil_engine::RunLimits; pub use runvil_engine::RunOutcome; pub use runvil_engine::Step; -pub use runvil_parser::ParseError; -pub use runvil_parser::ParseErrorKind; -pub use runvil_parser::Prompt; +pub use runvil_prompt::ParseError; +pub use runvil_prompt::ParseErrorKind; +pub use runvil_prompt::Prompt; pub use runvil_types::seed::RunSeed; pub mod effect { @@ -116,13 +116,13 @@ pub mod prompt { //! What a prompt's frontmatter declares: its files, Packs, //! arguments, and model roles. - pub use runvil_parser::ArgDecl; - pub use runvil_parser::ArgType; - pub use runvil_parser::ArgsDecl; - pub use runvil_parser::ArgsDeclIter; - pub use runvil_parser::FileDecl; - pub use runvil_parser::Frontmatter; - pub use runvil_parser::RoleRequirements; + pub use runvil_prompt::ArgDecl; + pub use runvil_prompt::ArgType; + pub use runvil_prompt::ArgsDecl; + pub use runvil_prompt::ArgsDeclIter; + pub use runvil_prompt::FileDecl; + pub use runvil_prompt::Frontmatter; + pub use runvil_prompt::RoleRequirements; } pub mod vfs { From 261872a9402e36c28130983ea3d746a427314754 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 1 Nov 2026 07:46:37 -0800 Subject: [PATCH 07/14] build-llama-cuda is no longer part of the repository --- vibelight.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/vibelight.toml b/vibelight.toml index ce975e7b5..f591e3779 100644 --- a/vibelight.toml +++ b/vibelight.toml @@ -4,7 +4,6 @@ workspace = true follow = ["**"] exclude = [ "crates/build-ceiling", # build tooling - "crates/build-llama-cuda", # build tooling "crates/build-ui", # build tooling "crates/build-user-guide", # build tooling "crates/build-workshop", # run by the cargo alias `workshop` From 91a9c7d12e92a105b06d1b14da7b2f332257bc15 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 1 Nov 2026 10:04:03 -0800 Subject: [PATCH 08/14] Move source position helpers into their own module The fence splitter imported four helpers from the section walker, and the walker imports the splitter. The helpers now live in `position`, and the splitter no longer names `sections`. - `parse` takes `line_add` from `position` as well. - The moved functions keep their bodies, signatures, and error messages. --- crates/runvil-internal/prompt/src/fence.rs | 10 ++-- crates/runvil-internal/prompt/src/lib.rs | 1 + crates/runvil-internal/prompt/src/parse.rs | 5 +- crates/runvil-internal/prompt/src/position.rs | 55 +++++++++++++++++++ .../prompt/src/sections-tests.rs | 2 +- crates/runvil-internal/prompt/src/sections.rs | 49 +---------------- 6 files changed, 66 insertions(+), 56 deletions(-) create mode 100644 crates/runvil-internal/prompt/src/position.rs diff --git a/crates/runvil-internal/prompt/src/fence.rs b/crates/runvil-internal/prompt/src/fence.rs index 3908dcc62..8341a47f5 100644 --- a/crates/runvil-internal/prompt/src/fence.rs +++ b/crates/runvil-internal/prompt/src/fence.rs @@ -15,7 +15,7 @@ use std::sync::Arc; use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag}; use runvil_types::emitter::Emitter; -use crate::{Block, Error, ParseErrorKind, Result, sections}; +use crate::{Block, Error, ParseErrorKind, Result, position}; use runvil_lua::LuaProgram; /// Uncompiled block produced while scanning section content. @@ -48,18 +48,18 @@ pub(super) fn split_h1( for raw in raw_blocks { match raw { RawBlock::Prose(text) => blocks.push(Block::Prose { - text: sections::body_text(body, text)?, + text: position::body_text(body, text)?, }), RawBlock::Lua { source, line_offset, } => { let location = format!("H1 `{title}` lua"); - let line = sections::line_add(content_abs_line, line_offset)?; + let line = position::line_add(content_abs_line, line_offset)?; blocks.push(Block::Lua(LuaProgram::compile( &source, &location, - sections::nz_source_line(line)?, + position::nz_source_line(line)?, emitter, title, )?)); @@ -353,7 +353,7 @@ pub(super) fn split_section_blocks<'a>( blocks.push(RawBlock::Prose(pending.trim())); } - let line_offset = sections::line_add(sections::newlines_before(content, opening)?, 1)?; + let line_offset = position::line_add(position::newlines_before(content, opening)?, 1)?; blocks.push(RawBlock::Lua { source, line_offset, diff --git a/crates/runvil-internal/prompt/src/lib.rs b/crates/runvil-internal/prompt/src/lib.rs index 393fc06bc..1a40cd632 100644 --- a/crates/runvil-internal/prompt/src/lib.rs +++ b/crates/runvil-internal/prompt/src/lib.rs @@ -31,6 +31,7 @@ mod fence; mod frontmatter; mod list; mod parse; +mod position; mod prompt; mod sections; diff --git a/crates/runvil-internal/prompt/src/parse.rs b/crates/runvil-internal/prompt/src/parse.rs index c4bceddf5..52d408db2 100644 --- a/crates/runvil-internal/prompt/src/parse.rs +++ b/crates/runvil-internal/prompt/src/parse.rs @@ -8,6 +8,7 @@ use runvil_types::event::{Event, lifecycle}; use crate::contract::{self, PackListError}; use crate::fence::{self, FenceViolation}; +use crate::position; use crate::sections::{self, Heading}; use crate::{Error, Frontmatter, ParseError, ParseErrorKind, Prompt, Result, frontmatter}; @@ -136,7 +137,7 @@ impl Prompt { let lines = h1_positions .iter() .map(|&index| { - sections::line_add(frontmatter_lines, headings[index].source_line) + position::line_add(frontmatter_lines, headings[index].source_line) .map(|line| line.to_string()) }) .collect::>>()?; @@ -157,7 +158,7 @@ impl Prompt { )); } let title = h1.title.clone(); - let h1_content_abs_line = sections::line_add(frontmatter_lines, h1.content_start_line)?; + let h1_content_abs_line = position::line_add(frontmatter_lines, h1.content_start_line)?; if let Some((line, violation)) = fence::first_fence_violation(body) { let message = match violation { FenceViolation::Label(label) => { diff --git a/crates/runvil-internal/prompt/src/position.rs b/crates/runvil-internal/prompt/src/position.rs new file mode 100644 index 000000000..a2636914a --- /dev/null +++ b/crates/runvil-internal/prompt/src/position.rs @@ -0,0 +1,55 @@ +//! Source positions: 1-based line arithmetic, and the location of a stretch of +//! the shared prompt body. The fence splitter, the section walker, and the +//! parser all use these, so none of them has to name another for them. + +use std::num::NonZeroU32; +use std::sync::Arc; + +use crate::{BodyText, Error, Result}; + +/// Wraps a computed 1-based source line for the Lua compiler. +/// +/// Line numbers computed during parsing are always at least 1. A zero here means +/// a broken source-position invariant; rather than silently coercing it to line +/// one, this reports a structured internal parse error. +/// +/// # Errors +/// Returns [`Error::Internal`] when `line` is zero. +pub(crate) fn nz_source_line(line: u32) -> Result { + NonZeroU32::new(line).ok_or(Error::Internal( + "parser: computed 1-based source line was zero", + )) +} + +/// Adds two 1-based line components with overflow checking. +/// +/// # Errors +/// Returns [`Error::Internal`] when the sum overflows `u32`, rather than +/// saturating and hiding a broken position. +pub(crate) fn line_add(a: u32, b: u32) -> Result { + a.checked_add(b) + .ok_or(Error::Internal("parser: source line arithmetic overflowed")) +} + +/// Locates `part` inside the shared `text` it was sliced from. +/// +/// # Errors +/// Returns [`Error::Internal`] when `part` is not a slice of `text`. +pub(crate) fn body_text(text: &Arc, part: &str) -> Result { + BodyText::within(text, part).ok_or(Error::Internal( + "parser: section text is not a slice of the prompt body", + )) +} + +/// Counts the number of `\n` characters in `text[..byte_offset]`. +/// +/// # Errors +/// Returns [`Error::Internal`] when `byte_offset` is out of bounds or not a +/// UTF-8 boundary, or when the newline count exceeds `u32`. +pub(crate) fn newlines_before(text: &str, byte_offset: usize) -> Result { + let prefix = text.get(..byte_offset).ok_or(Error::Internal( + "parser: source byte offset invariant broken", + ))?; + u32::try_from(prefix.matches('\n').count()) + .map_err(|_| Error::Internal("parser: newline count exceeded u32 range")) +} diff --git a/crates/runvil-internal/prompt/src/sections-tests.rs b/crates/runvil-internal/prompt/src/sections-tests.rs index 4d4e974ba..9d9648576 100644 --- a/crates/runvil-internal/prompt/src/sections-tests.rs +++ b/crates/runvil-internal/prompt/src/sections-tests.rs @@ -1,7 +1,7 @@ //! Tests for the source-position helpers. -use super::newlines_before; use crate::Error; +use crate::position::newlines_before; #[test] fn newlines_before_reports_broken_byte_offset_invariants() { diff --git a/crates/runvil-internal/prompt/src/sections.rs b/crates/runvil-internal/prompt/src/sections.rs index 819695ffa..301a3e825 100644 --- a/crates/runvil-internal/prompt/src/sections.rs +++ b/crates/runvil-internal/prompt/src/sections.rs @@ -1,6 +1,5 @@ //! Walks a prompt body's markdown headings and builds its [`Section`] tree. -use std::num::NonZeroU32; use std::ops::Range; use std::sync::Arc; @@ -8,43 +7,10 @@ use pulldown_cmark::{Event, HeadingLevel, Options, Parser, Tag, TagEnd}; use runvil_types::emitter::Emitter; use crate::fence::{self, RawBlock}; +use crate::position::{body_text, line_add, newlines_before, nz_source_line}; use crate::{Block, BodyText, Error, ParseErrorKind, Result, Section, list}; use runvil_lua::LuaProgram; -/// Wraps a computed 1-based source line for [`LuaProgram::compile`]. -/// -/// Line numbers computed during parsing are always at least 1. A zero here means -/// a broken source-position invariant; rather than silently coercing it to line -/// one, this reports a structured internal parse error. -/// -/// # Errors -/// Returns [`Error::Internal`] when `line` is zero. -pub(crate) fn nz_source_line(line: u32) -> Result { - NonZeroU32::new(line).ok_or(Error::Internal( - "parser: computed 1-based source line was zero", - )) -} - -/// Adds two 1-based line components with overflow checking. -/// -/// # Errors -/// Returns [`Error::Internal`] when the sum overflows `u32`, rather than -/// saturating and hiding a broken position. -pub(crate) fn line_add(a: u32, b: u32) -> Result { - a.checked_add(b) - .ok_or(Error::Internal("parser: source line arithmetic overflowed")) -} - -/// Locates `part` inside the shared `text` it was sliced from. -/// -/// # Errors -/// Returns [`Error::Internal`] when `part` is not a slice of `text`. -pub(crate) fn body_text(text: &Arc, part: &str) -> Result { - BodyText::within(text, part).ok_or(Error::Internal( - "parser: section text is not a slice of the prompt body", - )) -} - /// A heading with its title and the prose/Lua that follows it (before the next /// heading of any level). #[derive(Debug, Clone)] @@ -60,19 +26,6 @@ pub(crate) struct Heading<'a> { span: Range, } -/// Counts the number of `\n` characters in `text[..byte_offset]`. -/// -/// # Errors -/// Returns [`Error::Internal`] when `byte_offset` is out of bounds or not a -/// UTF-8 boundary, or when the newline count exceeds `u32`. -pub(crate) fn newlines_before(text: &str, byte_offset: usize) -> Result { - let prefix = text.get(..byte_offset).ok_or(Error::Internal( - "parser: source byte offset invariant broken", - ))?; - u32::try_from(prefix.matches('\n').count()) - .map_err(|_| Error::Internal("parser: newline count exceeded u32 range")) -} - /// Converts a `HeadingLevel` to its numeric level. fn level_num(level: HeadingLevel) -> u8 { match level { From dea79c46c151d48c20a592ecb5d6af612867fbd7 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 1 Nov 2026 10:05:44 -0800 Subject: [PATCH 09/14] Read Prompt fields directly in detail `detail` lives in the same crate as `Prompt`, so `Prompt::h1_blocks` and `Prompt::sections` only repeated a field read. The two `detail` functions now read the fields, so each field has one access path. - `Prompt::entry` stays, because the test suites call it. - The `detail` signatures and the Engine's calls are unchanged. --- crates/runvil-internal/prompt/src/detail.rs | 4 ++-- crates/runvil-internal/prompt/src/prompt.rs | 14 -------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/crates/runvil-internal/prompt/src/detail.rs b/crates/runvil-internal/prompt/src/detail.rs index e4a5e8628..369bd6b24 100644 --- a/crates/runvil-internal/prompt/src/detail.rs +++ b/crates/runvil-internal/prompt/src/detail.rs @@ -41,13 +41,13 @@ pub fn with_prompt_name(error: ParseError, name: &str) -> ParseError { /// Returns the ordered live Lua and prose blocks from the H1. #[must_use] pub fn h1_blocks(prompt: &Prompt) -> &[Block] { - prompt.h1_blocks() + &prompt.h1_blocks } /// Returns the top-level H2 sections in file order. #[must_use] pub fn sections(prompt: &Prompt) -> &[Section] { - prompt.sections() + &prompt.sections } /// The entry-point section: the first top-level section in file order. diff --git a/crates/runvil-internal/prompt/src/prompt.rs b/crates/runvil-internal/prompt/src/prompt.rs index 8f78d1f52..0ac9e1781 100644 --- a/crates/runvil-internal/prompt/src/prompt.rs +++ b/crates/runvil-internal/prompt/src/prompt.rs @@ -161,20 +161,6 @@ impl Prompt { &self.title } - /// Returns the ordered live Lua and prose blocks from the H1; the Engine - /// reaches them through [`crate::detail::h1_blocks`]. - #[must_use] - pub(crate) fn h1_blocks(&self) -> &[Block] { - &self.h1_blocks - } - - /// Returns the top-level H2 sections in file order; the Engine reaches - /// them through [`crate::detail::sections`]. - #[must_use] - pub(crate) fn sections(&self) -> &[Section] { - &self.sections - } - /// The entry-point section: the first top-level section in file order; /// the Engine reaches it through [`crate::detail::entry`]. #[must_use] From 5fac83880941c246ca03f8e8e97a11bd33e5ef24 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 1 Nov 2026 10:07:44 -0800 Subject: [PATCH 10/14] Pass the map key name instead of ContractKeys `ContractKeys` held one string and was built at two call sites, so it carried no fact of its own. The map deserializer and its visitor now take the key name directly. - Error messages keep their wording: `model role` for `models` and `arg name` for `args`. --- crates/runvil-internal/prompt/src/contract.rs | 26 ++++++------------- .../prompt/src/contract/args.rs | 5 ++-- .../prompt/src/contract/models.rs | 5 ++-- 3 files changed, 12 insertions(+), 24 deletions(-) diff --git a/crates/runvil-internal/prompt/src/contract.rs b/crates/runvil-internal/prompt/src/contract.rs index 51f80913f..ff6a68e06 100644 --- a/crates/runvil-internal/prompt/src/contract.rs +++ b/crates/runvil-internal/prompt/src/contract.rs @@ -45,13 +45,6 @@ fn is_valid_alias(alias: &str) -> bool { .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-')) } -/// How one contract map's keys are described in its errors. -#[derive(Clone, Copy)] -struct ContractKeys { - /// The key kind (`model role`), for error messages. - what: &'static str, -} - /// A key of a contract map: how it is checked and how an error names it. trait ContractKey: Ord { /// Refuses a key the map's grammar does not allow. @@ -89,10 +82,11 @@ impl ContractKey for ModelRole { } /// Deserializes a contract map (`models`, `args`): keys of type `K` checked -/// by [`ContractKey`], values deserialized as `T`, duplicates rejected. +/// by [`ContractKey`], values deserialized as `T`, duplicates rejected. `what` +/// names the keys in every error. fn deserialize_contract_map<'de, D, K, T>( deserializer: D, - keys: ContractKeys, + what: &'static str, ) -> Result, D::Error> where D: Deserializer<'de>, @@ -100,7 +94,7 @@ where T: Deserialize<'de>, { deserializer.deserialize_map(MapVisitor { - keys, + what, _marker: PhantomData, }) } @@ -108,8 +102,8 @@ where /// The visitor behind [`deserialize_contract_map`]: a streaming map walk so /// rejections keep their source position. struct MapVisitor { - /// The checks the map's keys get. - keys: ContractKeys, + /// The key kind (`model role` or `arg name`), for error messages. + what: &'static str, /// The key and value types, without ownership or variance claims. _marker: PhantomData (K, T)>, } @@ -122,18 +116,14 @@ where type Value = BTreeMap; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - formatter, - "a map of {} keys to declarations", - self.keys.what - ) + write!(formatter, "a map of {} keys to declarations", self.what) } fn visit_map(self, mut map: A) -> Result where A: MapAccess<'de>, { - let ContractKeys { what } = self.keys; + let what = self.what; let mut entries: BTreeMap = BTreeMap::new(); while let Some(key) = map.next_key::()? { key.check(what).map_err(de::Error::custom)?; diff --git a/crates/runvil-internal/prompt/src/contract/args.rs b/crates/runvil-internal/prompt/src/contract/args.rs index c84b03d25..8dd5c634e 100644 --- a/crates/runvil-internal/prompt/src/contract/args.rs +++ b/crates/runvil-internal/prompt/src/contract/args.rs @@ -9,7 +9,7 @@ use std::collections::{BTreeMap, btree_map}; use serde::Deserialize; -use crate::contract::{self, ContractKeys}; +use crate::contract; /// The type of a declared prompt arg. /// @@ -268,8 +268,7 @@ impl<'de> Deserialize<'de> for ArgsDecl { { // Arg names are `argv` fields, never globals, so a reserved name // such as the default declaration's `prose` is a valid arg name. - let fields = - contract::deserialize_contract_map(deserializer, ContractKeys { what: "arg name" })?; + let fields = contract::deserialize_contract_map(deserializer, "arg name")?; Ok(ArgsDecl { fields, implicit: false, diff --git a/crates/runvil-internal/prompt/src/contract/models.rs b/crates/runvil-internal/prompt/src/contract/models.rs index 98fe121d7..7634bdd3c 100644 --- a/crates/runvil-internal/prompt/src/contract/models.rs +++ b/crates/runvil-internal/prompt/src/contract/models.rs @@ -14,7 +14,7 @@ use std::num::NonZeroU32; use runvil_types::models::ModelRole; use serde::Deserialize; -use crate::contract::{self, ContractKeys}; +use crate::contract; /// The requirements a prompt puts on one model role. /// @@ -69,8 +69,7 @@ impl<'de> Deserialize<'de> for RoleMap { where D: serde::Deserializer<'de>, { - let roles = - contract::deserialize_contract_map(deserializer, ContractKeys { what: "model role" })?; + let roles = contract::deserialize_contract_map(deserializer, "model role")?; Ok(RoleMap { roles }) } } From 50226fbf48c10f5c08f9063dc9f57ac9c038feb3 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 1 Nov 2026 10:14:32 -0800 Subject: [PATCH 11/14] Compute ParseError facts from its inner error `ParseError` copied its kind, span, line, and column out of the inner error at construction, so the same facts were stored twice. The accessors now compute them from the inner error on each call. `name` matches the inner error directly, because it returns a borrow. - The inner error does not change after construction, so the computed values match the copied ones. - `Classification` no longer carries the frontmatter name, since only `name` reads it and `name` no longer uses the classification. - The public accessors keep their signatures. --- crates/runvil-internal/prompt/src/error.rs | 33 +++++++--------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/crates/runvil-internal/prompt/src/error.rs b/crates/runvil-internal/prompt/src/error.rs index 4d00f03a7..15a7f24ea 100644 --- a/crates/runvil-internal/prompt/src/error.rs +++ b/crates/runvil-internal/prompt/src/error.rs @@ -225,24 +225,17 @@ pub enum ParseErrorKind { #[derive(Debug)] #[non_exhaustive] pub struct ParseError { - kind: ParseErrorKind, - span: Option>, - name: Option, - line: Option, - column: Option, inner: Box, } /// The classified parts of an internal error: the stable kind plus the /// location fields the internal error holds. /// -/// The location fields are the source span, the prompt's frontmatter name -/// when the failure postdates the frontmatter, and the 1-based line/column - +/// The location fields are the source span and the 1-based line/column - /// surfaced from the retained YAML failure, or computed from the span. struct Classification { kind: ParseErrorKind, span: Option>, - name: Option, line: Option, column: Option, } @@ -252,7 +245,6 @@ fn classify_parse_error(inner: &Error) -> Classification { let unlocated = |kind| Classification { kind, span: None, - name: None, line: None, column: None, }; @@ -260,14 +252,12 @@ fn classify_parse_error(inner: &Error) -> Classification { Error::ParseStructured { kind, span, - name, line, column, .. } => Classification { kind: *kind, span: span.clone(), - name: name.clone(), line: *line, column: *column, }, @@ -290,7 +280,7 @@ impl ParseError { /// Returns the stable classification of this failure. #[must_use] pub fn kind(&self) -> ParseErrorKind { - self.kind + classify_parse_error(&self.inner).kind } /// Returns the byte span of the offending region, when one is available. @@ -303,7 +293,7 @@ impl ParseError { /// [`column`](ParseError::column). #[must_use] pub fn span(&self) -> Option> { - self.span.clone() + classify_parse_error(&self.inner).span } /// Returns the prompt's frontmatter name, when the failure came after the @@ -315,7 +305,10 @@ impl ParseError { /// also returns `None`. #[must_use] pub fn name(&self) -> Option<&str> { - self.name.as_deref() + match &*self.inner { + Error::ParseStructured { name, .. } => name.as_deref(), + _ => None, + } } /// Returns the 1-based file line of the failure, when known. @@ -325,13 +318,13 @@ impl ParseError { /// span starts. #[must_use] pub fn line(&self) -> Option { - self.line + classify_parse_error(&self.inner).line } /// Returns the 1-based column of the failure, when known. #[must_use] pub fn column(&self) -> Option { - self.column + classify_parse_error(&self.inner).column } /// Returns the originating error this failure keeps, when it has one. @@ -352,15 +345,9 @@ impl ParseError { *self.inner } - /// Classifies the internal error into its public wrapper. + /// Wraps the internal error in its public form. pub(crate) fn from_inner(inner: Error) -> ParseError { - let classified = classify_parse_error(&inner); ParseError { - kind: classified.kind, - span: classified.span, - name: classified.name, - line: classified.line, - column: classified.column, inner: Box::new(inner), } } From 213dbe21f4a73cf73dceeb2e0c1af2a2ccaa0410 Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 1 Nov 2026 10:17:57 -0800 Subject: [PATCH 12/14] Find the closing fence line with one shape test The fence splitter searched for an exact closing line, while the validator used a shape test to find the closing line. The splitter now uses the same shape test, and it reports an internal error when the line it finds is not exactly three backticks. - A validated prompt refuses any such line before a split, so its blocks are unchanged. - A direct split that meets an indented or four-backtick closing line now returns an internal error at that line. It used to skip the line and keep reading. - The test `a_fence_split_that_disagrees_with_the_markdown_parser_is_an_internal_error` passes without change. --- crates/runvil-internal/prompt/src/fence.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/runvil-internal/prompt/src/fence.rs b/crates/runvil-internal/prompt/src/fence.rs index 8341a47f5..62395932f 100644 --- a/crates/runvil-internal/prompt/src/fence.rs +++ b/crates/runvil-internal/prompt/src/fence.rs @@ -88,14 +88,21 @@ pub(crate) fn trim_leading_blank_lines(content: &str) -> &str { &content[offset..] } -/// Extracts source through the first exact, unindented triple-backtick closing -/// line. +/// Extracts source through the first closing line, the line the Markdown parser +/// closes the fence on. The validator refuses any closing line that is not +/// exactly three backticks before a split runs, so a split that meets one has +/// broken an invariant, and it reports an internal error. fn extract_exact_fence<'a>(content: &'a str, label: &str) -> Result<(String, &'a str)> { let mut offset = 0; for line in content.split_inclusive('\n') { let text = line.strip_suffix('\n').unwrap_or(line); let text = text.strip_suffix('\r').unwrap_or(text); - if text == "```" { + if closes_three_backtick_fence(text) { + if text != "```" { + return Err(Error::Internal( + "parser: fence split met a closing line the validator refuses", + )); + } let source = content[..offset].trim_end_matches(['\r', '\n']).to_string(); return Ok((source, &content[offset + line.len()..])); } @@ -236,7 +243,7 @@ fn closing_line(body: &str, extent: Range) -> Option> { /// Whether `line` has the shape of a line that closes a fence opened by three /// backticks: at most three spaces of indent, three or more backticks, then -/// only spaces or tabs. +/// only spaces or tabs. The splitter and the validator both use this one rule. fn closes_three_backtick_fence(line: &str) -> bool { let unindented = line.trim_start_matches(' '); let marker = unindented.trim_start_matches('`'); From 3937b14991c7320bbf209fc1dd2cdb2359317e7c Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 1 Nov 2026 10:19:50 -0800 Subject: [PATCH 13/14] Clarify the position module comment The module comment said that none of the modules had to name another, which did not say what the dependency rule was. It now says that the fence splitter does not depend on the section walker. --- crates/runvil-internal/prompt/src/position.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/runvil-internal/prompt/src/position.rs b/crates/runvil-internal/prompt/src/position.rs index a2636914a..db0d83b85 100644 --- a/crates/runvil-internal/prompt/src/position.rs +++ b/crates/runvil-internal/prompt/src/position.rs @@ -1,6 +1,6 @@ //! Source positions: 1-based line arithmetic, and the location of a stretch of //! the shared prompt body. The fence splitter, the section walker, and the -//! parser all use these, so none of them has to name another for them. +//! parser all use these, so the splitter does not depend on the walker. use std::num::NonZeroU32; use std::sync::Arc; From 3780ebae1494a98a927dfea297fc67c59e53365d Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Sun, 1 Nov 2026 18:01:53 -0800 Subject: [PATCH 14/14] Build runvil-prompt in the MSRV job The MSRV job named the crate runvil-parser. No package has that name, so cargo failed. The job now names runvil-prompt. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1733c637..31c8d90df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -343,7 +343,7 @@ jobs: uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2 - name: Build the Engine crates at the floor - run: cargo build --locked -p runvil -p runvil-engine -p runvil-lua -p runvil-parser -p runvil-types -p runvil-vfs -p runvil-model-client + run: cargo build --locked -p runvil -p runvil-engine -p runvil-lua -p runvil-prompt -p runvil-types -p runvil-vfs -p runvil-model-client # Single required status check for branch protection: it fails when any # job fails or is cancelled, so a cancelled job cannot read as green.