scripts: validate-changelog-frontmatter 增加 --files 提交前自检模式(与 pre-push 门禁同规则,供作者/Agent 与 hl-workflow lint 复用)
changelog-filename-gate / validate (push) Successful in 2s

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Claude-Session: https://claude.ai/code/session_01XYL5S9SsBtkg7aGyFAbrrQ
这个提交包含在:
API Changelog Bot
2026-09-06 16:18:27 +08:00
共同撰写人 Claude Fable 5.1
父节点 7b9b60b9ac
当前提交 354d9083a7
@@ -447,8 +447,51 @@ export function runFrontmatterValidation(records, root = process.cwd()) {
return { checkedCount: documents.length, errors }; return { checkedCount: documents.length, errors };
} }
/**
* 提交前自检模式:`--files a.md b.md`(或逗号分隔)按「新增文件」口径校验工作区里的 changelog,
* 让作者/Agent 不用先 commit 就能跑到与 pre-push 门禁完全相同的规则(hl-workflow changelog_workflow.py lint 也走这里)。
*/
export function runFilesValidation(files, root = process.cwd()) {
const errors = [];
let checkedCount = 0;
for (const raw of files) {
const absolute = path.resolve(root, raw);
const relative = path.relative(root, absolute).split(path.sep).join('/');
if (!controlledRootForPath(relative) || !relative.endsWith('.md')) {
errors.push(ruleError('E_PATH', relative, '不在受控 changelog 目录内(changelogs/ 或 changelogs-v2/)'));
continue;
}
let text;
try {
text = readFileSync(absolute, 'utf8');
} catch (error) {
errors.push(ruleError('E_READ', relative, `无法读取文件: ${error.message}`));
continue;
}
checkedCount += 1;
errors.push(...validateV2Document(relative, text, { requireV2: true }));
}
return { checkedCount, errors };
}
export function main(argv = process.argv.slice(2)) { export function main(argv = process.argv.slice(2)) {
try { try {
if (argv[0] === '--files') {
const files = argv.slice(1).flatMap((item) => item.split(',')).filter(Boolean);
if (files.length === 0) {
throw new Error('--files 需要至少一个文件路径');
}
const result = runFilesValidation(files);
if (result.errors.length > 0) {
for (const error of result.errors) {
console.error(`[${error.code}] ${error.path}: ${error.message}`);
}
console.error(`FAIL: ${result.errors.length} frontmatter error(s) in ${result.checkedCount} changelog file(s).`);
return 1;
}
console.log(`PASS: validated frontmatter for ${result.checkedCount} changelog file(s) (--files).`);
return 0;
}
const options = parseArguments(argv); const options = parseArguments(argv);
const records = parseNameStatusZ(diffFromOptions(options)); const records = parseNameStatusZ(diffFromOptions(options));
const result = runFrontmatterValidation(records); const result = runFrontmatterValidation(records);