mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Fix: normalize provider paths before comparing skill hashes
Skills installed via npx skills add resolve {{scripts_path}} to
.agents/skills/... while our bundle resolves it per-provider
(.claude/skills/..., .cursor/skills/..., etc). Without normalizing,
identical content always shows as different.
Also compare only one provider instead of all (they have the same
content, just different path prefixes).
CLI bumped to v2.1.4.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
5071d85a4b
commit
bd775e414e
+26
-15
@@ -112,30 +112,41 @@ async function downloadAndExtractBundle() {
|
||||
return tmpDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a SKILL.md's content for comparison by stripping
|
||||
* provider-specific paths. Different install methods (npx skills add
|
||||
* vs our bundle) resolve {{scripts_path}} to different provider dirs
|
||||
* (e.g. .agents vs .claude), so we strip those differences.
|
||||
*/
|
||||
function normalizeForHash(content) {
|
||||
return content.replace(/\.(claude|cursor|agents|gemini|codex|kiro|opencode|pi|trae|trae-cn|rovodev)\/skills\//g, '.PROVIDER/skills/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare local skills against a downloaded bundle.
|
||||
* Only checks skills that exist in the bundle (ignores user's custom
|
||||
* skills that aren't part of impeccable).
|
||||
* skills that aren't part of impeccable). Uses a single provider from
|
||||
* the bundle as canonical and normalizes provider-specific paths.
|
||||
* Returns true if every bundle skill matches the local copy.
|
||||
*/
|
||||
function isUpToDate(root, providers, bundleDir) {
|
||||
for (const provider of providers) {
|
||||
const bundleSkillsDir = join(bundleDir, provider, 'skills');
|
||||
const localSkillsDir = join(root, provider, 'skills');
|
||||
if (!existsSync(bundleSkillsDir)) continue;
|
||||
// Use the first installed provider for comparison
|
||||
const provider = providers[0];
|
||||
const bundleSkillsDir = join(bundleDir, provider, 'skills');
|
||||
const localSkillsDir = join(root, provider, 'skills');
|
||||
if (!existsSync(bundleSkillsDir)) return false;
|
||||
|
||||
for (const name of readdirSync(bundleSkillsDir)) {
|
||||
const bundleMd = join(bundleSkillsDir, name, 'SKILL.md');
|
||||
const localMd = join(localSkillsDir, name, 'SKILL.md');
|
||||
if (!existsSync(bundleMd)) continue;
|
||||
for (const name of readdirSync(bundleSkillsDir)) {
|
||||
const bundleMd = join(bundleSkillsDir, name, 'SKILL.md');
|
||||
const localMd = join(localSkillsDir, name, 'SKILL.md');
|
||||
if (!existsSync(bundleMd)) continue;
|
||||
|
||||
// Missing locally = needs update
|
||||
if (!existsSync(localMd)) return false;
|
||||
// Missing locally = needs update
|
||||
if (!existsSync(localMd)) return false;
|
||||
|
||||
const bundleHash = createHash('sha256').update(readFileSync(bundleMd)).digest('hex');
|
||||
const localHash = createHash('sha256').update(readFileSync(localMd)).digest('hex');
|
||||
if (bundleHash !== localHash) return false;
|
||||
}
|
||||
const bundleHash = createHash('sha256').update(normalizeForHash(readFileSync(bundleMd, 'utf-8'))).digest('hex');
|
||||
const localHash = createHash('sha256').update(normalizeForHash(readFileSync(localMd, 'utf-8'))).digest('hex');
|
||||
if (bundleHash !== localHash) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "impeccable",
|
||||
"version": "2.1.3",
|
||||
"version": "2.1.4",
|
||||
"author": "Paul Bakaus",
|
||||
"description": "Design skills, commands, and anti-pattern detection for AI coding agents",
|
||||
"keywords": [
|
||||
|
||||
Reference in New Issue
Block a user