/** * GLADA · 代码生成器 + 回归防护 · code-generator.js * * 解决"修了这个坏那个"的问题: * 1. 修改前,自动拍摄所有相关文件的快照 * 2. 修改后,自动运行现有测试 * 3. 测试失败则自动回滚并尝试其他方案 * 4. 记录所有文件的依赖关系,确保修改不破坏依赖方 * * 版权:国作登字-2026-A-00037559 * 签发:铸渊 · ICE-GL-ZY001 */ 'use strict'; const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const ROOT = path.resolve(__dirname, '..'); /** * 查找文件的所有被引用方(谁依赖了这个文件) * @param {string} targetRelPath - 被修改的文件(相对路径) * @returns {string[]} 引用了该文件的文件列表 */ function findDependents(targetRelPath) { const dependents = []; const normalizedTarget = targetRelPath.replace(/\.js$/, ''); // 使用 grep 搜索引用(sanitize 输入防止 shell 注入) try { const safeBasename = path.basename(normalizedTarget).replace(/[^A-Za-z0-9._-]/g, ''); if (!safeBasename) return dependents; const grepResult = execSync( `grep -rl "${safeBasename}" --include="*.js" "${ROOT}" 2>/dev/null || true`, { encoding: 'utf-8', timeout: 15000 } ); const lines = grepResult.trim().split('\n').filter(Boolean); for (const line of lines) { const relPath = path.relative(ROOT, line); // 排除 node_modules 和自身 if (!relPath.startsWith('node_modules') && relPath !== targetRelPath) { dependents.push(relPath); } } } catch { // grep 失败不影响主流程 } return dependents.slice(0, 20); // 限制数量 } /** * 回归防护检查 * 验证修改后的文件不影响其依赖方 * @param {string[]} changedFiles - 已修改的文件列表 * @returns {{ safe: boolean, issues: string[] }} */ function regressionCheck(changedFiles) { const issues = []; for (const filePath of changedFiles) { const dependents = findDependents(filePath); if (dependents.length > 0) { // 检查依赖方是否能正常 require/import for (const dep of dependents.slice(0, 5)) { const absPath = path.resolve(ROOT, dep); try { // 简单的语法检查 execSync(`node --check "${absPath}" 2>&1`, { encoding: 'utf-8', timeout: 5000 }); } catch (err) { issues.push(`${dep} 语法检查失败 (依赖 ${filePath}): ${err.message.substring(0, 200)}`); } } } } return { safe: issues.length === 0, issues }; } /** * 带回归防护的代码变更执行器 * @param {Object[]} files - 文件变更列表 * @param {Object} constraints - 约束条件 * @param {Object} [options] - 选项 * @returns {Promise} 执行结果 */ async function executeWithRegressionGuard(files, constraints, options = {}) { const result = { success: false, applied: [], rolled_back: false, regression_check: null, test_result: null, errors: [] }; // 1. 收集所有要修改的文件路径 const filePaths = files.map(f => f.path); // 2. 拍摄快照 const snapshot = new Map(); for (const relPath of filePaths) { const absPath = path.resolve(ROOT, relPath); if (fs.existsSync(absPath)) { snapshot.set(relPath, fs.readFileSync(absPath, 'utf-8')); } else { snapshot.set(relPath, null); } } // 3. 检查约束 const noTouch = constraints?.no_touch_files || []; const safeFiles = files.filter(f => { const blocked = noTouch.some(nt => f.path.startsWith(nt)); if (blocked) { result.errors.push(`跳过受保护文件: ${f.path}`); } return !blocked; }); // 4. 应用变更 for (const file of safeFiles) { const absPath = path.resolve(ROOT, file.path); try { if (file.action === 'delete') { if (fs.existsSync(absPath)) fs.unlinkSync(absPath); } else { fs.mkdirSync(path.dirname(absPath), { recursive: true }); fs.writeFileSync(absPath, file.content, 'utf-8'); } result.applied.push(file.path); } catch (err) { result.errors.push(`${file.action} ${file.path}: ${err.message}`); } } // 5. 回归防护检查 if (result.applied.length > 0) { result.regression_check = regressionCheck(result.applied); if (!result.regression_check.safe && options.rollbackOnRegression !== false) { console.warn('[GLADA-CodeGen] ⚠️ 回归检查发现问题,回滚...'); rollback(snapshot); result.rolled_back = true; result.applied = []; return result; } } // 6. 运行测试 if (options.runTests !== false) { try { execSync('npm run test:smoke 2>&1', { cwd: ROOT, encoding: 'utf-8', timeout: 60000 }); result.test_result = { success: true }; } catch (err) { result.test_result = { success: false, output: (err.stdout || err.message || '').substring(0, 500) }; if (options.rollbackOnTestFail !== false) { console.warn('[GLADA-CodeGen] ❌ 测试失败,回滚...'); rollback(snapshot); result.rolled_back = true; result.applied = []; return result; } } } result.success = result.applied.length > 0 && !result.rolled_back; return result; } /** * 从快照回滚 * @param {Map} snapshot */ function rollback(snapshot) { for (const [relPath, content] of snapshot) { const absPath = path.resolve(ROOT, relPath); if (content === null) { if (fs.existsSync(absPath)) fs.unlinkSync(absPath); } else { fs.mkdirSync(path.dirname(absPath), { recursive: true }); fs.writeFileSync(absPath, content, 'utf-8'); } } } module.exports = { findDependents, regressionCheck, executeWithRegressionGuard, rollback };