feat(scripts): 校验器加 --verbose,成功路径也打印逐端点命中行
changelog-filename-gate / validate (push) Failing after 2s

背景:changelog 校验器的端点解析结果原本只在「清单与详情对不上」时才进 errors,
成功路径零输出(只有一行 PASS)。于是「每个端点都被校验过」这件事**无法取证**——
#7439 AC-19 要求「贴校验器输出里逐端点的命中行、端点数对得上」,判据字面不可达成。

改法:模块级开关 VERBOSE_ENDPOINTS,main 识别 --verbose 后从 argv 剔除再走原解析
(避免打乱 argv[0] === '--files' 的判断)。打印格式:

  [endpoint] <file> #<n> <METHOD> <path>
  [endpoint-count] <file> <n>

硬约束:**默认行为一个字节都不变**。它是提交门禁,加日志不得影响判定。
已验:同一组文件在改前/改后不带 --verbose 跑,stdout+stderr 逐字节 diff 为空;
仓内 14 条测试改前改后均 14 pass / 0 fail。

为什么值得改而不是改判据:逐端点证据对所有 changelog 都有用,
而「手工用同一条正则再枚举一遍」产出的不是校验器输出,等于用一个未经检验的
复制品去证明原件跑过了。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
API Changelog Bot
2026-09-13 13:04:22 +08:00
共同撰写人 Claude Opus 5
父节点 ad0c43afb1
当前提交 5a24c71fe0
@@ -110,6 +110,15 @@ function sectionByPrefix(body, prefix, level = 2) {
return lines.slice(start + 1, end).join('\n');
}
// --verbose 时把逐端点命中行打到 stdout。默认 false ⇒ 输出与加它之前逐字节相同。
// 为什么需要它:本校验器的端点解析结果原本只在「清单与详情对不上」时才进 errors,
// 成功路径零输出,于是「每个端点都被校验过」这件事无法取证(#7439 AC-19)。
let VERBOSE_ENDPOINTS = false;
export function setVerboseEndpoints(on) {
VERBOSE_ENDPOINTS = Boolean(on);
}
function apiEndpointKey(method, endpointPath) {
return `${method.trim().toUpperCase()} ${endpointPath.trim()}`;
}
@@ -166,6 +175,12 @@ function validateApiTemplate(file, metadata, body) {
);
const detailMatches = [...detailSection.matchAll(detailPattern)];
const detailed = detailMatches.map((match) => apiEndpointKey(match[1], match[2]));
if (VERBOSE_ENDPOINTS) {
detailMatches.forEach((match, index) => {
console.log(`[endpoint] ${file} #${index + 1} ${match[1]} ${match[2].trim()}`);
});
console.log(`[endpoint-count] ${file} ${detailMatches.length}`);
}
if (detailMatches.length === 0) {
errors.push(ruleError(
'E_API_TEMPLATE',
@@ -477,6 +492,11 @@ export function runFilesValidation(files, root = process.cwd()) {
}
export function main(argv = process.argv.slice(2)) {
// --verbose 可出现在任意位置;剔除后再走原有解析,避免打乱 argv[0] === '--files' 的判断。
if (argv.includes('--verbose')) {
setVerboseEndpoints(true);
argv = argv.filter((item) => item !== '--verbose');
}
try {
if (argv[0] === '--files') {
const files = argv.slice(1).flatMap((item) => item.split(',')).filter(Boolean);