bookworm-smart-assistant/scripts/patches/patch-p0-3-skill-tiering.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

107 lines
3.3 KiB
JavaScript

#!/usr/bin/env node
/**
* P0-3 Patch: Skill 三层分层
* 基于 actual-skills.jsonl 频率 + criticality 维度, 给 skills-index.json 每个 skill 追加:
* - tier: 1 (常驻 ~20) / 2 (按需 ~30) / 3 (归档 ~45)
* - criticality: "HIGH" / "MEDIUM" / "LOW"
* 幂等: 检查首个 skill 是否已有 tier 字段
*/
'use strict';
const fs = require('fs');
const path = require('path');
const CLAUDE_ROOT = path.resolve(__dirname, '..', '..');
const INDEX_PATH = path.join(CLAUDE_ROOT, 'skills-index.json');
const ACTUAL_PATH = path.join(CLAUDE_ROOT, 'debug', 'actual-skills.jsonl');
// --- Criticality 定义 ---
const CRITICALITY_HIGH = new Set([
'security-expert', 'guardian', 'zero-defect-guardian', 'review',
'debugger-expert', 'developer-expert', 'project-audit-expert',
]);
const TIER1_ESSENTIAL = new Set([
'frontend-expert', 'backend-builder', 'qa', 'prompt-optimizer',
'mobile-expert', 'architect-expert',
]);
const TIER2_USEFUL = new Set([
'cloud-architect', 'database-tuning-expert', 'notification-system-expert',
'finance-advisor', 'sre-expert', 'data-analyst-expert', 'vue-expert',
'nextjs-developer', 'python-pro', 'typescript-pro', 'rust-engineer',
'golang-pro', 'swift-expert', 'flutter-expert', 'terraform-engineer',
'kubernetes-specialist', 'graphql-architect', 'websocket-engineer',
'angular-architect', 'cloud-native-expert',
]);
const TIER1_FREQ_THRESHOLD = 3;
// --- 频率统计 ---
function countFrequency() {
const counts = {};
try {
const lines = fs.readFileSync(ACTUAL_PATH, 'utf8').trim().split('\n');
for (const line of lines) {
try {
const obj = JSON.parse(line);
const skill = obj.actualSkill;
if (skill) counts[skill] = (counts[skill] || 0) + 1;
} catch {}
}
} catch {}
return counts;
}
// --- 分层逻辑 ---
function assignTier(name, freq) {
const isHigh = CRITICALITY_HIGH.has(name);
const isEssential = TIER1_ESSENTIAL.has(name);
const criticality = isHigh ? 'HIGH' : (isEssential || TIER2_USEFUL.has(name) || freq >= 2) ? 'MEDIUM' : 'LOW';
let tier;
if (freq >= TIER1_FREQ_THRESHOLD || isHigh || isEssential) {
tier = 1;
} else if (freq >= 1 || TIER2_USEFUL.has(name) || criticality === 'MEDIUM') {
tier = 2;
} else {
tier = 3;
}
return { tier, criticality };
}
// --- Execute ---
if (!fs.existsSync(INDEX_PATH)) {
process.stderr.write('[FAIL] skills-index.json not found\n');
process.exit(1);
}
const index = JSON.parse(fs.readFileSync(INDEX_PATH, 'utf8'));
// 幂等检查
if (index.skills[0] && typeof index.skills[0].tier === 'number') {
process.stderr.write('[SKIP] skills-index.json already has tier field\n');
process.exit(0);
}
const freq = countFrequency();
const tierCounts = { 1: 0, 2: 0, 3: 0 };
for (const skill of index.skills) {
const f = freq[skill.name] || 0;
const { tier, criticality } = assignTier(skill.name, f);
skill.tier = tier;
skill.criticality = criticality;
skill.callCount30d = f;
tierCounts[tier]++;
}
// 备份
fs.writeFileSync(INDEX_PATH + '.bak-p03.' + Date.now(), fs.readFileSync(INDEX_PATH));
// 原子写
const tmp = INDEX_PATH + '.tmp.' + process.pid;
fs.writeFileSync(tmp, JSON.stringify(index, null, 2), 'utf8');
fs.renameSync(tmp, INDEX_PATH);
process.stderr.write('[DONE] skills-index.json tiered: T1=' + tierCounts[1] + ' T2=' + tierCounts[2] + ' T3=' + tierCounts[3] + '\n');