mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 14:16:28 +03:00
fix(cleanup): authoritative lock signal + fingerprint fallback for orphan dirs
The content heuristic for deciding whether a deprecated skill dir belongs to us returned false for harden and optimize (their v2.x SKILL.md never said "impeccable"), while lock-entry cleanup used the authoritative source field. Result: lock entries purged, dirs orphaned. Layer three signals now: lock source (authoritative), word heuristic, then per-skill description fingerprints for the two stock v2.x skills that predate the self-identification convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
05b0ac3e1f
commit
67e468f84c
@@ -57,6 +57,17 @@ const HARNESS_DIRS = [
|
||||
'.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev',
|
||||
];
|
||||
|
||||
// Per-skill fingerprints for SKILL.md bodies that never mentioned
|
||||
// "impeccable" in their v2.x source. Used as a last-resort match
|
||||
// when no skills-lock.json exists and the word heuristic fails.
|
||||
// The strings are lifted verbatim from the v2.x frontmatter
|
||||
// descriptions, so collisions with hand-written user skills are
|
||||
// vanishingly unlikely.
|
||||
const SKILL_FINGERPRINTS = {
|
||||
harden: 'Make interfaces production-ready: error handling, empty states',
|
||||
optimize: 'Diagnoses and fixes UI performance across loading speed',
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk up from startDir until we find a directory that looks like a
|
||||
* project root (has package.json, .git, or skills-lock.json).
|
||||
@@ -93,26 +104,32 @@ export function loadLock(projectRoot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a skill directory belongs to Impeccable. Prefers the
|
||||
* authoritative lock signal (source === "pbakaus/impeccable") when a
|
||||
* skillName and lock are supplied, and falls back to a SKILL.md
|
||||
* content check for older skills that predate the self-identification
|
||||
* convention.
|
||||
* Check whether a skill directory belongs to Impeccable. Three layered
|
||||
* signals, in order of reliability:
|
||||
* 1. Lock source equals "pbakaus/impeccable" (authoritative).
|
||||
* 2. SKILL.md body contains the word "impeccable".
|
||||
* 3. SKILL.md body contains a per-skill fingerprint (for harden and
|
||||
* optimize, whose v2.x SKILL.md never mentioned the pack name).
|
||||
*/
|
||||
export function isImpeccableSkill(skillDir, { skillName, lock } = {}) {
|
||||
// Authoritative: the lock file claims this skill is ours.
|
||||
// 1. Authoritative: the lock file claims this skill is ours.
|
||||
if (skillName && lock?.skills?.[skillName]?.source === 'pbakaus/impeccable') {
|
||||
return true;
|
||||
}
|
||||
// Fallback: content heuristic for skills without a lock entry.
|
||||
const skillMd = join(skillDir, 'SKILL.md');
|
||||
if (!existsSync(skillMd)) return false;
|
||||
let content;
|
||||
try {
|
||||
const content = readFileSync(skillMd, 'utf-8');
|
||||
return /impeccable/i.test(content);
|
||||
content = readFileSync(skillMd, 'utf-8');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
// 2. Word-level content heuristic.
|
||||
if (/impeccable/i.test(content)) return true;
|
||||
// 3. Per-skill fingerprint for old skills that never mentioned the pack.
|
||||
const fingerprint = skillName && SKILL_FINGERPRINTS[skillName];
|
||||
if (fingerprint && content.includes(fingerprint)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,6 +57,17 @@ const HARNESS_DIRS = [
|
||||
'.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev',
|
||||
];
|
||||
|
||||
// Per-skill fingerprints for SKILL.md bodies that never mentioned
|
||||
// "impeccable" in their v2.x source. Used as a last-resort match
|
||||
// when no skills-lock.json exists and the word heuristic fails.
|
||||
// The strings are lifted verbatim from the v2.x frontmatter
|
||||
// descriptions, so collisions with hand-written user skills are
|
||||
// vanishingly unlikely.
|
||||
const SKILL_FINGERPRINTS = {
|
||||
harden: 'Make interfaces production-ready: error handling, empty states',
|
||||
optimize: 'Diagnoses and fixes UI performance across loading speed',
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk up from startDir until we find a directory that looks like a
|
||||
* project root (has package.json, .git, or skills-lock.json).
|
||||
@@ -93,26 +104,32 @@ export function loadLock(projectRoot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a skill directory belongs to Impeccable. Prefers the
|
||||
* authoritative lock signal (source === "pbakaus/impeccable") when a
|
||||
* skillName and lock are supplied, and falls back to a SKILL.md
|
||||
* content check for older skills that predate the self-identification
|
||||
* convention.
|
||||
* Check whether a skill directory belongs to Impeccable. Three layered
|
||||
* signals, in order of reliability:
|
||||
* 1. Lock source equals "pbakaus/impeccable" (authoritative).
|
||||
* 2. SKILL.md body contains the word "impeccable".
|
||||
* 3. SKILL.md body contains a per-skill fingerprint (for harden and
|
||||
* optimize, whose v2.x SKILL.md never mentioned the pack name).
|
||||
*/
|
||||
export function isImpeccableSkill(skillDir, { skillName, lock } = {}) {
|
||||
// Authoritative: the lock file claims this skill is ours.
|
||||
// 1. Authoritative: the lock file claims this skill is ours.
|
||||
if (skillName && lock?.skills?.[skillName]?.source === 'pbakaus/impeccable') {
|
||||
return true;
|
||||
}
|
||||
// Fallback: content heuristic for skills without a lock entry.
|
||||
const skillMd = join(skillDir, 'SKILL.md');
|
||||
if (!existsSync(skillMd)) return false;
|
||||
let content;
|
||||
try {
|
||||
const content = readFileSync(skillMd, 'utf-8');
|
||||
return /impeccable/i.test(content);
|
||||
content = readFileSync(skillMd, 'utf-8');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
// 2. Word-level content heuristic.
|
||||
if (/impeccable/i.test(content)) return true;
|
||||
// 3. Per-skill fingerprint for old skills that never mentioned the pack.
|
||||
const fingerprint = skillName && SKILL_FINGERPRINTS[skillName];
|
||||
if (fingerprint && content.includes(fingerprint)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,6 +57,17 @@ const HARNESS_DIRS = [
|
||||
'.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev',
|
||||
];
|
||||
|
||||
// Per-skill fingerprints for SKILL.md bodies that never mentioned
|
||||
// "impeccable" in their v2.x source. Used as a last-resort match
|
||||
// when no skills-lock.json exists and the word heuristic fails.
|
||||
// The strings are lifted verbatim from the v2.x frontmatter
|
||||
// descriptions, so collisions with hand-written user skills are
|
||||
// vanishingly unlikely.
|
||||
const SKILL_FINGERPRINTS = {
|
||||
harden: 'Make interfaces production-ready: error handling, empty states',
|
||||
optimize: 'Diagnoses and fixes UI performance across loading speed',
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk up from startDir until we find a directory that looks like a
|
||||
* project root (has package.json, .git, or skills-lock.json).
|
||||
@@ -93,26 +104,32 @@ export function loadLock(projectRoot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a skill directory belongs to Impeccable. Prefers the
|
||||
* authoritative lock signal (source === "pbakaus/impeccable") when a
|
||||
* skillName and lock are supplied, and falls back to a SKILL.md
|
||||
* content check for older skills that predate the self-identification
|
||||
* convention.
|
||||
* Check whether a skill directory belongs to Impeccable. Three layered
|
||||
* signals, in order of reliability:
|
||||
* 1. Lock source equals "pbakaus/impeccable" (authoritative).
|
||||
* 2. SKILL.md body contains the word "impeccable".
|
||||
* 3. SKILL.md body contains a per-skill fingerprint (for harden and
|
||||
* optimize, whose v2.x SKILL.md never mentioned the pack name).
|
||||
*/
|
||||
export function isImpeccableSkill(skillDir, { skillName, lock } = {}) {
|
||||
// Authoritative: the lock file claims this skill is ours.
|
||||
// 1. Authoritative: the lock file claims this skill is ours.
|
||||
if (skillName && lock?.skills?.[skillName]?.source === 'pbakaus/impeccable') {
|
||||
return true;
|
||||
}
|
||||
// Fallback: content heuristic for skills without a lock entry.
|
||||
const skillMd = join(skillDir, 'SKILL.md');
|
||||
if (!existsSync(skillMd)) return false;
|
||||
let content;
|
||||
try {
|
||||
const content = readFileSync(skillMd, 'utf-8');
|
||||
return /impeccable/i.test(content);
|
||||
content = readFileSync(skillMd, 'utf-8');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
// 2. Word-level content heuristic.
|
||||
if (/impeccable/i.test(content)) return true;
|
||||
// 3. Per-skill fingerprint for old skills that never mentioned the pack.
|
||||
const fingerprint = skillName && SKILL_FINGERPRINTS[skillName];
|
||||
if (fingerprint && content.includes(fingerprint)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,6 +57,17 @@ const HARNESS_DIRS = [
|
||||
'.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev',
|
||||
];
|
||||
|
||||
// Per-skill fingerprints for SKILL.md bodies that never mentioned
|
||||
// "impeccable" in their v2.x source. Used as a last-resort match
|
||||
// when no skills-lock.json exists and the word heuristic fails.
|
||||
// The strings are lifted verbatim from the v2.x frontmatter
|
||||
// descriptions, so collisions with hand-written user skills are
|
||||
// vanishingly unlikely.
|
||||
const SKILL_FINGERPRINTS = {
|
||||
harden: 'Make interfaces production-ready: error handling, empty states',
|
||||
optimize: 'Diagnoses and fixes UI performance across loading speed',
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk up from startDir until we find a directory that looks like a
|
||||
* project root (has package.json, .git, or skills-lock.json).
|
||||
@@ -93,26 +104,32 @@ export function loadLock(projectRoot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a skill directory belongs to Impeccable. Prefers the
|
||||
* authoritative lock signal (source === "pbakaus/impeccable") when a
|
||||
* skillName and lock are supplied, and falls back to a SKILL.md
|
||||
* content check for older skills that predate the self-identification
|
||||
* convention.
|
||||
* Check whether a skill directory belongs to Impeccable. Three layered
|
||||
* signals, in order of reliability:
|
||||
* 1. Lock source equals "pbakaus/impeccable" (authoritative).
|
||||
* 2. SKILL.md body contains the word "impeccable".
|
||||
* 3. SKILL.md body contains a per-skill fingerprint (for harden and
|
||||
* optimize, whose v2.x SKILL.md never mentioned the pack name).
|
||||
*/
|
||||
export function isImpeccableSkill(skillDir, { skillName, lock } = {}) {
|
||||
// Authoritative: the lock file claims this skill is ours.
|
||||
// 1. Authoritative: the lock file claims this skill is ours.
|
||||
if (skillName && lock?.skills?.[skillName]?.source === 'pbakaus/impeccable') {
|
||||
return true;
|
||||
}
|
||||
// Fallback: content heuristic for skills without a lock entry.
|
||||
const skillMd = join(skillDir, 'SKILL.md');
|
||||
if (!existsSync(skillMd)) return false;
|
||||
let content;
|
||||
try {
|
||||
const content = readFileSync(skillMd, 'utf-8');
|
||||
return /impeccable/i.test(content);
|
||||
content = readFileSync(skillMd, 'utf-8');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
// 2. Word-level content heuristic.
|
||||
if (/impeccable/i.test(content)) return true;
|
||||
// 3. Per-skill fingerprint for old skills that never mentioned the pack.
|
||||
const fingerprint = skillName && SKILL_FINGERPRINTS[skillName];
|
||||
if (fingerprint && content.includes(fingerprint)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,6 +57,17 @@ const HARNESS_DIRS = [
|
||||
'.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev',
|
||||
];
|
||||
|
||||
// Per-skill fingerprints for SKILL.md bodies that never mentioned
|
||||
// "impeccable" in their v2.x source. Used as a last-resort match
|
||||
// when no skills-lock.json exists and the word heuristic fails.
|
||||
// The strings are lifted verbatim from the v2.x frontmatter
|
||||
// descriptions, so collisions with hand-written user skills are
|
||||
// vanishingly unlikely.
|
||||
const SKILL_FINGERPRINTS = {
|
||||
harden: 'Make interfaces production-ready: error handling, empty states',
|
||||
optimize: 'Diagnoses and fixes UI performance across loading speed',
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk up from startDir until we find a directory that looks like a
|
||||
* project root (has package.json, .git, or skills-lock.json).
|
||||
@@ -93,26 +104,32 @@ export function loadLock(projectRoot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a skill directory belongs to Impeccable. Prefers the
|
||||
* authoritative lock signal (source === "pbakaus/impeccable") when a
|
||||
* skillName and lock are supplied, and falls back to a SKILL.md
|
||||
* content check for older skills that predate the self-identification
|
||||
* convention.
|
||||
* Check whether a skill directory belongs to Impeccable. Three layered
|
||||
* signals, in order of reliability:
|
||||
* 1. Lock source equals "pbakaus/impeccable" (authoritative).
|
||||
* 2. SKILL.md body contains the word "impeccable".
|
||||
* 3. SKILL.md body contains a per-skill fingerprint (for harden and
|
||||
* optimize, whose v2.x SKILL.md never mentioned the pack name).
|
||||
*/
|
||||
export function isImpeccableSkill(skillDir, { skillName, lock } = {}) {
|
||||
// Authoritative: the lock file claims this skill is ours.
|
||||
// 1. Authoritative: the lock file claims this skill is ours.
|
||||
if (skillName && lock?.skills?.[skillName]?.source === 'pbakaus/impeccable') {
|
||||
return true;
|
||||
}
|
||||
// Fallback: content heuristic for skills without a lock entry.
|
||||
const skillMd = join(skillDir, 'SKILL.md');
|
||||
if (!existsSync(skillMd)) return false;
|
||||
let content;
|
||||
try {
|
||||
const content = readFileSync(skillMd, 'utf-8');
|
||||
return /impeccable/i.test(content);
|
||||
content = readFileSync(skillMd, 'utf-8');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
// 2. Word-level content heuristic.
|
||||
if (/impeccable/i.test(content)) return true;
|
||||
// 3. Per-skill fingerprint for old skills that never mentioned the pack.
|
||||
const fingerprint = skillName && SKILL_FINGERPRINTS[skillName];
|
||||
if (fingerprint && content.includes(fingerprint)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,6 +57,17 @@ const HARNESS_DIRS = [
|
||||
'.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev',
|
||||
];
|
||||
|
||||
// Per-skill fingerprints for SKILL.md bodies that never mentioned
|
||||
// "impeccable" in their v2.x source. Used as a last-resort match
|
||||
// when no skills-lock.json exists and the word heuristic fails.
|
||||
// The strings are lifted verbatim from the v2.x frontmatter
|
||||
// descriptions, so collisions with hand-written user skills are
|
||||
// vanishingly unlikely.
|
||||
const SKILL_FINGERPRINTS = {
|
||||
harden: 'Make interfaces production-ready: error handling, empty states',
|
||||
optimize: 'Diagnoses and fixes UI performance across loading speed',
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk up from startDir until we find a directory that looks like a
|
||||
* project root (has package.json, .git, or skills-lock.json).
|
||||
@@ -93,26 +104,32 @@ export function loadLock(projectRoot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a skill directory belongs to Impeccable. Prefers the
|
||||
* authoritative lock signal (source === "pbakaus/impeccable") when a
|
||||
* skillName and lock are supplied, and falls back to a SKILL.md
|
||||
* content check for older skills that predate the self-identification
|
||||
* convention.
|
||||
* Check whether a skill directory belongs to Impeccable. Three layered
|
||||
* signals, in order of reliability:
|
||||
* 1. Lock source equals "pbakaus/impeccable" (authoritative).
|
||||
* 2. SKILL.md body contains the word "impeccable".
|
||||
* 3. SKILL.md body contains a per-skill fingerprint (for harden and
|
||||
* optimize, whose v2.x SKILL.md never mentioned the pack name).
|
||||
*/
|
||||
export function isImpeccableSkill(skillDir, { skillName, lock } = {}) {
|
||||
// Authoritative: the lock file claims this skill is ours.
|
||||
// 1. Authoritative: the lock file claims this skill is ours.
|
||||
if (skillName && lock?.skills?.[skillName]?.source === 'pbakaus/impeccable') {
|
||||
return true;
|
||||
}
|
||||
// Fallback: content heuristic for skills without a lock entry.
|
||||
const skillMd = join(skillDir, 'SKILL.md');
|
||||
if (!existsSync(skillMd)) return false;
|
||||
let content;
|
||||
try {
|
||||
const content = readFileSync(skillMd, 'utf-8');
|
||||
return /impeccable/i.test(content);
|
||||
content = readFileSync(skillMd, 'utf-8');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
// 2. Word-level content heuristic.
|
||||
if (/impeccable/i.test(content)) return true;
|
||||
// 3. Per-skill fingerprint for old skills that never mentioned the pack.
|
||||
const fingerprint = skillName && SKILL_FINGERPRINTS[skillName];
|
||||
if (fingerprint && content.includes(fingerprint)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,6 +57,17 @@ const HARNESS_DIRS = [
|
||||
'.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev',
|
||||
];
|
||||
|
||||
// Per-skill fingerprints for SKILL.md bodies that never mentioned
|
||||
// "impeccable" in their v2.x source. Used as a last-resort match
|
||||
// when no skills-lock.json exists and the word heuristic fails.
|
||||
// The strings are lifted verbatim from the v2.x frontmatter
|
||||
// descriptions, so collisions with hand-written user skills are
|
||||
// vanishingly unlikely.
|
||||
const SKILL_FINGERPRINTS = {
|
||||
harden: 'Make interfaces production-ready: error handling, empty states',
|
||||
optimize: 'Diagnoses and fixes UI performance across loading speed',
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk up from startDir until we find a directory that looks like a
|
||||
* project root (has package.json, .git, or skills-lock.json).
|
||||
@@ -93,26 +104,32 @@ export function loadLock(projectRoot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a skill directory belongs to Impeccable. Prefers the
|
||||
* authoritative lock signal (source === "pbakaus/impeccable") when a
|
||||
* skillName and lock are supplied, and falls back to a SKILL.md
|
||||
* content check for older skills that predate the self-identification
|
||||
* convention.
|
||||
* Check whether a skill directory belongs to Impeccable. Three layered
|
||||
* signals, in order of reliability:
|
||||
* 1. Lock source equals "pbakaus/impeccable" (authoritative).
|
||||
* 2. SKILL.md body contains the word "impeccable".
|
||||
* 3. SKILL.md body contains a per-skill fingerprint (for harden and
|
||||
* optimize, whose v2.x SKILL.md never mentioned the pack name).
|
||||
*/
|
||||
export function isImpeccableSkill(skillDir, { skillName, lock } = {}) {
|
||||
// Authoritative: the lock file claims this skill is ours.
|
||||
// 1. Authoritative: the lock file claims this skill is ours.
|
||||
if (skillName && lock?.skills?.[skillName]?.source === 'pbakaus/impeccable') {
|
||||
return true;
|
||||
}
|
||||
// Fallback: content heuristic for skills without a lock entry.
|
||||
const skillMd = join(skillDir, 'SKILL.md');
|
||||
if (!existsSync(skillMd)) return false;
|
||||
let content;
|
||||
try {
|
||||
const content = readFileSync(skillMd, 'utf-8');
|
||||
return /impeccable/i.test(content);
|
||||
content = readFileSync(skillMd, 'utf-8');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
// 2. Word-level content heuristic.
|
||||
if (/impeccable/i.test(content)) return true;
|
||||
// 3. Per-skill fingerprint for old skills that never mentioned the pack.
|
||||
const fingerprint = skillName && SKILL_FINGERPRINTS[skillName];
|
||||
if (fingerprint && content.includes(fingerprint)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,6 +57,17 @@ const HARNESS_DIRS = [
|
||||
'.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev',
|
||||
];
|
||||
|
||||
// Per-skill fingerprints for SKILL.md bodies that never mentioned
|
||||
// "impeccable" in their v2.x source. Used as a last-resort match
|
||||
// when no skills-lock.json exists and the word heuristic fails.
|
||||
// The strings are lifted verbatim from the v2.x frontmatter
|
||||
// descriptions, so collisions with hand-written user skills are
|
||||
// vanishingly unlikely.
|
||||
const SKILL_FINGERPRINTS = {
|
||||
harden: 'Make interfaces production-ready: error handling, empty states',
|
||||
optimize: 'Diagnoses and fixes UI performance across loading speed',
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk up from startDir until we find a directory that looks like a
|
||||
* project root (has package.json, .git, or skills-lock.json).
|
||||
@@ -93,26 +104,32 @@ export function loadLock(projectRoot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a skill directory belongs to Impeccable. Prefers the
|
||||
* authoritative lock signal (source === "pbakaus/impeccable") when a
|
||||
* skillName and lock are supplied, and falls back to a SKILL.md
|
||||
* content check for older skills that predate the self-identification
|
||||
* convention.
|
||||
* Check whether a skill directory belongs to Impeccable. Three layered
|
||||
* signals, in order of reliability:
|
||||
* 1. Lock source equals "pbakaus/impeccable" (authoritative).
|
||||
* 2. SKILL.md body contains the word "impeccable".
|
||||
* 3. SKILL.md body contains a per-skill fingerprint (for harden and
|
||||
* optimize, whose v2.x SKILL.md never mentioned the pack name).
|
||||
*/
|
||||
export function isImpeccableSkill(skillDir, { skillName, lock } = {}) {
|
||||
// Authoritative: the lock file claims this skill is ours.
|
||||
// 1. Authoritative: the lock file claims this skill is ours.
|
||||
if (skillName && lock?.skills?.[skillName]?.source === 'pbakaus/impeccable') {
|
||||
return true;
|
||||
}
|
||||
// Fallback: content heuristic for skills without a lock entry.
|
||||
const skillMd = join(skillDir, 'SKILL.md');
|
||||
if (!existsSync(skillMd)) return false;
|
||||
let content;
|
||||
try {
|
||||
const content = readFileSync(skillMd, 'utf-8');
|
||||
return /impeccable/i.test(content);
|
||||
content = readFileSync(skillMd, 'utf-8');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
// 2. Word-level content heuristic.
|
||||
if (/impeccable/i.test(content)) return true;
|
||||
// 3. Per-skill fingerprint for old skills that never mentioned the pack.
|
||||
const fingerprint = skillName && SKILL_FINGERPRINTS[skillName];
|
||||
if (fingerprint && content.includes(fingerprint)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,6 +57,17 @@ const HARNESS_DIRS = [
|
||||
'.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev',
|
||||
];
|
||||
|
||||
// Per-skill fingerprints for SKILL.md bodies that never mentioned
|
||||
// "impeccable" in their v2.x source. Used as a last-resort match
|
||||
// when no skills-lock.json exists and the word heuristic fails.
|
||||
// The strings are lifted verbatim from the v2.x frontmatter
|
||||
// descriptions, so collisions with hand-written user skills are
|
||||
// vanishingly unlikely.
|
||||
const SKILL_FINGERPRINTS = {
|
||||
harden: 'Make interfaces production-ready: error handling, empty states',
|
||||
optimize: 'Diagnoses and fixes UI performance across loading speed',
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk up from startDir until we find a directory that looks like a
|
||||
* project root (has package.json, .git, or skills-lock.json).
|
||||
@@ -93,26 +104,32 @@ export function loadLock(projectRoot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a skill directory belongs to Impeccable. Prefers the
|
||||
* authoritative lock signal (source === "pbakaus/impeccable") when a
|
||||
* skillName and lock are supplied, and falls back to a SKILL.md
|
||||
* content check for older skills that predate the self-identification
|
||||
* convention.
|
||||
* Check whether a skill directory belongs to Impeccable. Three layered
|
||||
* signals, in order of reliability:
|
||||
* 1. Lock source equals "pbakaus/impeccable" (authoritative).
|
||||
* 2. SKILL.md body contains the word "impeccable".
|
||||
* 3. SKILL.md body contains a per-skill fingerprint (for harden and
|
||||
* optimize, whose v2.x SKILL.md never mentioned the pack name).
|
||||
*/
|
||||
export function isImpeccableSkill(skillDir, { skillName, lock } = {}) {
|
||||
// Authoritative: the lock file claims this skill is ours.
|
||||
// 1. Authoritative: the lock file claims this skill is ours.
|
||||
if (skillName && lock?.skills?.[skillName]?.source === 'pbakaus/impeccable') {
|
||||
return true;
|
||||
}
|
||||
// Fallback: content heuristic for skills without a lock entry.
|
||||
const skillMd = join(skillDir, 'SKILL.md');
|
||||
if (!existsSync(skillMd)) return false;
|
||||
let content;
|
||||
try {
|
||||
const content = readFileSync(skillMd, 'utf-8');
|
||||
return /impeccable/i.test(content);
|
||||
content = readFileSync(skillMd, 'utf-8');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
// 2. Word-level content heuristic.
|
||||
if (/impeccable/i.test(content)) return true;
|
||||
// 3. Per-skill fingerprint for old skills that never mentioned the pack.
|
||||
const fingerprint = skillName && SKILL_FINGERPRINTS[skillName];
|
||||
if (fingerprint && content.includes(fingerprint)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,6 +57,17 @@ const HARNESS_DIRS = [
|
||||
'.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev',
|
||||
];
|
||||
|
||||
// Per-skill fingerprints for SKILL.md bodies that never mentioned
|
||||
// "impeccable" in their v2.x source. Used as a last-resort match
|
||||
// when no skills-lock.json exists and the word heuristic fails.
|
||||
// The strings are lifted verbatim from the v2.x frontmatter
|
||||
// descriptions, so collisions with hand-written user skills are
|
||||
// vanishingly unlikely.
|
||||
const SKILL_FINGERPRINTS = {
|
||||
harden: 'Make interfaces production-ready: error handling, empty states',
|
||||
optimize: 'Diagnoses and fixes UI performance across loading speed',
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk up from startDir until we find a directory that looks like a
|
||||
* project root (has package.json, .git, or skills-lock.json).
|
||||
@@ -93,26 +104,32 @@ export function loadLock(projectRoot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a skill directory belongs to Impeccable. Prefers the
|
||||
* authoritative lock signal (source === "pbakaus/impeccable") when a
|
||||
* skillName and lock are supplied, and falls back to a SKILL.md
|
||||
* content check for older skills that predate the self-identification
|
||||
* convention.
|
||||
* Check whether a skill directory belongs to Impeccable. Three layered
|
||||
* signals, in order of reliability:
|
||||
* 1. Lock source equals "pbakaus/impeccable" (authoritative).
|
||||
* 2. SKILL.md body contains the word "impeccable".
|
||||
* 3. SKILL.md body contains a per-skill fingerprint (for harden and
|
||||
* optimize, whose v2.x SKILL.md never mentioned the pack name).
|
||||
*/
|
||||
export function isImpeccableSkill(skillDir, { skillName, lock } = {}) {
|
||||
// Authoritative: the lock file claims this skill is ours.
|
||||
// 1. Authoritative: the lock file claims this skill is ours.
|
||||
if (skillName && lock?.skills?.[skillName]?.source === 'pbakaus/impeccable') {
|
||||
return true;
|
||||
}
|
||||
// Fallback: content heuristic for skills without a lock entry.
|
||||
const skillMd = join(skillDir, 'SKILL.md');
|
||||
if (!existsSync(skillMd)) return false;
|
||||
let content;
|
||||
try {
|
||||
const content = readFileSync(skillMd, 'utf-8');
|
||||
return /impeccable/i.test(content);
|
||||
content = readFileSync(skillMd, 'utf-8');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
// 2. Word-level content heuristic.
|
||||
if (/impeccable/i.test(content)) return true;
|
||||
// 3. Per-skill fingerprint for old skills that never mentioned the pack.
|
||||
const fingerprint = skillName && SKILL_FINGERPRINTS[skillName];
|
||||
if (fingerprint && content.includes(fingerprint)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,6 +57,17 @@ const HARNESS_DIRS = [
|
||||
'.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev',
|
||||
];
|
||||
|
||||
// Per-skill fingerprints for SKILL.md bodies that never mentioned
|
||||
// "impeccable" in their v2.x source. Used as a last-resort match
|
||||
// when no skills-lock.json exists and the word heuristic fails.
|
||||
// The strings are lifted verbatim from the v2.x frontmatter
|
||||
// descriptions, so collisions with hand-written user skills are
|
||||
// vanishingly unlikely.
|
||||
const SKILL_FINGERPRINTS = {
|
||||
harden: 'Make interfaces production-ready: error handling, empty states',
|
||||
optimize: 'Diagnoses and fixes UI performance across loading speed',
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk up from startDir until we find a directory that looks like a
|
||||
* project root (has package.json, .git, or skills-lock.json).
|
||||
@@ -93,26 +104,32 @@ export function loadLock(projectRoot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a skill directory belongs to Impeccable. Prefers the
|
||||
* authoritative lock signal (source === "pbakaus/impeccable") when a
|
||||
* skillName and lock are supplied, and falls back to a SKILL.md
|
||||
* content check for older skills that predate the self-identification
|
||||
* convention.
|
||||
* Check whether a skill directory belongs to Impeccable. Three layered
|
||||
* signals, in order of reliability:
|
||||
* 1. Lock source equals "pbakaus/impeccable" (authoritative).
|
||||
* 2. SKILL.md body contains the word "impeccable".
|
||||
* 3. SKILL.md body contains a per-skill fingerprint (for harden and
|
||||
* optimize, whose v2.x SKILL.md never mentioned the pack name).
|
||||
*/
|
||||
export function isImpeccableSkill(skillDir, { skillName, lock } = {}) {
|
||||
// Authoritative: the lock file claims this skill is ours.
|
||||
// 1. Authoritative: the lock file claims this skill is ours.
|
||||
if (skillName && lock?.skills?.[skillName]?.source === 'pbakaus/impeccable') {
|
||||
return true;
|
||||
}
|
||||
// Fallback: content heuristic for skills without a lock entry.
|
||||
const skillMd = join(skillDir, 'SKILL.md');
|
||||
if (!existsSync(skillMd)) return false;
|
||||
let content;
|
||||
try {
|
||||
const content = readFileSync(skillMd, 'utf-8');
|
||||
return /impeccable/i.test(content);
|
||||
content = readFileSync(skillMd, 'utf-8');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
// 2. Word-level content heuristic.
|
||||
if (/impeccable/i.test(content)) return true;
|
||||
// 3. Per-skill fingerprint for old skills that never mentioned the pack.
|
||||
const fingerprint = skillName && SKILL_FINGERPRINTS[skillName];
|
||||
if (fingerprint && content.includes(fingerprint)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -57,6 +57,17 @@ const HARNESS_DIRS = [
|
||||
'.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev',
|
||||
];
|
||||
|
||||
// Per-skill fingerprints for SKILL.md bodies that never mentioned
|
||||
// "impeccable" in their v2.x source. Used as a last-resort match
|
||||
// when no skills-lock.json exists and the word heuristic fails.
|
||||
// The strings are lifted verbatim from the v2.x frontmatter
|
||||
// descriptions, so collisions with hand-written user skills are
|
||||
// vanishingly unlikely.
|
||||
const SKILL_FINGERPRINTS = {
|
||||
harden: 'Make interfaces production-ready: error handling, empty states',
|
||||
optimize: 'Diagnoses and fixes UI performance across loading speed',
|
||||
};
|
||||
|
||||
/**
|
||||
* Walk up from startDir until we find a directory that looks like a
|
||||
* project root (has package.json, .git, or skills-lock.json).
|
||||
@@ -93,26 +104,32 @@ export function loadLock(projectRoot) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a skill directory belongs to Impeccable. Prefers the
|
||||
* authoritative lock signal (source === "pbakaus/impeccable") when a
|
||||
* skillName and lock are supplied, and falls back to a SKILL.md
|
||||
* content check for older skills that predate the self-identification
|
||||
* convention.
|
||||
* Check whether a skill directory belongs to Impeccable. Three layered
|
||||
* signals, in order of reliability:
|
||||
* 1. Lock source equals "pbakaus/impeccable" (authoritative).
|
||||
* 2. SKILL.md body contains the word "impeccable".
|
||||
* 3. SKILL.md body contains a per-skill fingerprint (for harden and
|
||||
* optimize, whose v2.x SKILL.md never mentioned the pack name).
|
||||
*/
|
||||
export function isImpeccableSkill(skillDir, { skillName, lock } = {}) {
|
||||
// Authoritative: the lock file claims this skill is ours.
|
||||
// 1. Authoritative: the lock file claims this skill is ours.
|
||||
if (skillName && lock?.skills?.[skillName]?.source === 'pbakaus/impeccable') {
|
||||
return true;
|
||||
}
|
||||
// Fallback: content heuristic for skills without a lock entry.
|
||||
const skillMd = join(skillDir, 'SKILL.md');
|
||||
if (!existsSync(skillMd)) return false;
|
||||
let content;
|
||||
try {
|
||||
const content = readFileSync(skillMd, 'utf-8');
|
||||
return /impeccable/i.test(content);
|
||||
content = readFileSync(skillMd, 'utf-8');
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
// 2. Word-level content heuristic.
|
||||
if (/impeccable/i.test(content)) return true;
|
||||
// 3. Per-skill fingerprint for old skills that never mentioned the pack.
|
||||
const fingerprint = skillName && SKILL_FINGERPRINTS[skillName];
|
||||
if (fingerprint && content.includes(fingerprint)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,6 +69,45 @@ describe('cleanup-deprecated', () => {
|
||||
it('returns false for non-existent directory', () => {
|
||||
assert.equal(isImpeccableSkill(join(tmp, 'nope')), false);
|
||||
});
|
||||
|
||||
it('returns true when lock source says pbakaus/impeccable, even if SKILL.md never mentions it', () => {
|
||||
const dir = writeSkill(tmp, '.claude', 'harden', 'A custom skill with no pack mention.');
|
||||
const lock = {
|
||||
skills: { harden: { source: 'pbakaus/impeccable' } },
|
||||
};
|
||||
assert.equal(isImpeccableSkill(dir, { skillName: 'harden', lock }), true);
|
||||
});
|
||||
|
||||
it('returns false when lock source is a different pack', () => {
|
||||
const dir = writeSkill(tmp, '.claude', 'harden', 'A custom skill with no pack mention.');
|
||||
const lock = {
|
||||
skills: { harden: { source: 'someone-else/pack' } },
|
||||
};
|
||||
assert.equal(isImpeccableSkill(dir, { skillName: 'harden', lock }), false);
|
||||
});
|
||||
|
||||
it('falls back to SKILL.md content when no lock entry exists', () => {
|
||||
const dir = writeSkill(tmp, '.claude', 'harden', 'Invoke /impeccable to harden.');
|
||||
const lock = { skills: {} };
|
||||
assert.equal(isImpeccableSkill(dir, { skillName: 'harden', lock }), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadLock', () => {
|
||||
it('returns null when skills-lock.json is missing', () => {
|
||||
assert.equal(loadLock(tmp), null);
|
||||
});
|
||||
|
||||
it('parses skills-lock.json when present', () => {
|
||||
const lock = { version: 1, skills: { arrange: { source: 'pbakaus/impeccable' } } };
|
||||
writeFileSync(join(tmp, 'skills-lock.json'), JSON.stringify(lock), 'utf-8');
|
||||
assert.deepEqual(loadLock(tmp), lock);
|
||||
});
|
||||
|
||||
it('returns null on malformed JSON', () => {
|
||||
writeFileSync(join(tmp, 'skills-lock.json'), '{not json', 'utf-8');
|
||||
assert.equal(loadLock(tmp), null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildTargetNames', () => {
|
||||
@@ -136,6 +175,74 @@ describe('cleanup-deprecated', () => {
|
||||
assert.equal(existsSync(join(tmp, '.claude', 'skills', 'my-custom-skill')), true);
|
||||
});
|
||||
|
||||
it('deletes a stock v2.x harden skill with no lock file via the fingerprint fallback', () => {
|
||||
// Reproduces the no-lock-file install path: user installed via
|
||||
// submodule or manual copy, so skills-lock.json never existed. The
|
||||
// v2.x harden SKILL.md never contained the word "impeccable", but
|
||||
// does contain the distinctive description fingerprint.
|
||||
const body = [
|
||||
'---',
|
||||
'name: harden',
|
||||
'description: "Make interfaces production-ready: error handling, empty states, onboarding flows, i18n, text overflow, and edge case management."',
|
||||
'---',
|
||||
'',
|
||||
'Strengthen interfaces against edge cases.',
|
||||
].join('\n');
|
||||
writeSkill(tmp, '.claude', 'harden', body);
|
||||
const deleted = removeDeprecatedSkills(tmp);
|
||||
assert.equal(deleted.length, 1);
|
||||
assert.equal(existsSync(join(tmp, '.claude', 'skills', 'harden')), false);
|
||||
});
|
||||
|
||||
it('deletes a stock v2.x optimize skill with no lock file via the fingerprint fallback', () => {
|
||||
const body = [
|
||||
'---',
|
||||
'name: optimize',
|
||||
'description: "Diagnoses and fixes UI performance across loading speed, rendering, animations, images, and bundle size."',
|
||||
'---',
|
||||
'',
|
||||
'Identify and fix performance issues.',
|
||||
].join('\n');
|
||||
writeSkill(tmp, '.claude', 'optimize', body);
|
||||
const deleted = removeDeprecatedSkills(tmp);
|
||||
assert.equal(deleted.length, 1);
|
||||
});
|
||||
|
||||
it('does NOT delete a user-written harden skill that lacks both the pack word and the fingerprint', () => {
|
||||
writeSkill(tmp, '.claude', 'harden', 'My custom skill for hardening cookies against CSRF.');
|
||||
const deleted = removeDeprecatedSkills(tmp);
|
||||
assert.equal(deleted.length, 0);
|
||||
assert.equal(existsSync(join(tmp, '.claude', 'skills', 'harden')), true);
|
||||
});
|
||||
|
||||
it('deletes skills whose SKILL.md never mentions impeccable when the lock claims them', () => {
|
||||
// Reproduces the "orphan dir" bug: the old SKILL.md bodies described
|
||||
// each skill on its own merits and never said the word "impeccable",
|
||||
// so the content heuristic returned false. The lock source is the
|
||||
// authoritative signal.
|
||||
writeSkill(tmp, '.claude', 'harden', '# Harden\n\nA custom skill with zero pack-name mentions.');
|
||||
const lock = {
|
||||
version: 1,
|
||||
skills: { harden: { source: 'pbakaus/impeccable', sourceType: 'github', computedHash: 'x' } },
|
||||
};
|
||||
writeFileSync(join(tmp, 'skills-lock.json'), JSON.stringify(lock), 'utf-8');
|
||||
const deleted = removeDeprecatedSkills(tmp);
|
||||
assert.equal(deleted.length, 1);
|
||||
assert.equal(existsSync(join(tmp, '.claude', 'skills', 'harden')), false);
|
||||
});
|
||||
|
||||
it('does NOT delete a same-named skill owned by a different pack', () => {
|
||||
writeSkill(tmp, '.claude', 'extract', 'Some user-written extract skill.');
|
||||
const lock = {
|
||||
version: 1,
|
||||
skills: { extract: { source: 'someone-else/pack' } },
|
||||
};
|
||||
writeFileSync(join(tmp, 'skills-lock.json'), JSON.stringify(lock), 'utf-8');
|
||||
const deleted = removeDeprecatedSkills(tmp);
|
||||
assert.equal(deleted.length, 0);
|
||||
assert.equal(existsSync(join(tmp, '.claude', 'skills', 'extract')), true);
|
||||
});
|
||||
|
||||
it('handles symlinks to deprecated skills', () => {
|
||||
// Create the canonical skill in .agents
|
||||
const canonical = writeSkill(tmp, '.agents', 'extract', 'Use impeccable extract.');
|
||||
|
||||
Reference in New Issue
Block a user