/* Skills: frontmatter, discovery order, prompt index, the skill tool and * path containment. */ #define _POSIX_C_SOURCE 200809L #include "cst_agent.h" #include "cst_skills.h" #include "cst_tools.h" #include #include #include #include #include static int nfail; #define CHECK(cond, msg) \ do { \ if (!(cond)) { \ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, msg); \ nfail++; \ } \ } while (0) static char g_root[256]; static void put(const char *rel, const char *text) { char p[512]; snprintf(p, sizeof(p), "%s/%s", g_root, rel); for (char *q = p + strlen(g_root) + 1; *q; q++) { if (*q == '/') { *q = 0; mkdir(p, 0755); *q = '/'; } } FILE *fp = fopen(p, "w"); if (fp) { fputs(text, fp); fclose(fp); } } static void frontmatter(void) { char v[256]; const char *t = "---\nname: \"pdf-tools\"\ndescription: >\n Fill PDF\n " "forms.\ntools: [read_file, grep ]\nmodel: deepseek-pro\n" "list:\n - a\n - b\nlit: |\n one\n two\n---\n\n# Body\n"; CHECK(cst_fm_get(t, "name", v, sizeof(v)) && strcmp(v, "pdf-tools") == 0, "quoted value"); CHECK(cst_fm_get(t, "description", v, sizeof(v)) && strcmp(v, "Fill PDF forms.") == 0, "folded value"); CHECK(cst_fm_get(t, "tools", v, sizeof(v)) && strcmp(v, "read_file,grep") == 0, "flow list"); CHECK(cst_fm_get(t, "list", v, sizeof(v)) && strcmp(v, "a,b") == 0, "block list"); CHECK(cst_fm_get(t, "lit", v, sizeof(v)) && strcmp(v, "one\ntwo") == 0, "literal block"); CHECK(!cst_fm_get(t, "nope", v, sizeof(v)), "missing key"); CHECK(strcmp(cst_fm_body(t), "# Body\n") == 0, "body after fence"); CHECK(strcmp(cst_fm_body("no frontmatter\n"), "no frontmatter\n") == 0, "no frontmatter: whole text"); CHECK(!cst_fm_get("---\nname: x\n", "name", v, sizeof(v)), "unclosed frontmatter ignored"); } int main(void) { frontmatter(); snprintf(g_root, sizeof(g_root), "/tmp/cst-skills-%d", (int)getpid()); mkdir(g_root, 0755); char ws[300], cfgdir[300]; snprintf(ws, sizeof(ws), "%s/ws", g_root); snprintf(cfgdir, sizeof(cfgdir), "%s/cfg", g_root); setenv("XDG_CONFIG_HOME", cfgdir, 1); put("ws/.agent/skills/zeta/SKILL.md", "---\nname: zeta\ndescription: Last one alphabetically.\n---\nDo Z.\n"); put("ws/.agent/skills/forms/SKILL.md", "---\nname: pdf-forms\ndescription: Fill PDF forms with the bundled " "script.\n---\n# PDF forms\n\nRun scripts/fill.py.\n"); put("ws/.agent/skills/forms/scripts/fill.py", "print('fill')\n"); put("ws/.agent/skills/forms/reference.md", "Field names...\n"); put("ws/.claude/skills/plain/SKILL.md", "# Plain\n\nA skill without " "frontmatter.\n"); put("cfg/cst-agent/skills/forms2/SKILL.md", "---\nname: pdf-forms\ndescription: shadowed by the workspace\n---\n"); put("cfg/cst-agent/skills/userskill/SKILL.md", "---\nname: user-skill\ndescription: From the config dir.\n---\nU\n"); put("ws/secret.txt", "top secret\n"); { char link[512], target[512]; snprintf(link, sizeof(link), "%s/ws/.agent/skills/forms/leak", g_root); snprintf(target, sizeof(target), "%s/ws/secret.txt", g_root); if (symlink(target, link) != 0) perror("symlink"); } cst_skill_t v[CST_SKILLS_MAX]; int n = cst_skills_list(ws, v, CST_SKILLS_MAX); CHECK(n == 4, "four distinct skills"); CHECK(n == 4 && strcmp(v[0].name, "pdf-forms") == 0 && strcmp(v[1].name, "plain") == 0 && strcmp(v[2].name, "user-skill") == 0 && strcmp(v[3].name, "zeta") == 0, "sorted by name"); CHECK(n == 4 && strcmp(v[0].source, "workspace") == 0 && strstr(v[0].desc, "bundled script"), "workspace wins over the config dir"); CHECK(n == 4 && strcmp(v[1].source, "claude") == 0 && strcmp(v[1].desc, "A skill without frontmatter.") == 0, ".claude skill, description from the body"); char *ix = cst_skills_index(ws); CHECK(ix && strstr(ix, "- pdf-forms: Fill PDF forms") && strstr(ix, "- zeta: Last one"), "index lists name: description"); free(ix); int code = -1; char *out = cst_skill_read(ws, "pdf-forms", NULL, &code); CHECK(code == 0 && out && strstr(out, "# PDF forms\n\nRun scripts/fill.py.") && !strstr(out, "description:"), "SKILL.md body without frontmatter"); CHECK(out && strstr(out, "- reference.md\n") && strstr(out, "- scripts/fill.py\n"), "file list"); free(out); out = cst_skill_read(ws, "pdf-forms", "scripts/fill.py", &code); CHECK(code == 0 && out && strstr(out, "print('fill')"), "read a skill file"); free(out); out = cst_skill_read(ws, "pdf-forms", "../../../secret.txt", &code); CHECK(code == 1 && out && !strstr(out, "top secret"), "no ../ escape"); free(out); out = cst_skill_read(ws, "pdf-forms", "leak", &code); CHECK(code == 1 && out && !strstr(out, "top secret"), "no symlink escape"); free(out); out = cst_skill_read(ws, "missing", NULL, &code); CHECK(code == 1 && out && strstr(out, "available: pdf-forms, plain"), "unknown skill lists names"); free(out); char *pr = cst_skill_prompt(ws, "zeta", "do the thing"); CHECK(pr && strstr(pr, "") && strstr(pr, "Do Z.") && strstr(pr, "do the thing"), "/skill prompt inlines the instructions"); free(pr); /* tool + system prompt */ CHECK(cst_skills_register_tool() == 0 && cst_tool_find("skill"), "skill tool registered"); cst_agent_config_t cfg; memset(&cfg, 0, sizeof(cfg)); snprintf(cfg.workspace_root, sizeof(cfg.workspace_root), "%s", ws); ctl_tool_call_t c; memset(&c, 0, sizeof(c)); snprintf(c.name, sizeof(c.name), "skill"); c.nargs = 1; snprintf(c.args[0].key, sizeof(c.args[0].key), "name"); c.args[0].value = "user-skill"; c.args[0].value_len = strlen(c.args[0].value); cst_tool_ctx_t ctx; memset(&ctx, 0, sizeof(ctx)); ctx.workspace_root = ws; cst_tool_result_t r; memset(&r, 0, sizeof(r)); cst_tool_run(&ctx, &c, &r); CHECK(r.exit_code == 0 && r.out && strstr(r.out, "skill user-skill (user)"), "skill tool runs"); cst_tool_result_free(&r); char *sp = cst_agent_build_prompt(&cfg, CST_PROTO_NATIVE_EOS, NULL); CHECK(sp && strstr(sp, "Skills (call skill(name)") && strstr(sp, "- user-skill: From the config dir."), "system prompt carries the index"); free(sp); cfg.tool_profile = CST_TOOLS_MINIMAL; sp = cst_agent_build_prompt(&cfg, CST_PROTO_NATIVE_EOS, NULL); CHECK(sp && !strstr(sp, "Skills ("), "minimal prompt unchanged"); free(sp); char cmd[400]; snprintf(cmd, sizeof(cmd), "rm -rf %s", g_root); if (system(cmd) != 0) fprintf(stderr, "note: cleanup failed\n"); if (nfail) { fprintf(stderr, "test_skills: %d failure(s)\n", nfail); return 1; } printf("test_skills: OK\n"); return 0; }