Skip to content
149 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Agents → Skills is the workspace's skill library in the open SKILL.md format: write a skill in g1t, import a SKILL.md or zip, link a repository whose .g1t/skills folders publish on every push, or save a finished session as a skill once a person reviews it; attach a skill to an agent, a team or the whole workspace, each attachment pinned to a version. Agents see each skill's name and when to use it, and read the rest with use_skill when a request needs it; a skill never adds a permission, and scripts wait for an agent's own computer. The agent skills guide says how.1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import {
5 AGENT_TOOL_NAMES,
6 RESERVED_SKILL_NAMES,
7 SKILL_FOLDER_MAX_BYTES,
8 checkSkillFolder,
9 foundationalSkillMd,
10 parseFrontMatter,
11 renderSkillMd,
12 skillFileBytes,
13 skillNameProblem,
14 splitFrontMatter,
15} from "./skill-format.ts";
16import { FOUNDATIONAL_SKILLS, FOUNDATIONAL_SKILL_IDS } from "./skills.ts";
17
18test("front-matter as SKILL.md files write it", () => {
19 const front = parseFrontMatter(
20 [
21 "name: pdf-forms",
22 "description: >",
23 " Use when filling PDF forms:",
24 " text fields and checkboxes.",
25 "",
26 " Not for scans.",
27 "license: 'Apache-2.0'",
28 "allowed-tools: Read Write",
29 "tools:",
30 " - make_file",
31 " - read_artifact",
32 "requires_computer: true",
33 "version: 2",
34 "metadata:",
35 " author: \"Ana \\\"A\\\" Lima\"",
36 " tags: [forms, \"pdf, docs\"]",
37 "notes: |",
38 " line one",
39 " indented",
40 "# a comment",
41 "plain: words: with a colon # and a comment",
42 "",
43 ].join("\n"),
44 );
45 assert.deepEqual(front, {
46 name: "pdf-forms",
47 description: "Use when filling PDF forms: text fields and checkboxes.\nNot for scans.\n",
48 license: "Apache-2.0",
49 "allowed-tools": "Read Write",
50 tools: ["make_file", "read_artifact"],
51 requires_computer: true,
52 version: 2,
53 metadata: { author: 'Ana "A" Lima', tags: ["forms", "pdf, docs"] },
54 notes: "line one\n indented\n",
55 plain: "words: with a colon",
56 });
57 // Lists at the key's own indentation, and folded plain values.
58 assert.deepEqual(parseFrontMatter("tools:\n- read_file\n- search_code\ndescription: Use when\n reading code.\n"), { tools: ["read_file", "search_code"], description: "Use when reading code." });
59 assert.throws(() => parseFrontMatter("name: a\nname: b\n"), /name is given twice/);
60 assert.throws(() => parseFrontMatter("just words\n"), /line 2: expected "key: value"/);
61 assert.throws(() => parseFrontMatter('name: "open\n'), /isn't closed/);
62});
63
64test("SKILL.md splits into front-matter and body, CRLF and BOM included", () => {
65 const split = splitFrontMatter("---\r\nname: a\r\ndescription: b\r\n---\r\n\r\n# Body\r\n");
66 assert.ok(split.ok);
67 assert.equal(split.ok && split.yaml, "name: a\ndescription: b\n");
68 assert.equal(split.ok && split.body, "\n# Body\n");
69 assert.equal(splitFrontMatter("# No front-matter").ok, false);
70 assert.equal(splitFrontMatter("---\nname: a\n").ok, false);
71});
72
73test("a skill folder is checked: names, description, tools agents have, paths, size, scripts", () => {
74 const md = (front: string, body = "Do the thing.") => `---\n${front}\n---\n\n${body}\n`;
75 const good = checkSkillFolder([
76 { path: "SKILL.md", content: md("name: release-notes\ndescription: Use when someone asks for release notes.\ntools: make_file, read_file\nlicense: MIT") },
77 { path: "./resources/template.md", content: "## Added" },
78 { path: "scripts/collect.sh", content: "echo hi" },
79 { path: "assets/logo.png", content: "iVBORw0KGgo=", encoding: "base64" },
80 ]);
81 assert.ok(good.ok);
82 if (!good.ok) return;
83 assert.equal(good.skill.name, "release-notes");
84 assert.deepEqual(good.skill.tools, ["make_file", "read_file"]);
85 assert.equal(good.skill.requires_computer, true, "scripts need a computer");
86 assert.deepEqual(good.skill.scripts, ["scripts/collect.sh"]);
87 assert.deepEqual(good.skill.files.map((f) => f.path), ["assets/logo.png", "resources/template.md", "scripts/collect.sh"]);
88 assert.deepEqual(good.skill.extra, { license: "MIT" });
89 assert.equal(good.skill.body, "Do the thing.");
90
91 const bad = (files: Parameters<typeof checkSkillFolder>[0], pattern: RegExp, expectName?: string) => {
92 const checked = checkSkillFolder(files, { expectName });
93 assert.equal(checked.ok, false, pattern.source);
94 assert.match(!checked.ok ? checked.message : "", pattern);
95 };
96 bad([], /a folder with a SKILL\.md/);
97 bad([{ path: "README.md", content: "x" }], /needs a SKILL\.md at its top/);
98 bad([{ path: "SKILL.md", content: md("description: x") }], /needs a name/);
99 bad([{ path: "SKILL.md", content: md("name: Release_Notes\ndescription: x") }], /lowercase letters, digits and single hyphens/);
100 bad([{ path: "SKILL.md", content: md("name: code\ndescription: x") }], /one of g1t's foundational skills/);
101 bad([{ path: "SKILL.md", content: md("name: a-b") }], /needs a description/);
102 bad([{ path: "SKILL.md", content: md(`name: a\ndescription: ${"x".repeat(1025)}`) }], /at most 1024 characters/);
103 bad([{ path: "SKILL.md", content: md("name: a\ndescription: x\ntools: [bash]") }], /names bash, which isn't a tool agents have/);
104 bad([{ path: "SKILL.md", content: md("name: a\ndescription: x\nrequires_computer: maybe") }], /requires_computer: is true or false/);
105 bad([{ path: "SKILL.md", content: md("name: a\ndescription: x", "") }], /needs instructions/);
106 bad([{ path: "SKILL.md", content: md("name: a\ndescription: x") }, { path: "../etc/passwd", content: "x" }], /isn't a path a skill can hold/);
107 bad([{ path: "SKILL.md", content: md("name: a\ndescription: x") }, { path: "skill.md", content: "x" }], /twice/);
108 bad([{ path: "SKILL.md", content: md("name: a\ndescription: x") }, { path: "big.txt", content: "x".repeat(SKILL_FOLDER_MAX_BYTES) }], /at most 1 MB/);
109 bad([{ path: "SKILL.md", content: md("name: a\ndescription: x") }], /The folder is b, but its SKILL\.md is named a/, "b");
110 assert.equal(skillFileBytes({ path: "x", content: "aGk=", encoding: "base64" }), 2);
111 assert.equal(skillFileBytes({ path: "x", content: "é" }), 2);
112 assert.equal(skillNameProblem("x".repeat(65))?.includes("at most 64"), true);
113});
114
115test("the editor writes SKILL.md that reads back the same", () => {
116 const written = renderSkillMd({
117 name: "brand-voice",
118 description: "Use when writing: posts, emails # and docs",
119 tools: ["create_artifact"],
120 requires_computer: true,
121 body: "# Voice\r\n\r\nWarm.",
122 extra: { license: "MIT", metadata: { version: "1.0", author: "acme" } },
123 });
124 assert.equal(
125 written,
126 '---\nname: brand-voice\ndescription: "Use when writing: posts, emails # and docs"\ntools: [create_artifact]\nrequires_computer: true\nlicense: MIT\nmetadata:\n version: "1.0"\n author: acme\n---\n\n# Voice\n\nWarm.\n',
127 );
128 const checked = checkSkillFolder([{ path: "SKILL.md", content: written }]);
129 assert.ok(checked.ok);
130 assert.equal(checked.ok && checked.skill.description, "Use when writing: posts, emails # and docs");
131 assert.deepEqual(checked.ok && checked.skill.extra, { license: "MIT", metadata: { version: "1.0", author: "acme" } });
132});
133
134test("g1t's foundational skills are written in the same format, and read back as themselves", () => {
135 assert.deepEqual(RESERVED_SKILL_NAMES, FOUNDATIONAL_SKILL_IDS);
136 for (const skill of FOUNDATIONAL_SKILLS) {
137 const md = foundationalSkillMd(skill);
138 // Checked as a library skill would be, apart from the reserved name.
139 const front = parseFrontMatter((splitFrontMatter(md) as { yaml: string }).yaml);
140 assert.equal(front.name, skill.id);
141 assert.equal(front.description, skill.when);
142 assert.match(skill.when, /^Use when /);
143 for (const tool of front.tools as string[]) assert.ok(AGENT_TOOL_NAMES.includes(tool), `${skill.id}: ${tool}`);
144 assert.deepEqual(front.metadata, { source: "g1t", version: skill.version });
145 assert.ok(md.includes(skill.instructions), `${skill.id} keeps its playbook`);
146 assert.match(md, /## What works today/);
147 if (skill.abilities.some((a) => a.status === "coming")) assert.match(md, /## Not yet in g1t/);
148 }
149});