@batonfx/skills 0.3.7 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -67,9 +67,7 @@ describe("SkillLoader", () => {
67
67
  name: review
68
68
  description: Review code carefully
69
69
  when-to-use: before merging
70
- allowedTools:
71
- - read
72
- - grep
70
+ allowed-tools: read grep
73
71
  disableModelInvocation: false
74
72
  userInvocable: true
75
73
  contextFork: true
@@ -112,9 +110,10 @@ Use the checklist.
112
110
  }).pipe(Effect.provide(loaderTestLayer({ cwd: "/repo", roots: [".agents/skills"] }, files, directories, reads)))
113
111
  })
114
112
 
115
- it.effect("defaults names, namespaces nested skills, and lets later roots win collisions", () => {
113
+ it.effect("uses standard names for nested skills and lets later roots win collisions", () => {
116
114
  const files = {
117
115
  "/repo/a/frontend/lint/SKILL.md": `---
116
+ name: lint
118
117
  description: Lint frontend
119
118
  ---
120
119
  body a`,
@@ -136,10 +135,10 @@ body second`,
136
135
  return Effect.gen(function* () {
137
136
  const source = yield* SkillSource.SkillSource
138
137
  const all = yield* source.all
139
- const nested = yield* source.get("frontend:lint")
138
+ const nested = yield* source.get("lint")
140
139
  const duplicate = yield* source.get("dup")
141
140
 
142
- expect(all.map((skill) => skill.frontmatter.name)).toEqual(["dup", "frontend:lint"])
141
+ expect(all.map((skill) => skill.frontmatter.name)).toEqual(["dup", "lint"])
143
142
  expect(nested?.frontmatter.description).toBe("Lint frontend")
144
143
  expect(duplicate?.frontmatter.description).toBe("Second duplicate")
145
144
  }).pipe(Effect.provide(loaderTestLayer({ cwd: "/repo", roots: ["a", "b"] }, files, directories)))
@@ -148,6 +147,7 @@ body second`,
148
147
  it.effect("fails typed for invalid frontmatter and keeps user-only skills addressable", () => {
149
148
  const files = {
150
149
  "/repo/skills/user-only/SKILL.md": `---
150
+ name: user-only
151
151
  description: User only
152
152
  disableModelInvocation: true
153
153
  ---
@@ -184,4 +184,65 @@ body`,
184
184
  expect(SkillSource.selectListings(userOnly === undefined ? [] : [userOnly], 1_000, [])).toEqual([])
185
185
  })
186
186
  })
187
+
188
+ it.effect("enforces required standard name, directory equality, and description length", () => {
189
+ const cases = [
190
+ {
191
+ file: "/repo/skills/missing/SKILL.md",
192
+ content: `---
193
+ description: Missing name
194
+ ---
195
+ body`,
196
+ },
197
+ {
198
+ file: "/repo/skills/directory/SKILL.md",
199
+ content: `---
200
+ name: different
201
+ description: Mismatched directory
202
+ ---
203
+ body`,
204
+ },
205
+ {
206
+ file: "/repo/skills/invalid/SKILL.md",
207
+ content: `---
208
+ name: Invalid_Name
209
+ description: Invalid name
210
+ ---
211
+ body`,
212
+ },
213
+ {
214
+ file: "/repo/skills/double--dash/SKILL.md",
215
+ content: `---
216
+ name: double--dash
217
+ description: Consecutive hyphens are invalid
218
+ ---
219
+ body`,
220
+ },
221
+ {
222
+ file: "/repo/skills/long/SKILL.md",
223
+ content: `---
224
+ name: long
225
+ description: ${"x".repeat(1025)}
226
+ ---
227
+ body`,
228
+ },
229
+ ]
230
+ return Effect.gen(function* () {
231
+ for (const testCase of cases) {
232
+ const relative = testCase.file.slice("/repo/skills/".length)
233
+ const failure = yield* Effect.flip(
234
+ SkillSource.SkillSource.pipe(
235
+ Effect.provide(
236
+ loaderTestLayer(
237
+ { cwd: "/repo", roots: ["skills"] },
238
+ { [testCase.file]: testCase.content },
239
+ { "/repo/skills": [relative] },
240
+ ),
241
+ ),
242
+ ),
243
+ )
244
+ expect(failure._tag).toBe("@batonfx/core/SkillSourceError")
245
+ }
246
+ })
247
+ })
187
248
  })