fix(install): reference all curated skills in modules + reverse-coverage guard (#2431) (#2440)

* fix(install): reference all curated skills in modules + add reverse-coverage guard (#2431)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(install): normalize path separators in delivery-gate dry-run assertion (#2431)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: affaan <affaan@itomarkets.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-07-08 17:14:52 -04:00 committed by GitHub
parent 67537ea480
commit 38a7ebbe32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 349 additions and 14 deletions

View file

@ -16,6 +16,11 @@ const COMPONENTS_MANIFEST_PATH = path.join(REPO_ROOT, 'manifests/install-compone
const MODULES_SCHEMA_PATH = path.join(REPO_ROOT, 'schemas/install-modules.schema.json');
const PROFILES_SCHEMA_PATH = path.join(REPO_ROOT, 'schemas/install-profiles.schema.json');
const COMPONENTS_SCHEMA_PATH = path.join(REPO_ROOT, 'schemas/install-components.schema.json');
const CURATED_SKILLS_DIR = path.join(REPO_ROOT, 'skills');
// Empty by default; add only curated skills that are intentionally unshipped.
const INTENTIONALLY_UNSHIPPED_SKILL_IDS = new Set([
'skill-comply', // meta/measurement dev-skill; ships committed .pyc artifacts and a nested .gitignore, revisit after packaging cleanup
]);
const COMPONENT_FAMILY_PREFIXES = {
baseline: 'baseline:',
language: 'lang:',
@ -36,6 +41,18 @@ function normalizeRelativePath(relativePath) {
return String(relativePath).replace(/\\/g, '/').replace(/\/+$/, '');
}
function isCuratedSkillReferenced(claimedPaths, skillId) {
const skillRoot = `skills/${skillId}`;
for (const claimedPath of claimedPaths.keys()) {
if (claimedPath === skillRoot || claimedPath.startsWith(`${skillRoot}/`)) {
return true;
}
}
return false;
}
function validateSchema(ajv, schemaPath, data, label) {
const schema = readJson(schemaPath, `${label} schema`);
const validate = ajv.compile(schema);
@ -131,6 +148,30 @@ function validateInstallManifests() {
}
}
if (fs.existsSync(CURATED_SKILLS_DIR)) {
const entries = fs.readdirSync(CURATED_SKILLS_DIR, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory() || entry.name.startsWith('.')) {
continue;
}
const skillMdPath = path.join(CURATED_SKILLS_DIR, entry.name, 'SKILL.md');
if (!fs.existsSync(skillMdPath)) {
continue;
}
if (
!INTENTIONALLY_UNSHIPPED_SKILL_IDS.has(entry.name)
&& !isCuratedSkillReferenced(claimedPaths, entry.name)
) {
console.error(
`ERROR: curated skill skills/${entry.name} is not referenced by any install module`
);
hasErrors = true;
}
}
}
const profiles = profilesData.profiles || {};
const components = Array.isArray(componentsData.components) ? componentsData.components : [];
const expectedProfileIds = ['core', 'developer', 'security', 'research', 'full'];