#!/usr/bin/env node /** * 多项目上下文隔离器 (v5.9) * * 为不同工作目录 (cwd) 提供隔离的状态文件路径, * 防止项目 A 的上下文/权重污染项目 B 的路由。 * * 隔离文件: * - context-state.json → context-state-{hash}.json * - fusion-weights.json → fusion-weights-{hash}.json * - route-weights.json → route-weights-{hash}.json * * 非隔离 (全局共享): * - route-feedback.jsonl (全局学习语料) * - skill-outcome.jsonl (全局观测) * - skills-index.json (技能定义) * * 用法: * const { getIsolatedPath } = require('./project-isolator.js'); * const stateFile = getIsolatedPath('context-state.json', cwd); */ const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); const detectClaudeRoot = () => require('./paths.config.js').PATHS.root; const ROOT = detectClaudeRoot(); const DEBUG_DIR = path.join(ROOT, 'debug'); // 需要隔离的文件名列表 const ISOLATED_FILES = new Set([ 'context-state.json', 'fusion-weights.json', 'route-weights.json', 'session-memory.json', // 防止跨项目会话偏好污染 ]); /** * 为 cwd 生成短哈希 (6 字符) * @param {string} cwd - 工作目录 * @returns {string} 6 位 hex 哈希 */ function cwdHash(cwd) { if (!cwd) return 'global'; const normalized = cwd.replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase(); return crypto.createHash('md5').update(normalized).digest('hex').slice(0, 6); } /** * 获取隔离后的文件路径 * @param {string} filename - 原始文件名 (如 'context-state.json') * @param {string} [cwd] - 工作目录 (null/undefined 使用全局路径) * @returns {string} 隔离后的完整路径 */ function getIsolatedPath(filename, cwd) { if (!cwd || !ISOLATED_FILES.has(filename)) { return path.join(DEBUG_DIR, filename); } const hash = cwdHash(cwd); const ext = path.extname(filename); const base = path.basename(filename, ext); return path.join(DEBUG_DIR, `${base}-${hash}${ext}`); } /** * 列出某个基础文件的所有隔离实例 * @param {string} filename - 原始文件名 * @returns {Array<{path: string, hash: string, mtime: Date}>} */ function listInstances(filename) { const ext = path.extname(filename); const base = path.basename(filename, ext); const pattern = new RegExp(`^${base}-([a-f0-9]{6})${ext.replace('.', '\\.')}$`); try { return fs.readdirSync(DEBUG_DIR) .filter(f => pattern.test(f)) .map(f => { const match = f.match(pattern); const fullPath = path.join(DEBUG_DIR, f); const stat = fs.statSync(fullPath); return { path: fullPath, hash: match[1], mtime: stat.mtime }; }) .sort((a, b) => b.mtime - a.mtime); } catch { return []; } } /** * 清理过期隔离文件 (超过 30 天未修改) * @returns {number} 清理数量 */ function cleanStale(maxAgeDays = 30) { const cutoff = Date.now() - maxAgeDays * 86400000; let cleaned = 0; for (const filename of ISOLATED_FILES) { for (const inst of listInstances(filename)) { if (inst.mtime.getTime() < cutoff) { try { fs.unlinkSync(inst.path); cleaned++; } catch {} } } } return cleaned; } // 模块导出 if (typeof module !== 'undefined') { module.exports = { cwdHash, getIsolatedPath, listInstances, cleanStale, ISOLATED_FILES, DEBUG_DIR, }; } // CLI 入口 if (require.main === module) { console.log('=== 多项目隔离器 ==='); // 列出所有隔离实例 for (const filename of ISOLATED_FILES) { const instances = listInstances(filename); console.log(`\n${filename}: ${instances.length} 个实例`); for (const inst of instances) { console.log(` [${inst.hash}] ${inst.mtime.toISOString().slice(0, 19)}`); } } // 测试 const testCwds = [ 'C:\\Users\\home\\project-a', 'C:\\Users\\home\\project-b', '/opt/xianyuzhushou', ]; console.log('\n测试路径映射:'); for (const cwd of testCwds) { const hash = cwdHash(cwd); console.log(` ${cwd} → ${hash}`); console.log(` context-state: ${path.basename(getIsolatedPath('context-state.json', cwd))}`); } }