From 8c4c9fb400cbe1ed9d095ce8f64ad076b8f4c30b Mon Sep 17 00:00:00 2001 From: Holodeck23 Date: Fri, 21 Aug 2026 09:55:07 +0200 Subject: [PATCH 1/2] fix: accept numeric-leading skill names --- src/adapters/portable.ts | 4 ++-- src/ir.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/adapters/portable.ts b/src/adapters/portable.ts index ef21368..32cbd29 100644 --- a/src/adapters/portable.ts +++ b/src/adapters/portable.ts @@ -107,11 +107,11 @@ export class PortableAdapter implements Adapter { if (!ir.identity.name) { issues.push({ severity: 'error', field: 'identity.name', message: 'name is required' }); } - if (!/^[a-z][a-z0-9-]*$/.test(ir.identity.name)) { + if (!/^[a-z0-9][a-z0-9-]*$/.test(ir.identity.name)) { issues.push({ severity: 'error', field: 'identity.name', - message: 'name must be lowercase a-z + hyphens', + message: 'name must be lowercase a-z, 0-9 + hyphens', }); } if (!ir.identity.description.full) { diff --git a/src/ir.ts b/src/ir.ts index e8b99f0..a257f8f 100644 --- a/src/ir.ts +++ b/src/ir.ts @@ -17,7 +17,7 @@ export interface SkillIR { } export interface Identity { - /** Skill name. lowercase a-z + hyphens. Must match directory name. */ + /** Skill name. Lowercase letters, numbers, and hyphens. Must match directory name. */ name: string; description: { From ad57a27d89f6d9a0e0ecf2fd0b8ff4133b91006b Mon Sep 17 00:00:00 2001 From: Holodeck23 Date: Fri, 21 Aug 2026 09:59:02 +0200 Subject: [PATCH 2/2] test: cover numeric-leading skill names --- package.json | 1 + test/portable-name-validation.test.ts | 31 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 test/portable-name-validation.test.ts diff --git a/package.json b/package.json index 591f6e0..d648041 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ ], "scripts": { "build": "tsc", + "test": "node --import tsx --test test/portable-name-validation.test.ts", "dev": "tsx src/cli.ts", "prepublishOnly": "npm run build", "clean": "rm -rf dist" diff --git a/test/portable-name-validation.test.ts b/test/portable-name-validation.test.ts new file mode 100644 index 0000000..62cbc32 --- /dev/null +++ b/test/portable-name-validation.test.ts @@ -0,0 +1,31 @@ +import test from 'node:test'; +import assert from 'node:assert/strict'; +import { PortableAdapter } from '../src/adapters/portable.js'; +import { emptyIR } from '../src/ir.js'; + +const adapter = new PortableAdapter(); + +function validateName(name: string) { + const ir = emptyIR(name); + ir.identity.description.full = 'A test skill.'; + return adapter.validate(ir); +} + +test('accepts numeric-leading portable skill names such as 4up', () => { + const result = validateName('4up'); + + assert.equal(result.ok, true); + assert.deepEqual(result.issues, []); +}); + +test('continues rejecting invalid portable skill names', () => { + for (const name of ['Uppercase', 'has_underscore', 'has space', '']) { + const result = validateName(name); + + assert.equal(result.ok, false, `expected ${JSON.stringify(name)} to be rejected`); + assert.ok( + result.issues.some((issue) => issue.field === 'identity.name'), + `expected an identity.name issue for ${JSON.stringify(name)}` + ); + } +});