Files
agent-skills/scripts/lib/skill-lint-test.js
T

142 lines
4.5 KiB
JavaScript

#!/usr/bin/env node
'use strict';
const assert = require('node:assert/strict');
const { test } = require('node:test');
const { lintSkillContent } = require('./skill-lint.js');
const KNOWN = new Set(['alpha', 'beta']);
/** A SKILL.md body carrying every required section, so tests can isolate frontmatter. */
function withAllSections(frontmatter) {
return [
frontmatter,
'',
'## Overview',
'x',
'## When to Use',
'x',
'## Common Rationalizations',
'x',
'## Red Flags',
'x',
'## Verification',
'x',
'',
].join('\n');
}
const VALID_FRONTMATTER = [
'---',
'name: alpha',
'description: Designs alphas. Use when building one.',
'---',
].join('\n');
// ─── Section exemptions ──────────────────────────────────────────────────────
test('a directory named after an Object.prototype key is not exempt from section checks', () => {
// `constructor` satisfies KEBAB_CASE, and `dirName in SECTION_EXEMPT_SKILLS`
// finds it on the prototype chain — silently skipping every section check.
const content = [
'---',
'name: constructor',
'description: Does a thing. Use when you need it.',
'---',
'',
'no sections here',
'',
].join('\n');
const { errors, exempt } = lintSkillContent('constructor', content, KNOWN);
assert.equal(exempt, false, 'exemptions must come from the allowlist, not the prototype chain');
assert.equal(errors.filter(e => /Missing required section/.test(e)).length, 5);
});
test('a genuinely allowlisted skill is still exempt', () => {
const content = [
'---',
'name: using-agent-skills',
'description: Routes to other skills. Use when choosing one.',
'---',
'',
'no sections here',
'',
].join('\n');
const { errors, exempt } = lintSkillContent('using-agent-skills', content, KNOWN);
assert.equal(exempt, true);
assert.deepEqual(errors.filter(e => /Missing required section/.test(e)), []);
});
test('a skill claiming its own exemption without being allowlisted fails loud', () => {
const content = withAllSections(
['---', 'name: alpha', 'description: Designs alphas. Use when building one.', 'exempt: sections', '---'].join('\n')
);
const { errors } = lintSkillContent('alpha', content, KNOWN);
assert.equal(errors.length, 1);
assert.match(errors[0], /not in the validator's SECTION_EXEMPT_SKILLS allowlist/);
});
// ─── Guardrails on the rules this change sits beside ─────────────────────────
// Deliberately narrow: #428 rewrites frontmatter parsing and the cross-reference
// patterns, so asserting their behaviour here would collide with that work.
test('a fully valid skill produces no errors', () => {
const { errors } = lintSkillContent('alpha', withAllSections(VALID_FRONTMATTER), KNOWN);
assert.deepEqual(errors, []);
});
test('reports a description with no trigger clause', () => {
const content = withAllSections(
['---', 'name: alpha', 'description: Designs alpha things and nothing more.', '---'].join('\n')
);
const { errors } = lintSkillContent('alpha', content, KNOWN);
assert.equal(errors.length, 1);
assert.match(errors[0], /no 'when to use' trigger/);
});
test('reports frontmatter name that disagrees with the directory', () => {
const content = withAllSections(
['---', 'name: beta', 'description: Designs alphas. Use when building one.', '---'].join('\n')
);
const { errors } = lintSkillContent('alpha', content, KNOWN);
assert.equal(errors.length, 1);
assert.match(errors[0], /does not match directory name/);
});
test('reports a workflow step declared without a matching process section', () => {
const content = withAllSections(VALID_FRONTMATTER).replace(
'## Common Rationalizations',
[
'## The Optimization Workflow',
'',
'```',
'1. MEASURE → Establish a baseline',
'2. GUARD → Prevent regression',
'```',
'',
'### Step 1: Measure',
'',
'Measure first.',
'',
'## Common Rationalizations',
].join('\n'),
);
const { errors } = lintSkillContent('alpha', content, KNOWN);
assert.equal(errors.length, 1);
assert.match(errors[0], /Workflow declares Step 2 but has no matching process section/);
});
test('reports a missing frontmatter block', () => {
const { errors } = lintSkillContent('alpha', '## Overview\nx\n', KNOWN);
assert.equal(errors.length, 1);
assert.match(errors[0], /Missing or malformed YAML frontmatter/);
});