-module(pig@skill). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/pig/skill.gleam"). -export([parse_frontmatter/1, load/1, skill_to_system_fragment/1]). -export_type([skill/0, skill_error/0, frontmatter/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC( " Skill loading and parsing.\n" "\n" " A Skill is a directory on disk containing a `SKILL.md` file with\n" " YAML frontmatter (name + description) and a markdown body.\n" "\n" " Frontmatter parsing is deliberately simple: split on `---` delimiters,\n" " extract `name` and `description` fields from the YAML-like block.\n" " No full YAML parser needed.\n" ). -type skill() :: {skill, binary(), binary(), binary(), list(binary())}. -type skill_error() :: skill_not_found | invalid_frontmatter. -type frontmatter() :: {frontmatter, binary(), binary(), binary()}. -file("src/pig/skill.gleam", 126). ?DOC( " List supplementary files in a skill directory (excluding SKILL.md).\n" " Returns relative paths from the skill directory.\n" ). -spec list_files(binary()) -> list(binary()). list_files(Path) -> case simplifile_erl:read_directory(Path) of {ok, Entries} -> _pipe = Entries, _pipe@1 = gleam@list:filter( _pipe, fun(E) -> E /= <<"SKILL.md"/utf8>> end ), gleam@list:filter( _pipe@1, fun(E@1) -> Full = <<<>/binary, E@1/binary>>, case simplifile_erl:is_file(Full) of {ok, true} -> true; _ -> false end end ); {error, _} -> [] end. -file("src/pig/skill.gleam", 69). ?DOC(" Walk frontmatter lines collecting name/description accumulators.\n"). -spec parse_lines(list(binary()), binary(), binary(), boolean()) -> {ok, frontmatter()} | {error, skill_error()}. parse_lines(Lines, Acc_name, Acc_desc, In_fm) -> case Lines of [] -> {error, invalid_frontmatter}; [<<"---"/utf8>> | Rest] when In_fm -> Body = gleam@string:join(Rest, <<"\n"/utf8>>), case {Acc_name, Acc_desc} of {<<""/utf8>>, _} -> {error, invalid_frontmatter}; {_, <<""/utf8>>} -> {error, invalid_frontmatter}; {_, _} -> {ok, {frontmatter, gleam@string:trim(Acc_name), gleam@string:trim(Acc_desc), Body}} end; [Line | Rest@1] when In_fm -> Parts = gleam@string:split(Line, <<": "/utf8>>), case Parts of [Key, Value] -> New_name = case Key of <<"name"/utf8>> -> Value; _ -> Acc_name end, New_desc = case Key of <<"description"/utf8>> -> Value; _ -> Acc_desc end, parse_lines(Rest@1, New_name, New_desc, true); _ -> parse_lines(Rest@1, Acc_name, Acc_desc, true) end; _ -> {error, invalid_frontmatter} end. -file("src/pig/skill.gleam", 61). ?DOC( " Parse frontmatter from a SKILL.md content string.\n" "\n" " Expects `---\\n` at start, content between delimiters, and closing `---\\n`.\n" " Extracts `name:` and `description:` fields. Returns remaining body after\n" " the closing delimiter.\n" "\n" " This is a simple parser — not a full YAML parser. It handles:\n" " - `key: value` lines\n" ). -spec parse_frontmatter(binary()) -> {ok, frontmatter()} | {error, skill_error()}. parse_frontmatter(Raw) -> case gleam@string:split(Raw, <<"\n"/utf8>>) of [<<"---"/utf8>> | Rest] -> parse_lines(Rest, <<""/utf8>>, <<""/utf8>>, true); _ -> {error, invalid_frontmatter} end. -file("src/pig/skill.gleam", 34). ?DOC( " Load a skill from a directory path.\n" "\n" " Reads `SKILL.md` from the directory, parses frontmatter for name/description,\n" " and lists supplementary files (everything except `SKILL.md`).\n" ). -spec load(binary()) -> {ok, skill()} | {error, skill_error()}. load(Path) -> Skill_md_path = <>, case simplifile:read(Skill_md_path) of {ok, Content} -> case parse_frontmatter(Content) of {ok, Fm} -> {ok, {skill, erlang:element(2, Fm), erlang:element(3, Fm), Path, list_files(Path)}}; {error, E} -> {error, E} end; {error, _} -> {error, skill_not_found} end. -file("src/pig/skill.gleam", 114). ?DOC( " Generate a system prompt fragment from a skill.\n" "\n" " Used to inject skill descriptions into the system prompt so the agent\n" " knows what skills are available.\n" ). -spec skill_to_system_fragment(skill()) -> binary(). skill_to_system_fragment(Skill) -> <<<<<<<<<<<<"Skill: "/utf8, (erlang:element(2, Skill))/binary>>/binary, "\nDescription: "/utf8>>/binary, (erlang:element(3, Skill))/binary>>/binary, "\nPath: "/utf8>>/binary, (erlang:element(4, Skill))/binary>>/binary, "\n"/utf8>>.