autosnippet
Version:
Extract code patterns into a knowledge base for AI coding assistants
142 lines (141 loc) • 5.67 kB
JavaScript
/**
* @module DiscovererPreference
* @description Discoverer 用户偏好持久化 + 冲突检测
*
* 当多个 Discoverer 匹配且置信度接近时,允许用户确认选择并持久化。
* CLI 上下文使用 readline 交互,MCP/HTTP 上下文返回 ambiguous 标记。
*/
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
// ── Constants ───────────────────────────────────────
const PREFERENCE_DIR = '.autosnippet';
const PREFERENCE_FILE = 'discoverer-preference.json';
/** 两个 Discoverer confidence 差值低于此阈值视为模糊 */
const AMBIGUITY_THRESHOLD = 0.1;
/** 最高 confidence 低于此值视为启发式不确定 */
const HEURISTIC_UNCERTAIN_THRESHOLD = 0.6;
// ── Conflict Detection ──────────────────────────────
/**
* 检测 Discoverer 匹配结果是否存在冲突/模糊
*/
export function detectConflict(matches) {
if (matches.length === 0) {
return { ambiguous: false, matches };
}
if (matches.length === 1) {
return { ambiguous: false, matches, recommended: matches[0] };
}
const top = matches[0];
const second = matches[1];
// 条件 1: 多个高置信度结果 (≥ 0.60)
const highConfCount = matches.filter((m) => m.confidence >= 0.6).length;
// 条件 2: top-1 与 top-2 差距 < 阈值
const closeDelta = top.confidence - second.confidence < AMBIGUITY_THRESHOLD;
// 条件 3: 最高分仍低于阈值(仅启发式命中)
const heuristicOnly = top.confidence < HEURISTIC_UNCERTAIN_THRESHOLD;
if (highConfCount >= 2 && closeDelta) {
return {
ambiguous: true,
reason: `Multiple build systems detected with similar confidence (${top.displayName}: ${top.confidence.toFixed(2)} vs ${second.displayName}: ${second.confidence.toFixed(2)})`,
matches,
recommended: top,
};
}
if (heuristicOnly) {
return {
ambiguous: true,
reason: `No definitive build system identified (highest: ${top.displayName} at ${top.confidence.toFixed(2)})`,
matches,
recommended: top,
};
}
return { ambiguous: false, matches, recommended: top };
}
// ── Preference Persistence ──────────────────────────
/**
* 获取偏好文件路径
*/
function getPreferencePath(projectRoot) {
return join(projectRoot, PREFERENCE_DIR, PREFERENCE_FILE);
}
/**
* 加载已保存的 Discoverer 偏好
* @returns 偏好数据,或 null(无偏好/文件不存在/损坏)
*/
export function loadPreference(projectRoot) {
const prefPath = getPreferencePath(projectRoot);
if (!existsSync(prefPath)) {
return null;
}
try {
const content = readFileSync(prefPath, 'utf8');
const data = JSON.parse(content);
// 基本结构校验
if (typeof data.selectedDiscoverer !== 'string' || typeof data.userConfirmed !== 'boolean') {
return null;
}
return data;
}
catch {
return null;
}
}
/**
* 保存 Discoverer 偏好
*/
export function savePreference(projectRoot, discovererId, alternatives, userConfirmed) {
const prefPath = getPreferencePath(projectRoot);
const prefDir = join(projectRoot, PREFERENCE_DIR);
if (!existsSync(prefDir)) {
mkdirSync(prefDir, { recursive: true });
}
const data = {
selectedDiscoverer: discovererId,
selectedAt: new Date().toISOString(),
alternatives,
userConfirmed,
};
writeFileSync(prefPath, JSON.stringify(data, null, 2), 'utf8');
}
// ── CLI Interactive Prompt ──────────────────────────
/**
* CLI 交互式确认 Discoverer 选择
* 仅在 CLI 终端上下文(stdin 可用)时有效
*
* @returns 用户选择的 Discoverer ID,或 null(非交互环境/超时)
*/
export async function promptDiscovererChoice(matches, recommended) {
// 检测是否在可交互终端
if (!process.stdin.isTTY) {
return null;
}
const { createInterface } = await import('node:readline');
const rl = createInterface({ input: process.stdin, output: process.stdout });
const question = (prompt) => new Promise((resolve) => {
rl.question(prompt, (answer) => {
resolve(answer.trim());
});
});
try {
console.log('\n⚠️ 项目构建系统识别需要确认\n');
console.log('检测到以下构建系统配置:');
for (let i = 0; i < matches.length; i++) {
const m = matches[i];
const rec = recommended && m.discovererId === recommended.discovererId ? ' ← 推荐' : '';
console.log(` [${i + 1}] ${m.displayName} confidence: ${m.confidence.toFixed(2)}${rec}`);
}
const defaultIdx = recommended
? matches.findIndex((m) => m.discovererId === recommended.discovererId) + 1
: 1;
const answer = await question(`\n请选择主要构建系统 [1-${matches.length}],或按回车使用推荐 (${defaultIdx}): `);
const choice = answer === '' ? defaultIdx : Number.parseInt(answer, 10);
if (choice >= 1 && choice <= matches.length) {
return matches[choice - 1].discovererId;
}
// 无效输入, 使用推荐
return recommended?.discovererId ?? matches[0].discovererId;
}
finally {
rl.close();
}
}