reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const REQUIRED_FIELDS = [
|
||||
'修改前逻辑',
|
||||
'问题证据',
|
||||
'本次修改',
|
||||
'修改理由',
|
||||
'已失败尝试',
|
||||
'禁止回退',
|
||||
'验证命令',
|
||||
'验证结果',
|
||||
'当前验证范围',
|
||||
'遗留风险',
|
||||
'当前状态',
|
||||
];
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {
|
||||
caseDir: 'case',
|
||||
changed: [],
|
||||
init: false,
|
||||
requireEntry: false,
|
||||
json: false,
|
||||
markdown: false,
|
||||
help: false,
|
||||
};
|
||||
for (let i = 2; i < argv.length; i++) {
|
||||
const a = argv[i];
|
||||
if (a === '--case-dir' || a === '--dir' || a === '-d') args.caseDir = argv[++i] || '';
|
||||
else if (a === '--changed' || a === '--file') args.changed.push(argv[++i] || '');
|
||||
else if (a === '--init') args.init = true;
|
||||
else if (a === '--require-entry') args.requireEntry = true;
|
||||
else if (a === '--json') args.json = true;
|
||||
else if (a === '--markdown') args.markdown = true;
|
||||
else if (a === '--help' || a === '-h') args.help = true;
|
||||
else throw new Error(`未知参数:${a}`);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
function usage() {
|
||||
return `用法:
|
||||
node scripts/check_change_memory.js --case-dir case --init --markdown
|
||||
node scripts/check_change_memory.js --case-dir case --markdown
|
||||
node scripts/check_change_memory.js --case-dir case --changed result/src/env/install-env.js --require-entry --markdown
|
||||
node scripts/check_change_memory.js --case-dir case --changed result/src/signer/index.js --json
|
||||
|
||||
说明:检查复杂 case 是否维护 case/notes/代码变更记忆.md,并确认关键源码修改后记录了修改原因、失败尝试、禁止回退、验证范围和遗留风险。`;
|
||||
}
|
||||
|
||||
function normalizeSlash(v) {
|
||||
return String(v || '').replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
function exists(p) {
|
||||
try { fs.accessSync(p); return true; } catch { return false; }
|
||||
}
|
||||
|
||||
function memoryPath(caseDir) {
|
||||
return path.join(path.resolve(caseDir), 'notes', '代码变更记忆.md');
|
||||
}
|
||||
|
||||
function createTemplate() {
|
||||
return `# 代码变更记忆
|
||||
|
||||
本文件由通用代码变更记忆机制维护,用于记录复杂 case 中关键源码修改的事实、原因、失败尝试、禁止回退、验证范围和遗留风险。不要把“当前报错消失”直接写成永久结论。
|
||||
|
||||
## 使用规则
|
||||
|
||||
- 修改关键源码前先读取本文件,并搜索文件名、函数名、错误关键词和参数名。
|
||||
- 修改关键源码后立即追加一条“变更”记录。
|
||||
- 不删除失败记录;如历史记录被新证据替代,新增记录说明替代原因。
|
||||
- 状态只能使用:临时修复 / 当前验证通过 / 已失败 / 被替代 / 稳定基线。
|
||||
|
||||
## 变更 001 - 待填写
|
||||
|
||||
- 时间:YYYY-MM-DD HH:mm
|
||||
- 涉及文件:
|
||||
- 涉及函数 / 模块:
|
||||
- 当前状态:临时修复
|
||||
|
||||
### 修改前逻辑
|
||||
|
||||
待填写。
|
||||
|
||||
### 问题证据
|
||||
|
||||
待填写。
|
||||
|
||||
### 本次修改
|
||||
|
||||
待填写。
|
||||
|
||||
### 修改理由
|
||||
|
||||
待填写。
|
||||
|
||||
### 已失败尝试
|
||||
|
||||
待填写。
|
||||
|
||||
### 禁止回退
|
||||
|
||||
待填写。
|
||||
|
||||
### 验证命令
|
||||
|
||||
\`\`\`bash
|
||||
# 待填写
|
||||
\`\`\`
|
||||
|
||||
### 验证结果
|
||||
|
||||
待填写。
|
||||
|
||||
### 当前验证范围
|
||||
|
||||
待填写。
|
||||
|
||||
### 遗留风险
|
||||
|
||||
待填写。
|
||||
`;
|
||||
}
|
||||
|
||||
function initMemory(file) {
|
||||
fs.mkdirSync(path.dirname(file), { recursive: true });
|
||||
if (!exists(file)) {
|
||||
fs.writeFileSync(file, createTemplate(), 'utf8');
|
||||
return 'created';
|
||||
}
|
||||
return 'kept';
|
||||
}
|
||||
|
||||
function hasEntry(text) {
|
||||
return /^##\s+变更\s+\d+/m.test(text);
|
||||
}
|
||||
|
||||
function fieldMissing(text) {
|
||||
return REQUIRED_FIELDS.filter(field => !text.includes(field));
|
||||
}
|
||||
|
||||
function changedMatched(text, changedFile) {
|
||||
const normalized = normalizeSlash(changedFile).replace(/^\.\//, '');
|
||||
const basename = path.basename(normalized);
|
||||
const textSlash = normalizeSlash(text);
|
||||
if (normalized && textSlash.includes(normalized)) return true;
|
||||
if (basename && textSlash.includes(basename)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function check(args) {
|
||||
const caseDir = path.resolve(args.caseDir || 'case');
|
||||
const file = memoryPath(caseDir);
|
||||
const problems = [];
|
||||
const warnings = [];
|
||||
const actions = [];
|
||||
|
||||
if (args.init) actions.push({ action: initMemory(file), path: file });
|
||||
|
||||
if (!exists(file)) {
|
||||
problems.push(`未找到通用代码变更记忆文件:${file}`);
|
||||
return {
|
||||
caseDir,
|
||||
memoryFile: file,
|
||||
exists: false,
|
||||
clean: false,
|
||||
actions,
|
||||
requiredFields: REQUIRED_FIELDS,
|
||||
missingFields: REQUIRED_FIELDS.slice(),
|
||||
changed: args.changed,
|
||||
matchedChanged: [],
|
||||
problems,
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
|
||||
const text = fs.readFileSync(file, 'utf8');
|
||||
const missingFields = fieldMissing(text);
|
||||
if (missingFields.length) problems.push(`代码变更记忆缺少必要字段:${missingFields.join('、')}`);
|
||||
|
||||
const entryExists = hasEntry(text);
|
||||
if (args.requireEntry && !entryExists) problems.push('已要求至少一条变更记录,但未发现 “## 变更 001” 这类记录。');
|
||||
if (!entryExists) warnings.push('尚未发现正式变更记录;如果已经修改关键源码,请立即追加记录。');
|
||||
|
||||
const matchedChanged = [];
|
||||
for (const changed of args.changed.filter(Boolean)) {
|
||||
const matched = changedMatched(text, changed);
|
||||
matchedChanged.push({ file: changed, matched });
|
||||
if (args.requireEntry && !matched) {
|
||||
problems.push(`已修改关键文件但代码变更记忆中未提到该文件:${changed}`);
|
||||
} else if (!matched) {
|
||||
warnings.push(`代码变更记忆中未提到变更文件:${changed}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (/正确方案/.test(text)) {
|
||||
warnings.push('发现“正确方案”表述;建议改为“当前验证通过 / 稳定基线”,并写明验证范围和遗留风险。');
|
||||
}
|
||||
|
||||
return {
|
||||
caseDir,
|
||||
memoryFile: file,
|
||||
exists: true,
|
||||
clean: problems.length === 0,
|
||||
actions,
|
||||
requiredFields: REQUIRED_FIELDS,
|
||||
missingFields,
|
||||
hasEntry: entryExists,
|
||||
changed: args.changed,
|
||||
matchedChanged,
|
||||
problems,
|
||||
warnings,
|
||||
};
|
||||
}
|
||||
|
||||
function renderMarkdown(result) {
|
||||
const lines = [
|
||||
'# 通用代码变更记忆检查',
|
||||
'',
|
||||
`- case 目录:${result.caseDir}`,
|
||||
`- 记忆文件:${result.memoryFile}`,
|
||||
`- 文件存在:${result.exists ? '是' : '否'}`,
|
||||
`- 是否通过:${result.clean ? '是' : '否'}`,
|
||||
'',
|
||||
];
|
||||
|
||||
if (result.actions.length) {
|
||||
lines.push('## 初始化动作');
|
||||
for (const item of result.actions) {
|
||||
lines.push(`- ${item.action === 'created' ? '已创建' : '已存在'}:${item.path}`);
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push('## 必要字段');
|
||||
for (const field of result.requiredFields) {
|
||||
lines.push(`- ${field}:${result.missingFields.includes(field) ? '缺失' : '存在'}`);
|
||||
}
|
||||
lines.push('');
|
||||
|
||||
if (result.matchedChanged && result.matchedChanged.length) {
|
||||
lines.push('## 变更文件命中');
|
||||
for (const item of result.matchedChanged) {
|
||||
lines.push(`- ${item.file}:${item.matched ? '已记录' : '未记录'}`);
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
if (result.problems.length) {
|
||||
lines.push('## 问题');
|
||||
for (const p of result.problems) lines.push(`- ${p}`);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
if (result.warnings.length) {
|
||||
lines.push('## 提醒');
|
||||
for (const w of result.warnings) lines.push(`- ${w}`);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
return lines.join('\n') + '\n';
|
||||
}
|
||||
|
||||
try {
|
||||
const args = parseArgs(process.argv);
|
||||
if (args.help) {
|
||||
console.log(usage());
|
||||
process.exit(0);
|
||||
}
|
||||
const result = check(args);
|
||||
if (args.json) console.log(JSON.stringify(result, null, 2));
|
||||
if (args.markdown || !args.json) process.stdout.write(renderMarkdown(result));
|
||||
process.exit(result.clean ? 0 : 1);
|
||||
} catch (err) {
|
||||
console.error(err.message || String(err));
|
||||
console.error(usage());
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user