Merge branch 'codex/validator-alignment'

This commit is contained in:
sck_0
2026-02-02 21:43:27 +01:00
6 changed files with 208 additions and 140 deletions

View File

@@ -21,6 +21,7 @@ The skill MUST have a section explicitly stating when to trigger it.
- **Good**: "Use when the user asks to debug a React component." - **Good**: "Use when the user asks to debug a React component."
- **Bad**: "This skill helps you with code." - **Bad**: "This skill helps you with code."
Accepted headings: `## When to Use`, `## Use this skill when`, `## When to Use This Skill`.
### 3. Safety & Risk Classification ### 3. Safety & Risk Classification

View File

@@ -8,6 +8,8 @@ const SKILLS_DIR = path.join(ROOT, 'skills');
const ALLOWED_FIELDS = new Set([ const ALLOWED_FIELDS = new Set([
'name', 'name',
'description', 'description',
'risk',
'source',
'license', 'license',
'compatibility', 'compatibility',
'metadata', 'metadata',

View File

@@ -0,0 +1,18 @@
import os
import sys
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from validate_skills import has_when_to_use_section
SAMPLES = [
("## When to Use", True),
("## Use this skill when", True),
("## When to Use This Skill", True),
("## Overview", False),
]
for heading, expected in SAMPLES:
content = f"\n{heading}\n- item\n"
assert has_when_to_use_section(content) is expected, heading
print("ok")

View File

@@ -0,0 +1,16 @@
const assert = require('assert');
const { hasUseSection } = require('../validate-skills');
const samples = [
['## When to Use', true],
['## Use this skill when', true],
['## When to Use This Skill', true],
['## Overview', false],
];
for (const [heading, expected] of samples) {
const content = `\n${heading}\n- item\n`;
assert.strictEqual(hasUseSection(content), expected, heading);
}
console.log('ok');

View File

@@ -32,12 +32,24 @@ const MAX_SKILL_LINES = 500;
const ALLOWED_FIELDS = new Set([ const ALLOWED_FIELDS = new Set([
'name', 'name',
'description', 'description',
'risk',
'source',
'license', 'license',
'compatibility', 'compatibility',
'metadata', 'metadata',
'allowed-tools', 'allowed-tools',
]); ]);
const USE_SECTION_PATTERNS = [
/^##\s+When\s+to\s+Use/im,
/^##\s+Use\s+this\s+skill\s+when/im,
/^##\s+When\s+to\s+Use\s+This\s+Skill/im,
];
function hasUseSection(content) {
return USE_SECTION_PATTERNS.some(pattern => pattern.test(content));
}
function isPlainObject(value) { function isPlainObject(value) {
return value && typeof value === 'object' && !Array.isArray(value); return value && typeof value === 'object' && !Array.isArray(value);
} }
@@ -99,6 +111,7 @@ function addStrictSectionErrors(label, missing, baselineSet) {
} }
} }
function run() {
const skillIds = listSkillIds(SKILLS_DIR); const skillIds = listSkillIds(SKILLS_DIR);
const baseline = loadBaseline(); const baseline = loadBaseline();
const baselineUse = new Set(baseline.useSection || []); const baselineUse = new Set(baseline.useSection || []);
@@ -202,7 +215,7 @@ for (const skillId of skillIds) {
longFiles.push(skillId); longFiles.push(skillId);
} }
if (!content.includes('## Use this skill when')) { if (!hasUseSection(content)) {
missingUseSection.push(skillId); missingUseSection.push(skillId);
} }
@@ -268,3 +281,13 @@ if (errors.length) {
} }
console.log(`Validation passed for ${skillIds.length} skills.`); console.log(`Validation passed for ${skillIds.length} skills.`);
}
if (require.main === module) {
run();
}
module.exports = {
hasUseSection,
run,
};

View File

@@ -3,6 +3,15 @@ import re
import argparse import argparse
import sys import sys
WHEN_TO_USE_PATTERNS = [
re.compile(r"^##\s+When\s+to\s+Use", re.MULTILINE | re.IGNORECASE),
re.compile(r"^##\s+Use\s+this\s+skill\s+when", re.MULTILINE | re.IGNORECASE),
re.compile(r"^##\s+When\s+to\s+Use\s+This\s+Skill", re.MULTILINE | re.IGNORECASE),
]
def has_when_to_use_section(content):
return any(pattern.search(content) for pattern in WHEN_TO_USE_PATTERNS)
def parse_frontmatter(content): def parse_frontmatter(content):
""" """
Simple frontmatter parser using regex to avoid external dependencies. Simple frontmatter parser using regex to avoid external dependencies.
@@ -30,7 +39,6 @@ def validate_skills(skills_dir, strict_mode=False):
# Pre-compiled regex # Pre-compiled regex
security_disclaimer_pattern = re.compile(r"AUTHORIZED USE ONLY", re.IGNORECASE) security_disclaimer_pattern = re.compile(r"AUTHORIZED USE ONLY", re.IGNORECASE)
trigger_section_pattern = re.compile(r"^##\s+When to Use", re.MULTILINE | re.IGNORECASE)
valid_risk_levels = ["none", "safe", "critical", "offensive"] valid_risk_levels = ["none", "safe", "critical", "offensive"]
@@ -80,7 +88,7 @@ def validate_skills(skills_dir, strict_mode=False):
else: warnings.append(msg) else: warnings.append(msg)
# 3. Content Checks (Triggers) # 3. Content Checks (Triggers)
if not trigger_section_pattern.search(content): if not has_when_to_use_section(content):
msg = f"⚠️ {rel_path}: Missing '## When to Use' section" msg = f"⚠️ {rel_path}: Missing '## When to Use' section"
if strict_mode: errors.append(msg.replace("⚠️", "")) if strict_mode: errors.append(msg.replace("⚠️", ""))
else: warnings.append(msg) else: warnings.append(msg)