///|
// Golden replay tests: read contract/goldens/skills/*.json and verify
// that our MoonBit implementation produces equivalent results.
fn ffi_read_skills_golden(name : String) -> String {
let dir = (@env.get_env_var("MNEMO_CONTRACT_DIR")).unwrap_or("./contract")
let path = dir + "/goldens/skills/" + name + ".json"
(try? @fs.read_file_to_string(path)).unwrap_or("")
}
///| Skills fixture dir relative to cwd (matches prior JS behavior).
fn ffi_skills_fixtures_path_golden() -> String {
"./proto/fixtures/skills"
}
///| Load and parse a skills golden JSON file
fn sg_load_golden(name : String) -> Json {
let raw = ffi_read_skills_golden(name)
try {
@json.parse(raw)
} catch {
_ => Json::null()
}
}
///| Extract a string field from a Json object, or return default
fn sg_get_str(j : Json, key : String) -> String {
match j {
Json::Object(obj) =>
match obj.get(key) {
Some(Json::String(s)) => s
_ => ""
}
_ => ""
}
}
///| Extract an int field from a Json object, or return default
fn sg_get_int(j : Json, key : String) -> Int {
match j {
Json::Object(obj) =>
match obj.get(key) {
Some(Json::Number(n, ..)) => n.to_int()
_ => 0
}
_ => 0
}
}
///| Extract a bool field from a Json object, or return false
fn sg_get_bool(j : Json, key : String) -> Bool {
match j {
Json::Object(obj) =>
match obj.get(key) {
Some(Json::True) => true
_ => false
}
_ => false
}
}
///| Get the "expected" sub-object from a golden JSON
fn sg_expected(j : Json) -> Json {
match j {
Json::Object(obj) =>
match obj.get("expected") {
Some(e) => e
_ => Json::null()
}
_ => Json::null()
}
}
///| Get the "input" sub-object from a golden JSON
fn sg_input(j : Json) -> Json {
match j {
Json::Object(obj) =>
match obj.get("input") {
Some(i) => i
_ => Json::null()
}
_ => Json::null()
}
}
///| Find skill by name in array (manual loop — Iter has no find)
fn sg_find_by_name(skills : Array[SkillEntry], name : String) -> Bool {
let mut found = false
for s in skills {
if s.name == name {
found = true
}
}
found
}
///| Golden 01: frontmatter parse
test "golden: 01_frontmatter_parse" {
let j = sg_load_golden("01_frontmatter_parse")
let input = sg_input(j)
let expected = sg_expected(j)
let content = sg_get_str(input, "content")
let fm_json = match expected {
Json::Object(obj) =>
match obj.get("frontmatter") {
Some(fm) => fm
_ => Json::null()
}
_ => Json::null()
}
let expected_name = sg_get_str(fm_json, "name")
let fm = parse_frontmatter(content)
assert_eq(fm.name, Some(expected_name))
}
///| Golden 02: load_dir — count and names
test "golden: 02_load_dir" {
let j = sg_load_golden("02_load_dir")
let expected = sg_expected(j)
let expected_count = sg_get_int(expected, "count")
let dir = ffi_skills_fixtures_path_golden()
let store = skills_store_load(dir, [])
let skills = skills_store_list(store, None, None)
assert_eq(skills.length(), expected_count)
}
///| Golden 03: filter_by_tools
test "golden: 03_filter_by_tools" {
let j = sg_load_golden("03_filter_by_tools")
let expected = sg_expected(j)
let docker_hidden = sg_get_bool(expected, "docker_build_hidden_when_tool_available")
let docker_shown = sg_get_bool(expected, "docker_build_shown_when_no_tool")
let dir = ffi_skills_fixtures_path_golden()
let store = skills_store_load(dir, [])
let with_tool = skills_store_list(store, Some(["docker_build_tool"]), None)
let without_tool = skills_store_list(store, Some([]), None)
// Iter has no find — use manual loop
let actual_hidden = !sg_find_by_name(with_tool, "docker-build")
let actual_shown = sg_find_by_name(without_tool, "docker-build")
assert_eq(actual_hidden, docker_hidden)
assert_eq(actual_shown, docker_shown)
}
///| Golden 04: filter_by_platform
test "golden: 04_filter_by_platform" {
let j = sg_load_golden("04_filter_by_platform")
let expected = sg_expected(j)
let deploy_on_linux = sg_get_bool(expected, "deploy_k8s_on_linux")
let deploy_on_mac = sg_get_bool(expected, "deploy_k8s_on_mac")
let dir = ffi_skills_fixtures_path_golden()
let store = skills_store_load(dir, [])
let for_linux = skills_store_list(store, Some(["kubectl"]), Some("linux"))
let for_mac = skills_store_list(store, None, Some("mac"))
let actual_linux = sg_find_by_name(for_linux, "deploy-k8s")
let actual_mac = sg_find_by_name(for_mac, "deploy-k8s")
assert_eq(actual_linux, deploy_on_linux)
assert_eq(actual_mac, deploy_on_mac)
}
///| Golden 05: build_prompt
test "golden: 05_build_prompt" {
let j = sg_load_golden("05_build_prompt")
let expected = sg_expected(j)
let contains_tdd = sg_get_bool(expected, "contains_tdd_workflow")
let dir = ffi_skills_fixtures_path_golden()
let store = skills_store_load(dir, [])
let prompt = skills_store_build_prompt(store, None, None)
assert_eq(prompt.contains("tdd-workflow"), contains_tdd)
assert_eq(prompt.has_prefix("## Skills"), true)
}
///| Golden 06: skill_view
test "golden: 06_skill_view" {
let j = sg_load_golden("06_skill_view")
let expected = sg_expected(j)
let expected_ok = sg_get_bool(expected, "ok")
let expected_content_contains = sg_get_bool(expected, "content_contains")
let dir = ffi_skills_fixtures_path_golden()
let store = skills_store_load(dir, [])
let result = skills_store_view(store, "tdd-workflow")
match result {
ViewOk(_name, content) => {
assert_eq(true, expected_ok)
assert_eq(content.contains("Red"), expected_content_contains)
}
ViewErr(_) => assert_eq(false, expected_ok)
}
}
///| Golden 07: disabled filter — MoonBit matches proto behavior
test "golden: 07_disabled_filter — disabled skill excluded" {
let j = sg_load_golden("07_disabled_filter")
let expected = sg_expected(j)
let supported = sg_get_bool(expected, "supported")
assert_eq(supported, true)
let dir = ffi_skills_fixtures_path_golden()
let store = skills_store_load(dir, [])
let all = skills_store_list(store, None, None)
let filtered = skills_store_list(store, None, None, disabled=["tdd-workflow"])
// Golden should record (all count - 1 == filtered count)
assert_eq(filtered.length(), all.length() - 1)
// tdd-workflow must be gone
match find_skill_by_name(filtered, "tdd-workflow") {
Some(_) => fail("tdd-workflow should have been disabled")
None => ()
}
// Compare counts against golden's filtered_names array for cross-impl parity
match expected {
Json::Object(obj) =>
match obj.get("filtered_names") {
Some(Json::Array(names)) => assert_eq(filtered.length(), names.length())
_ => ()
}
_ => ()
}
}
///| Golden 08: metadata.hermes.tags — MoonBit now parses tags
test "golden: 08_metadata_tags — tags parsed from frontmatter" {
let j = sg_load_golden("08_metadata_tags")
let expected = sg_expected(j)
let tags_count = sg_get_int(expected, "tags_count")
assert_eq(tags_count, 2)
// Live: parse the same content the proto golden used and verify MoonBit matches.
let content = "---\nname: tagged-skill\ndescription: A skill with tags\nmetadata:\n hermes:\n tags: [alpha, beta]\n---\n\n# Tagged Skill\nBody.\n"
let fm = parse_frontmatter(content)
assert_eq(fm.tags.length(), tags_count)
assert_eq(fm.tags.contains("alpha"), true)
assert_eq(fm.tags.contains("beta"), true)
}
// ── FFI helpers for golden 10 temp dir (cross-target) ──────────────────────
///| Monotonic counter for unique tmp dirs across same-ms calls.
let _sk_tmp_counter : Array[Int] = [0]
fn sg_mkdtemp(prefix : String) -> String {
let tmp = (@env.get_env_var("TMPDIR")).unwrap_or("/tmp")
_sk_tmp_counter[0] = _sk_tmp_counter[0] + 1
let suffix = @env.now().to_string() + "-" + _sk_tmp_counter[0].to_string()
let dir = tmp + "/" + prefix + suffix
let _ = try? @fs.create_dir(dir)
dir
}
///| Recursive mkdir.
fn sg_mkdir(dir : String) -> Unit {
if @fs.path_exists(dir) {
return
}
let view = dir.view()
let mut last = -1
let mut i = 0
while i < view.length() {
if view.get_char(i) is Some('/') {
last = i
}
i = i + 1
}
if last > 0 {
sg_mkdir(view[0:last].to_string())
}
let _ = try? @fs.create_dir(dir)
}
fn sg_write_file(path : String, content : String) -> Unit {
let _ = try? @fs.write_string_to_file(path, content)
}
fn sg_rmrf(dir : String) -> Unit {
let _ = try? @fs.remove_dir(dir)
}
fn sg_path_join(a : String, b : String) -> String {
if a.is_empty() {
b
} else if a.has_suffix("/") {
a + b
} else {
a + "/" + b
}
}
///| Golden 10: filter by available_toolsets
// Proto behavior: skills with requires_toolsets:[web_search] are hidden when
// available_toolsets:[] is passed and shown when available_toolsets:["web_search"] is passed.
// MoonBit now implements this correctly with the available_toolsets parameter.
test "golden: 10_filter_by_toolsets — MoonBit filters by toolsets (matches proto)" {
let j = sg_load_golden("10_filter_by_toolsets")
let expected = sg_expected(j)
// Golden records proto's correct behavior
let always_shown_with = sg_get_bool(expected, "always_shown_with")
let always_shown_without = sg_get_bool(expected, "always_shown_without")
let needs_websearch_hidden_without = sg_get_bool(expected, "needs_websearch_hidden_without")
let needs_websearch_shown_with = sg_get_bool(expected, "needs_websearch_shown_with")
assert_eq(always_shown_with, true)
assert_eq(always_shown_without, true)
assert_eq(needs_websearch_hidden_without, true)
assert_eq(needs_websearch_shown_with, true)
// Live MoonBit behavior: create temp skills matching proto's golden scenario
let local_dir = sg_mkdtemp("golden-skills-toolsets-")
// skill-always: no toolset restriction
sg_mkdir(sg_path_join(local_dir, "skill-always"))
sg_write_file(
sg_path_join(sg_path_join(local_dir, "skill-always"), "SKILL.md"),
"---\nname: skill-always\ndescription: Always visible skill\n---\n\n# Always\nNo restrictions.\n",
)
// skill-needs-websearch: requires web_search toolset
sg_mkdir(sg_path_join(local_dir, "skill-needs-websearch"))
sg_write_file(
sg_path_join(sg_path_join(local_dir, "skill-needs-websearch"), "SKILL.md"),
"---\nname: skill-needs-websearch\ndescription: Requires web_search toolset\nrequires_toolsets: [web_search]\n---\n\n# Web Search Skill\n",
)
let store = skills_store_load(local_dir, [])
// With web_search toolset: both skills shown
let with_toolset = skills_store_list(
store, Some([]), None, available_toolsets=Some(["web_search"]),
)
// Without web_search toolset: only skill-always shown
let without_toolset = skills_store_list(
store, Some([]), None, available_toolsets=Some([]),
)
sg_rmrf(local_dir)
// Verify MoonBit matches proto's golden expectations
assert_eq(sg_find_by_name(with_toolset, "skill-always"), always_shown_with)
assert_eq(sg_find_by_name(without_toolset, "skill-always"), always_shown_without)
assert_eq(sg_find_by_name(with_toolset, "skill-needs-websearch"), needs_websearch_shown_with)
assert_eq(!sg_find_by_name(without_toolset, "skill-needs-websearch"), needs_websearch_hidden_without)
}
///| Golden 09: external dirs precedence — local skill wins on name collision
test "golden: 09_external_dirs_precedence — local dir wins on name collision" {
let j = sg_load_golden("09_external_dirs_precedence")
let expected = sg_expected(j)
let local_wins = sg_get_bool(expected, "local_wins")
let count = sg_get_int(expected, "skill_foo_count")
assert_eq(count, 1)
assert_eq(local_wins, true)
}