use serde_json::{Value as JsonValue, json};
pub fn descriptors() -> Vec<JsonValue> {
vec![json!({
"name": "format_and_lint_yaml",
"title": "Format and lint a YAML file (lossless)",
"description": "Guided workflow for inspecting and losslessly \
fixing a YAML file with noyalib_get and noyalib_set, preserving \
comments and formatting.",
"arguments": [
{
"name": "file",
"description": "Path to the YAML file to review. Optional; \
omit for a general workflow.",
"required": false
}
]
})]
}
pub fn get(params: JsonValue) -> Result<JsonValue, (i32, String)> {
let name = params
.get("name")
.and_then(|v| v.as_str())
.ok_or_else(|| (-32602, "missing field: name".to_string()))?;
match name {
"format_and_lint_yaml" => Ok(format_and_lint_yaml(¶ms)),
_ => Err((-32602, format!("unknown prompt: {name}"))),
}
}
fn format_and_lint_yaml(params: &JsonValue) -> JsonValue {
let file = params
.get("arguments")
.and_then(|a| a.get("file"))
.and_then(|v| v.as_str())
.unwrap_or("");
let target = if file.is_empty() {
"the YAML file".to_string()
} else {
format!("`{file}`")
};
let text = format!(
"Help me format and lint {target} without losing comments or \
formatting. First call noyalib_get on the paths you want to inspect \
to read the current values exactly as written. Propose a minimal set \
of fixes (indentation, key ordering, quoting, obviously wrong \
values). For each fix, call noyalib_set with the dotted/indexed path \
and the replacement YAML fragment: it rewrites only the touched span, \
so every comment, blank line and sibling entry is preserved \
byte-for-byte. Re-read with noyalib_get to confirm each change."
);
json!({
"description": "Guided lossless YAML format-and-lint workflow.",
"messages": [
{ "role": "user", "content": { "type": "text", "text": text } }
]
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn descriptors_lists_the_prompt_with_arguments() {
let d = descriptors();
assert_eq!(d.len(), 1);
assert_eq!(d[0]["name"].as_str(), Some("format_and_lint_yaml"));
assert!(d[0]["arguments"].is_array());
}
#[test]
fn get_rejects_missing_name() {
let err = get(json!({})).unwrap_err();
assert_eq!(err.0, -32602);
assert!(err.1.contains("name"));
}
#[test]
fn get_rejects_unknown_prompt() {
let err = get(json!({"name": "nope"})).unwrap_err();
assert_eq!(err.0, -32602);
assert!(err.1.contains("nope"));
}
#[test]
fn get_without_file_uses_generic_target() {
let v = get(json!({"name": "format_and_lint_yaml"})).unwrap();
let text = v["messages"][0]["content"]["text"].as_str().unwrap();
assert!(text.contains("the YAML file"));
assert!(text.contains("noyalib_get"));
assert!(text.contains("noyalib_set"));
assert_eq!(v["messages"][0]["role"].as_str(), Some("user"));
}
#[test]
fn get_with_file_embeds_the_path() {
let v = get(json!({
"name": "format_and_lint_yaml",
"arguments": { "file": "config.yml" }
}))
.unwrap();
let text = v["messages"][0]["content"]["text"].as_str().unwrap();
assert!(text.contains("`config.yml`"));
}
}