bookworm-smart-assistant/scripts/skill-alias-resolver.js
Bookworm Admin b7a8e29d21 release: v6.7.0 - OTA E2E test release
- VERSION file as authoritative version source
- export.mjs reads VERSION with package.json fallback
- bw-ota.ps1 DryRun mode for safe testing
- auto-setup.ps1 bumped to v3.2.0 (Phase 8 OTA)
2026-04-27 17:59:44 +08:00

83 lines
2.7 KiB
JavaScript

#!/usr/bin/env node
// Skill 别名解析模块 (v6.6 Phase 2 合并兼容层)
// fail-open: 任何异常都返回原名,绝不阻断路由
const fs = require('fs');
const path = require('path');
const os = require('os');
const ROOT = path.join(os.homedir(), '.claude');
const ALIAS_FILE = path.join(ROOT, 'skill-alias.json');
const LOG_FILE = path.join(ROOT, 'debug', 'skill-alias-resolutions.jsonl');
let _cache = null;
let _mtime = 0;
function loadAliases() {
try {
const stat = fs.statSync(ALIAS_FILE);
if (_cache && stat.mtimeMs === _mtime) return _cache;
const data = JSON.parse(fs.readFileSync(ALIAS_FILE, 'utf8'));
_cache = {
aliases: data.aliases || {},
policy: data.policy || { log_when_resolved: false, fail_open: true },
deprecatedUntil: data.deprecated_until || {},
};
_mtime = stat.mtimeMs;
return _cache;
} catch {
return { aliases: {}, policy: { fail_open: true }, deprecatedUntil: {} };
}
}
function resolve(name) {
if (!name || typeof name !== 'string') return { resolved: name, wasAlias: false };
const conf = loadAliases();
const target = conf.aliases[name];
if (!target || target === name) return { resolved: name, wasAlias: false };
const out = { resolved: target, wasAlias: true, original: name };
if (conf.deprecatedUntil[name]) out.expiresOn = conf.deprecatedUntil[name];
if (conf.policy.log_when_resolved) safeLog(out);
return out;
}
function resolveCandidates(candidates) {
if (!Array.isArray(candidates)) return candidates;
const seen = new Map();
for (const c of candidates) {
const r = resolve(c.name || c.skill || c);
const key = r.resolved;
if (!seen.has(key)) {
seen.set(key, { ...c, name: key, score: c.score, _aliasOrigin: r.wasAlias ? r.original : undefined });
} else if (c.score > seen.get(key).score) {
seen.get(key).score = c.score;
}
}
return Array.from(seen.values());
}
function safeLog(entry) {
try {
const line = JSON.stringify({ ts: new Date().toISOString(), ...entry }) + '\n';
fs.appendFileSync(LOG_FILE, line);
} catch {}
}
function listAliases() {
return Object.entries(loadAliases().aliases).map(([from, to]) => ({ from, to }));
}
module.exports = { resolve, resolveCandidates, listAliases, loadAliases };
if (require.main === module) {
const args = process.argv.slice(2);
if (args[0]) {
process.stdout.write(JSON.stringify(resolve(args[0]), null, 2) + '\n');
} else {
const all = listAliases();
process.stdout.write('Loaded ' + all.length + ' alias mappings:\n');
all.forEach(a => process.stdout.write(' ' + a.from + ' -> ' + a.to + '\n'));
process.stdout.write('\nUsage: node skill-alias-resolver.js <skill-name>\n');
}
}