nemo_fabric_cli/
examples.rs1use std::collections::BTreeMap;
7use std::path::{Path, PathBuf};
8
9use nemo_fabric_core::{DiscoveryConfig, FabricConfig, MetadataConfig, SkillConfig};
10
11use crate::assets::{EmbeddedFile, StagedAssets};
12use crate::presets::{self, Preset};
13
14pub(crate) const CODE_REVIEW_WORKSPACE: &str =
15 include_str!("../assets/examples/code-review/repo/calculator.py");
16pub(crate) const CODE_REVIEW_SKILL: &str =
17 include_str!("../assets/examples/code-review/skills/code-review.md");
18
19const CODE_REVIEW_FILES: &[EmbeddedFile] = &[
20 EmbeddedFile {
21 path: "repo/calculator.py",
22 contents: CODE_REVIEW_WORKSPACE,
23 },
24 EmbeddedFile {
25 path: "skills/code-review.md",
26 contents: CODE_REVIEW_SKILL,
27 },
28];
29const CODE_REVIEW_VARIANTS: &[&str] = &["scripted", "hermes", "claude", "codex", "deepagents"];
30
31#[derive(Debug, Clone, Copy)]
33pub struct Example {
34 pub name: &'static str,
36 pub description: &'static str,
38 pub default_variant: &'static str,
40 pub variants: &'static [&'static str],
42}
43
44impl Example {
45 fn preset(self, variant: Option<&str>) -> Result<Preset, String> {
46 let variant = variant.unwrap_or(self.default_variant);
47 if !self.variants.contains(&variant) {
48 return Err(format!(
49 "unknown variant {variant:?} for example {:?}; available: {}",
50 self.name,
51 self.variants.join(", ")
52 ));
53 }
54 presets::find(variant).ok_or_else(|| {
55 format!(
56 "example {:?} references missing preset {variant:?}",
57 self.name
58 )
59 })
60 }
61
62 pub fn config(self, variant: Option<&str>) -> Result<FabricConfig, String> {
64 code_review_config(self.preset(variant)?)
65 }
66
67 pub fn select(self, variant: Option<&str>) -> Result<SelectedExample, String> {
69 let preset = self.preset(variant)?;
70 let config = code_review_config(preset)?;
71 let files = self.embedded_files(preset);
72 let assets = StagedAssets::create(&files)
73 .map_err(|error| format!("failed to stage example {:?}: {error}", self.name))?;
74 Ok(SelectedExample {
75 example: self,
76 variant: preset,
77 config,
78 assets,
79 })
80 }
81
82 pub(crate) fn embedded_files(self, preset: Preset) -> Vec<EmbeddedFile> {
83 let mut files = Vec::from(preset.embedded_files());
84 files.extend_from_slice(CODE_REVIEW_FILES);
85 files
86 }
87}
88
89#[derive(Debug)]
91pub struct SelectedExample {
92 pub example: Example,
94 pub variant: Preset,
96 pub config: FabricConfig,
98 assets: StagedAssets,
99}
100
101impl SelectedExample {
102 pub fn base_dir(&self) -> &Path {
104 self.assets.path()
105 }
106}
107
108pub fn all() -> &'static [Example] {
110 &EXAMPLES
111}
112
113pub fn find(name: &str) -> Option<Example> {
115 EXAMPLES
116 .iter()
117 .copied()
118 .find(|example| example.name == name)
119}
120
121const EXAMPLES: [Example; 1] = [Example {
122 name: "code-review",
123 description: "Review a small Python workspace with a deterministic default or a skill-capable harness.",
124 default_variant: "scripted",
125 variants: CODE_REVIEW_VARIANTS,
126}];
127
128fn code_review_config(preset: Preset) -> Result<FabricConfig, String> {
129 let mut config = preset.config()?;
130 config.metadata = MetadataConfig {
131 name: "code-review-agent".to_string(),
132 description: Some("Maintained example for reviewing a small Python workspace.".to_string()),
133 extensions: BTreeMap::new(),
134 };
135 config.discovery = Some(DiscoveryConfig {
136 local_paths: vec![PathBuf::from("adapters")],
137 extensions: BTreeMap::new(),
138 });
139 let environment = config
140 .environment
141 .as_mut()
142 .expect("CLI presets always define an execution environment");
143 environment.workspace = Some(PathBuf::from("repo"));
144 if preset.name != "scripted" {
145 config.skills = Some(SkillConfig {
146 paths: vec![PathBuf::from("skills/code-review.md")],
147 extensions: BTreeMap::new(),
148 });
149 }
150 Ok(config)
151}
152
153#[cfg(test)]
154mod tests {
155 use nemo_fabric_core::{ResolveContext, RunRequest, resolve_run_plan_from_config, run_plan};
156
157 use super::*;
158
159 #[test]
160 fn variants_reuse_the_preset_catalog() {
161 let example = find("code-review").expect("code review example");
162 for variant in example.variants {
163 assert!(presets::find(variant).is_some(), "missing preset {variant}");
164 }
165 }
166
167 #[test]
168 fn code_review_stages_one_shared_asset_tree_and_runs() {
169 let selected = find("code-review")
170 .expect("code review example")
171 .select(Some("scripted"))
172 .expect("select example");
173 assert!(selected.base_dir().join("repo/calculator.py").is_file());
174 assert!(selected.base_dir().join("skills/code-review.md").is_file());
175 assert!(
176 selected.config.skills.is_none(),
177 "the scripted smoke variant does not declare skill support"
178 );
179 assert!(
180 find("code-review")
181 .expect("code review example")
182 .select(Some("hermes"))
183 .expect("select Hermes example")
184 .config
185 .skills
186 .is_some(),
187 "maintained harness variants consume the staged skill"
188 );
189 let plan = resolve_run_plan_from_config(
190 selected.config.clone(),
191 ResolveContext::new(selected.base_dir()),
192 )
193 .expect("plan example");
194 assert_eq!(plan.agent_name, "code-review-agent");
195 assert_eq!(
196 plan.environment_plan
197 .as_ref()
198 .expect("environment")
199 .workspace
200 .as_ref(),
201 Some(&selected.base_dir().join("repo"))
202 );
203 let result =
204 run_plan(&plan, RunRequest::text("review")).expect("run deterministic example variant");
205 assert_eq!(result.output["response"], "review");
206 }
207}