chore(release): prepare public source release
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
//! Tests for the generated Mercury Toolbox AI assets.
|
||||
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
fn workspace_root() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("..")
|
||||
.join("..")
|
||||
.canonicalize()
|
||||
.expect("workspace root")
|
||||
}
|
||||
|
||||
fn workspace_path(root: &Path, relative_path: &[&str]) -> PathBuf {
|
||||
relative_path
|
||||
.iter()
|
||||
.fold(root.to_path_buf(), |path, component| path.join(component))
|
||||
}
|
||||
|
||||
fn read_workspace_text(root: &Path, relative_path: &[&str], label: &str) -> String {
|
||||
fs::read_to_string(workspace_path(root, relative_path))
|
||||
.unwrap_or_else(|error| panic!("failed to read {label}: {error}"))
|
||||
}
|
||||
|
||||
fn toolbox_commands(root: &Path) -> Vec<String> {
|
||||
let commands = read_workspace_text(
|
||||
root,
|
||||
&["scripts", "toolbox-commands.ps1"],
|
||||
"scripts/toolbox-commands.ps1",
|
||||
);
|
||||
let mut in_command_list = false;
|
||||
let mut consumed_command_list = false;
|
||||
let mut names = commands
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let trimmed = line.trim();
|
||||
if trimmed == "return @(" && !in_command_list && !consumed_command_list {
|
||||
in_command_list = true;
|
||||
return None;
|
||||
}
|
||||
if in_command_list && trimmed == ")" {
|
||||
in_command_list = false;
|
||||
consumed_command_list = true;
|
||||
return None;
|
||||
}
|
||||
if !in_command_list {
|
||||
return None;
|
||||
}
|
||||
trimmed
|
||||
.strip_prefix('\'')
|
||||
.and_then(|rest| rest.split_once('\''))
|
||||
.map(|(name, _)| name.to_string())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
names.sort();
|
||||
names.dedup();
|
||||
names
|
||||
}
|
||||
|
||||
fn assert_lf_only(root: &Path, relative_path: &[&str]) {
|
||||
let path = workspace_path(root, relative_path);
|
||||
let bytes = fs::read(&path).unwrap_or_else(|error| panic!("read {}: {error}", path.display()));
|
||||
assert!(
|
||||
!bytes.windows(2).any(|window| window == b"\r\n"),
|
||||
"{} should use LF line endings",
|
||||
path.display()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ai_prompt_assets_exist_and_cover_every_tool() {
|
||||
let root = workspace_root();
|
||||
let prompt = read_workspace_text(
|
||||
&root,
|
||||
&["docs", "ai", "mercury-toolbox-ai-prompt.md"],
|
||||
"generated AI prompt",
|
||||
);
|
||||
let notes = read_workspace_text(
|
||||
&root,
|
||||
&["docs", "ai", "toolbox-ai-prompt-notes.json"],
|
||||
"AI prompt notes",
|
||||
);
|
||||
let commands = toolbox_commands(&root);
|
||||
|
||||
assert!(prompt.contains("Mercury Toolbox"));
|
||||
assert!(prompt.contains("PowerShell"));
|
||||
assert!(prompt.contains("`--json`"));
|
||||
assert!(prompt.contains("`--toon`"));
|
||||
assert!(prompt.contains("Available tools:"));
|
||||
assert!(prompt.contains("Rules:"));
|
||||
assert!(prompt.contains("Pipe external JSON into `toon`"));
|
||||
assert!(prompt.contains("Every tool has guided triage metadata"));
|
||||
assert!(prompt.contains("Guided: answer="));
|
||||
assert!(prompt.contains("`report_quality`"));
|
||||
assert!(prompt.contains("`next_actions`"));
|
||||
assert!(!prompt.contains("toon --from json --to toon"));
|
||||
assert!(prompt.contains("Usage: `"));
|
||||
assert!(prompt.contains("Example:"));
|
||||
assert!(prompt.contains("msudo:"));
|
||||
assert!(prompt.contains("Top-level high-risk command"));
|
||||
assert!(prompt.contains(
|
||||
"msudo status --json | ConvertFrom-Json | Select-Object ok,host,supports_runas,is_elevated"
|
||||
));
|
||||
assert!(!prompt.contains("## Tool Catalog"));
|
||||
assert!(!prompt.contains("### `"));
|
||||
assert!(!prompt.contains("$Fence"));
|
||||
assert!(
|
||||
prompt.lines().count() <= 120,
|
||||
"prompt should stay compact for AI consumption"
|
||||
);
|
||||
|
||||
assert_eq!(commands.len(), 59, "toolbox command inventory changed");
|
||||
let notes_json = serde_json::from_str::<Value>(¬es).expect("AI prompt notes JSON");
|
||||
let tools = notes_json["tools"].as_object().expect("notes tools object");
|
||||
|
||||
for command in commands {
|
||||
assert!(
|
||||
prompt.contains(&format!("{command}:")),
|
||||
"prompt should contain {command}"
|
||||
);
|
||||
assert!(
|
||||
notes.contains(&format!("\"{command}\"")),
|
||||
"notes should contain {command}"
|
||||
);
|
||||
let guided = tools
|
||||
.get(&command)
|
||||
.and_then(|tool| tool.get("guided_triage"))
|
||||
.unwrap_or_else(|| panic!("notes should contain guided_triage for {command}"));
|
||||
assert!(
|
||||
guided["answer"]
|
||||
.as_str()
|
||||
.is_some_and(|value| !value.is_empty()),
|
||||
"guided_triage.answer should be non-empty for {command}"
|
||||
);
|
||||
assert!(
|
||||
guided["trust"]
|
||||
.as_str()
|
||||
.is_some_and(|value| !value.is_empty()),
|
||||
"guided_triage.trust should be non-empty for {command}"
|
||||
);
|
||||
assert!(
|
||||
guided["next_actions"]
|
||||
.as_array()
|
||||
.is_some_and(|items| items.len() >= 2
|
||||
&& items
|
||||
.iter()
|
||||
.all(|item| item.as_str().is_some_and(|value| !value.is_empty()))),
|
||||
"guided_triage.next_actions should list at least two actions for {command}"
|
||||
);
|
||||
}
|
||||
|
||||
assert!(
|
||||
notes.contains("msudo status --json | ConvertFrom-Json | Select-Object ok,host,supports_runas,is_elevated"),
|
||||
"notes should document the stable msudo status discovery fields"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ai_skill_assets_exist_and_cover_every_tool() {
|
||||
let root = workspace_root();
|
||||
let skill = read_workspace_text(
|
||||
&root,
|
||||
&["skills", "mercury-toolbox", "SKILL.md"],
|
||||
"generated skill",
|
||||
);
|
||||
let catalog = read_workspace_text(
|
||||
&root,
|
||||
&[
|
||||
"skills",
|
||||
"mercury-toolbox",
|
||||
"references",
|
||||
"command-catalog.md",
|
||||
],
|
||||
"generated skill catalog",
|
||||
);
|
||||
let openai_yaml = read_workspace_text(
|
||||
&root,
|
||||
&["skills", "mercury-toolbox", "agents", "openai.yaml"],
|
||||
"generated openai.yaml",
|
||||
);
|
||||
|
||||
assert!(skill.contains("Mercury Toolbox"));
|
||||
assert!(skill.contains("Prefer Mercury readers over `Get-Content`"));
|
||||
assert!(skill.contains("## Modern Pairings"));
|
||||
assert!(skill.contains("## Job Routing"));
|
||||
assert!(skill.contains("Windows driver: start with `drvshape <SYS>`"));
|
||||
assert!(skill.contains("`report_quality` and `next_actions`"));
|
||||
assert!(skill.contains("Use `rg` over recursive `grep`"));
|
||||
assert!(skill.contains("Treat `msudo` as the top-level high-risk toolbox command"));
|
||||
assert!(skill.contains("`msudo status --json`"));
|
||||
assert!(skill.contains("references/command-catalog.md"));
|
||||
assert!(skill.contains("switch to `--toon` or `--format toon`"));
|
||||
assert!(!skill.contains("toon --from json --to toon"));
|
||||
assert!(skill.lines().count() <= 95, "skill should stay concise");
|
||||
|
||||
assert!(catalog.contains("# Mercury Toolbox Command Catalog"));
|
||||
assert!(catalog.contains("Keep output compact"));
|
||||
assert!(catalog.contains("TOON example:"));
|
||||
assert!(catalog.contains("Guided answer:"));
|
||||
assert!(catalog.contains("Trust basis:"));
|
||||
assert!(catalog.contains("Next actions:"));
|
||||
assert!(catalog.contains("--toon"));
|
||||
assert!(!catalog.contains("toon --from json --to toon"));
|
||||
assert!(catalog.contains("### `msudo`"));
|
||||
assert!(catalog.contains("Top-level high-risk command"));
|
||||
assert!(catalog.contains(
|
||||
"msudo status --json | ConvertFrom-Json | Select-Object ok,host,supports_runas,is_elevated"
|
||||
));
|
||||
assert!(
|
||||
catalog.lines().count() <= 520,
|
||||
"catalog should stay compact"
|
||||
);
|
||||
|
||||
assert!(openai_yaml.contains("display_name: \"Mercury Toolbox\""));
|
||||
assert!(openai_yaml.contains("icon_small: \"./assets/logo.png\""));
|
||||
assert!(openai_yaml.contains("icon_large: \"./assets/logo.png\""));
|
||||
assert!(openai_yaml.contains("brand_color: \"#35C2FF\""));
|
||||
assert!(openai_yaml.contains("default_prompt: \"Use $mercury-toolbox first"));
|
||||
assert!(
|
||||
root.join("skills")
|
||||
.join("mercury-toolbox")
|
||||
.join("assets")
|
||||
.join("logo.png")
|
||||
.is_file(),
|
||||
"generated skill should include its logo asset"
|
||||
);
|
||||
|
||||
for command in toolbox_commands(&root) {
|
||||
assert!(
|
||||
catalog.contains(&format!("### `{command}`")),
|
||||
"catalog should contain {command}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_ai_markdown_assets_use_lf_line_endings() {
|
||||
let root = workspace_root();
|
||||
assert_lf_only(&root, &["docs", "ai", "mercury-toolbox-ai-prompt.md"]);
|
||||
assert_lf_only(&root, &["skills", "mercury-toolbox", "SKILL.md"]);
|
||||
assert_lf_only(
|
||||
&root,
|
||||
&[
|
||||
"skills",
|
||||
"mercury-toolbox",
|
||||
"references",
|
||||
"command-catalog.md",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn readme_tool_map_covers_every_toolbox_command() {
|
||||
let root = workspace_root();
|
||||
let readme = read_workspace_text(&root, &["README.md"], "README.md");
|
||||
|
||||
assert!(readme.contains("### Tool Map"));
|
||||
assert!(readme.contains("## Which Tool First"));
|
||||
assert!(readme.contains("Safe starter commands"));
|
||||
assert!(readme.contains("Every command has guided triage notes"));
|
||||
assert!(readme.contains("`report_quality` and `next_actions`"));
|
||||
assert!(readme.contains("Every command supports `--help`"));
|
||||
for command in toolbox_commands(&root) {
|
||||
assert!(
|
||||
readme.contains(&format!("| `{command}` |")),
|
||||
"README tool map should contain {command}"
|
||||
);
|
||||
}
|
||||
|
||||
assert!(readme.contains("### `asmflow`"));
|
||||
assert!(readme.contains("### `unityasset`"));
|
||||
assert!(readme.contains("### `unityprobe`"));
|
||||
assert!(readme.contains("### `unitydiag`"));
|
||||
}
|
||||
Reference in New Issue
Block a user