Skip to content

Commit 9c06a5c

Browse files
GarethManningclaude
andcommitted
fix: make all 165 skills load in Claude Code and Codex
Both plugin manifests pointed `skills` at the bare "./skills/" root, but Claude Code and Codex scan only one directory level deep while skills are nested two levels deep (skills/<domain>/<skill>/SKILL.md), so both surfaces discovered zero skills. List the 20 domain folders explicitly in BOTH .claude-plugin/plugin.json and .codex-plugin/plugin.json. Resolve the duplicate `critical-thinking-task-designer` slug (present in curriculum-assessment and literacy-critical-thinking) that collides in any flat name-keyed loader: rename the curriculum-assessment copy to discipline-specific-critical-thinking-task-designer (folder + frontmatter name/skill_id/skill_name). The MCP server, which already namespaced the duplicate as domain__slug, now exposes both under clean un-prefixed names. Regenerated registry.json and mcp-server/src/skills.json via the repo's own scripts (165 = 165 = 165). Updated tests to the new manifest-array and resolved-collision contracts, and added a CONTRIBUTING.md governance note (name uniqueness across domains; new domains must be added to both manifests). Verified: validate-skills 0 errors, validate-registry 165/20, claude plugin validate passes, `claude plugin details` reports Skills (165), both Playwright suites green (root 21, mcp-server 20). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c76522e commit 9c06a5c

8 files changed

Lines changed: 264 additions & 172 deletions

File tree

.claude-plugin/plugin.json

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,26 @@
99
},
1010
"homepage": "https://github.com/GarethManning/education-agent-skills",
1111
"license": "CC BY-SA 4.0",
12-
"skills": "./skills/"
12+
"skills": [
13+
"./skills/ai-learning-science",
14+
"./skills/ai-literacy",
15+
"./skills/curriculum-alignment",
16+
"./skills/curriculum-assessment",
17+
"./skills/eal-language-development",
18+
"./skills/environmental-experiential-learning",
19+
"./skills/explicit-instruction",
20+
"./skills/global-cross-cultural-pedagogies",
21+
"./skills/historical-thinking",
22+
"./skills/inclusive-design",
23+
"./skills/literacy-critical-thinking",
24+
"./skills/memory-learning-science",
25+
"./skills/montessori-alternative-approaches",
26+
"./skills/original-frameworks",
27+
"./skills/professional-learning",
28+
"./skills/questioning-discussion",
29+
"./skills/self-regulated-learning",
30+
"./skills/student-learning",
31+
"./skills/systems-thinking",
32+
"./skills/wellbeing-motivation-agency"
33+
]
1334
}

.codex-plugin/plugin.json

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,28 @@
44
"description": "165 evidence-based education skills for curriculum design, lesson planning, assessment, and AI-assisted teaching — built on the Agent Skills open standard.",
55
"repository": "https://github.com/GarethManning/education-agent-skills",
66
"license": "CC-BY-SA-4.0",
7-
"skills": "./skills/",
7+
"skills": [
8+
"./skills/ai-learning-science",
9+
"./skills/ai-literacy",
10+
"./skills/curriculum-alignment",
11+
"./skills/curriculum-assessment",
12+
"./skills/eal-language-development",
13+
"./skills/environmental-experiential-learning",
14+
"./skills/explicit-instruction",
15+
"./skills/global-cross-cultural-pedagogies",
16+
"./skills/historical-thinking",
17+
"./skills/inclusive-design",
18+
"./skills/literacy-critical-thinking",
19+
"./skills/memory-learning-science",
20+
"./skills/montessori-alternative-approaches",
21+
"./skills/original-frameworks",
22+
"./skills/professional-learning",
23+
"./skills/questioning-discussion",
24+
"./skills/self-regulated-learning",
25+
"./skills/student-learning",
26+
"./skills/systems-thinking",
27+
"./skills/wellbeing-motivation-agency"
28+
],
829
"interface": {
930
"displayName": "Education Agent Skills",
1031
"shortDescription": "165 evidence-based skills for curriculum design, lesson planning, and assessment",

CONTRIBUTING.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,23 @@ Every `SKILL.md` must preserve:
5151
- input and output schemas;
5252
- any existing citations and limitations.
5353

54+
### Skill `name` must be unique across all domains
55+
56+
A skill's `name` (and its `skill_id` slug) must be **unique across the whole library, not just within its own domain folder**. Claude Code and Codex identify a skill by its `name`; a flat, name-keyed loader cannot hold two skills with the same `name`, so one silently collides with or overwrites the other. (The MCP server tolerates duplicates by qualifying them with the domain, but the plugin surfaces do not.)
57+
58+
If two skills would share a slug, give one a more specific name — for example `discipline-specific-critical-thinking-task-designer` rather than a second `critical-thinking-task-designer` — and update that `SKILL.md`'s frontmatter `name`, `skill_id`, and `skill_name` plus its folder name to match.
59+
60+
### New domain folders must be added to BOTH plugin manifests
61+
62+
The `skills` field in each plugin manifest is an **explicit array of the domain folder paths** (e.g. `"./skills/curriculum-assessment"`), because Claude Code and Codex scan only one directory level deep while this repo nests skills two levels deep (`skills/<domain>/<skill>/SKILL.md`). A bare `"./skills/"` root path makes both loaders discover zero skills.
63+
64+
When you add a **new domain folder**, add its path to the `skills` array in **both**:
65+
66+
- `.claude-plugin/plugin.json`
67+
- `.codex-plugin/plugin.json`
68+
69+
(`.claude-plugin/marketplace.json` inherits via its `source` field and does not list skills.)
70+
5471
## Validation before a pull request
5572

5673
After adding or editing skills, run:

mcp-server/src/skills.json

Lines changed: 120 additions & 120 deletions
Large diffs are not rendered by default.

mcp-server/tests/mcp-server.spec.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,17 +153,22 @@ test.describe("MCP Server — skill tools", () => {
153153
expect(text).toContain("Year 9 novice");
154154
});
155155

156-
test("handles collision names with domain prefix", async () => {
156+
// The former slug collision (two `critical-thinking-task-designer` skills) was
157+
// resolved by renaming the curriculum-assessment copy. With no duplicate slug,
158+
// buildToolName exposes clean, un-prefixed tool names on both surfaces.
159+
test("exposes both critical-thinking skills under distinct un-prefixed names", async () => {
157160
const { tools } = await client.listTools();
158161
const criticalThinkingTools = tools.filter((t) =>
159162
t.name.includes("critical-thinking-task-designer"),
160163
);
161164

162165
expect(criticalThinkingTools.length).toBe(2);
163166
expect(criticalThinkingTools.map((t) => t.name).sort()).toEqual([
164-
"curriculum-assessment__critical-thinking-task-designer",
165-
"literacy-critical-thinking__critical-thinking-task-designer",
167+
"critical-thinking-task-designer",
168+
"discipline-specific-critical-thinking-task-designer",
166169
]);
170+
// No domain-prefixed (collision) form should remain.
171+
expect(tools.some((t) => t.name.includes("__"))).toBe(false);
167172
});
168173
});
169174

@@ -201,17 +206,21 @@ test.describe("MCP Server — skill prompts", () => {
201206
expect(text.text).toContain("Year 9 novice");
202207
});
203208

204-
test("handles collision names with domain prefix", async () => {
209+
// Mirror of the tools-side check: the resolved collision means both
210+
// critical-thinking prompts are registered under clean, un-prefixed names.
211+
test("exposes both critical-thinking prompts under distinct un-prefixed names", async () => {
205212
const { prompts } = await client.listPrompts();
206213
const criticalThinking = prompts.filter((p) =>
207214
p.name.includes("critical-thinking-task-designer"),
208215
);
209216

210217
expect(criticalThinking.length).toBe(2);
211218
expect(criticalThinking.map((p) => p.name).sort()).toEqual([
212-
"curriculum-assessment__critical-thinking-task-designer",
213-
"literacy-critical-thinking__critical-thinking-task-designer",
219+
"critical-thinking-task-designer",
220+
"discipline-specific-critical-thinking-task-designer",
214221
]);
222+
// No domain-prefixed (collision) form should remain.
223+
expect(prompts.some((p) => p.name.includes("__"))).toBe(false);
215224
});
216225
});
217226

registry.json

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"version": "2.0",
3-
"generated": "2026-05-27T14:54:56.907578+00:00",
3+
"generated": "2026-06-26T11:24:25.994801+00:00",
44
"standard": "agent-skills-1.0",
55
"total_skills": 165,
66
"domains": [
@@ -1051,41 +1051,6 @@
10511051
},
10521052
"path": "skills/curriculum-assessment/criterion-referenced-rubric-generator/SKILL.md"
10531053
},
1054-
{
1055-
"id": "curriculum-assessment/critical-thinking-task-designer",
1056-
"name": "critical-thinking-task-designer",
1057-
"display_name": "Critical Thinking Task Designer",
1058-
"domain": "curriculum-assessment",
1059-
"description": "Design discipline-specific critical thinking tasks grounded in knowledge-contingent reasoning rather than generic skills. Use when embedding higher-order thinking into subject content.",
1060-
"disable_model_invocation": false,
1061-
"evidence_strength": "strong",
1062-
"tags": [
1063-
"critical-thinking",
1064-
"disciplinary-thinking",
1065-
"horizontal-knowledge",
1066-
"knowledge-contingent",
1067-
"task-design",
1068-
"intellectual-resources",
1069-
"Bailin",
1070-
"Willingham",
1071-
"domain-specific"
1072-
],
1073-
"teacher_time": "10 minutes",
1074-
"chains_with": [
1075-
"curriculum-knowledge-architecture-designer",
1076-
"kud-knowledge-type-mapper",
1077-
"coherent-rubric-logic-builder",
1078-
"learning-target-authoring-guide",
1079-
"ai-output-critical-audit-designer"
1080-
],
1081-
"chain_edges": {
1082-
"receives_from": [],
1083-
"feeds_into": [],
1084-
"output_field": null,
1085-
"input_field": null
1086-
},
1087-
"path": "skills/curriculum-assessment/critical-thinking-task-designer/SKILL.md"
1088-
},
10891054
{
10901055
"id": "curriculum-assessment/curriculum-knowledge-architecture-designer",
10911056
"name": "curriculum-knowledge-architecture-designer",
@@ -1150,6 +1115,41 @@
11501115
},
11511116
"path": "skills/curriculum-assessment/differentiation-adapter/SKILL.md"
11521117
},
1118+
{
1119+
"id": "curriculum-assessment/discipline-specific-critical-thinking-task-designer",
1120+
"name": "discipline-specific-critical-thinking-task-designer",
1121+
"display_name": "Discipline-Specific Critical Thinking Task Designer",
1122+
"domain": "curriculum-assessment",
1123+
"description": "Design discipline-specific critical thinking tasks grounded in knowledge-contingent reasoning rather than generic skills. Use when embedding higher-order thinking into subject content.",
1124+
"disable_model_invocation": false,
1125+
"evidence_strength": "strong",
1126+
"tags": [
1127+
"critical-thinking",
1128+
"disciplinary-thinking",
1129+
"horizontal-knowledge",
1130+
"knowledge-contingent",
1131+
"task-design",
1132+
"intellectual-resources",
1133+
"Bailin",
1134+
"Willingham",
1135+
"domain-specific"
1136+
],
1137+
"teacher_time": "10 minutes",
1138+
"chains_with": [
1139+
"curriculum-knowledge-architecture-designer",
1140+
"kud-knowledge-type-mapper",
1141+
"coherent-rubric-logic-builder",
1142+
"learning-target-authoring-guide",
1143+
"ai-output-critical-audit-designer"
1144+
],
1145+
"chain_edges": {
1146+
"receives_from": [],
1147+
"feeds_into": [],
1148+
"output_field": null,
1149+
"input_field": null
1150+
},
1151+
"path": "skills/curriculum-assessment/discipline-specific-critical-thinking-task-designer/SKILL.md"
1152+
},
11531153
{
11541154
"id": "curriculum-assessment/formative-assessment-technique-selector",
11551155
"name": "formative-assessment-technique-selector",

skills/curriculum-assessment/critical-thinking-task-designer/SKILL.md renamed to skills/curriculum-assessment/discipline-specific-critical-thinking-task-designer/SKILL.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
---
22
# AGENT SKILLS STANDARD FIELDS (v2)
3-
name: critical-thinking-task-designer
3+
name: discipline-specific-critical-thinking-task-designer
44
description: "Design discipline-specific critical thinking tasks grounded in knowledge-contingent reasoning rather than generic skills. Use when embedding higher-order thinking into subject content."
55
disable-model-invocation: false
66
user-invocable: true
77
effort: medium
88

99
# EXISTING FIELDS
1010

11-
skill_id: "curriculum-assessment/critical-thinking-task-designer"
12-
skill_name: "Critical Thinking Task Designer"
11+
skill_id: "curriculum-assessment/discipline-specific-critical-thinking-task-designer"
12+
skill_name: "Discipline-Specific Critical Thinking Task Designer"
1313
domain: "curriculum-assessment"
1414
version: "1.0"
1515
evidence_strength: "strong"

tests/skills-library.spec.ts

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as yaml from "yaml";
66
const SKILLS_DIR = path.join(__dirname, "..", "skills");
77
const REGISTRY_PATH = path.join(__dirname, "..", "registry.json");
88
const PLUGIN_PATH = path.join(__dirname, "..", ".claude-plugin", "plugin.json");
9+
const CODEX_PLUGIN_PATH = path.join(__dirname, "..", ".codex-plugin", "plugin.json");
910

1011
// Helper: collect all SKILL.md paths
1112
function getAllSkillPaths(): string[] {
@@ -188,7 +189,7 @@ test.describe("Plugin Manifest Validation", () => {
188189
name: string;
189190
display_name: string;
190191
version: string;
191-
skills: string;
192+
skills: string[];
192193
license: string;
193194
};
194195

@@ -202,10 +203,33 @@ test.describe("Plugin Manifest Validation", () => {
202203
expect(plugin.version).toBe("2.1.0");
203204
});
204205

205-
test("plugin.json skills directory exists", () => {
206-
const skillsDir = path.join(__dirname, "..", plugin.skills);
207-
expect(fs.existsSync(skillsDir)).toBe(true);
208-
expect(fs.statSync(skillsDir).isDirectory()).toBe(true);
206+
// Claude Code and Codex scan only one directory level deep, while skills are
207+
// nested two levels deep (skills/<domain>/<skill>/SKILL.md). The `skills`
208+
// field must therefore be an explicit array of every domain folder, not the
209+
// bare "./skills/" root (which would discover zero skills).
210+
test("plugin.json skills field lists every domain folder", () => {
211+
const domainsOnDisk = fs
212+
.readdirSync(SKILLS_DIR)
213+
.filter((d) => fs.statSync(path.join(SKILLS_DIR, d)).isDirectory())
214+
.map((d) => `./skills/${d}`)
215+
.sort();
216+
217+
expect(Array.isArray(plugin.skills)).toBe(true);
218+
expect([...plugin.skills].sort()).toEqual(domainsOnDisk);
219+
220+
for (const entry of plugin.skills) {
221+
const skillsDir = path.join(__dirname, "..", entry);
222+
expect(fs.existsSync(skillsDir)).toBe(true);
223+
expect(fs.statSync(skillsDir).isDirectory()).toBe(true);
224+
}
225+
});
226+
227+
// The Codex manifest must stay in lockstep with the Claude manifest so the
228+
// same domain folders load on both surfaces (see CONTRIBUTING.md governance).
229+
test("codex plugin.json skills field matches the claude manifest", () => {
230+
const codex = JSON.parse(fs.readFileSync(CODEX_PLUGIN_PATH, "utf-8"));
231+
expect(Array.isArray(codex.skills)).toBe(true);
232+
expect([...codex.skills].sort()).toEqual([...plugin.skills].sort());
209233
});
210234

211235
test("plugin.json has correct license", () => {

0 commit comments

Comments
 (0)