reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
#!/usr/bin/env node
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { check: checkRuntimeConformance } = require('./check_trace_runtime_conformance');
|
||||
|
||||
const ALLOWED_STATUSES = new Set([
|
||||
'planned-first-pass',
|
||||
'implemented-first-pass',
|
||||
'needs-baseline-sampling',
|
||||
'deferred-not-mounted',
|
||||
'native-capability-gap',
|
||||
'live-discovered',
|
||||
'missed-from-trace',
|
||||
]);
|
||||
|
||||
const PRIORITIES_REQUIRING_DECISION = new Set(['P0', 'P1']);
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {
|
||||
caseDir: '',
|
||||
inventory: '',
|
||||
matrix: '',
|
||||
contract: '',
|
||||
nodeAudit: '',
|
||||
requireRuntimeClosure: false,
|
||||
requireStageAudit: 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 === '--inventory') args.inventory = argv[++i] || '';
|
||||
else if (a === '--matrix') args.matrix = argv[++i] || '';
|
||||
else if (a === '--contract') args.contract = argv[++i] || '';
|
||||
else if (a === '--node-audit') args.nodeAudit = argv[++i] || '';
|
||||
else if (a === '--require-runtime-closure') args.requireRuntimeClosure = true;
|
||||
else if (a === '--require-stage-audit') args.requireStageAudit = 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}`);
|
||||
}
|
||||
if (!args.json && !args.markdown) args.markdown = true;
|
||||
return args;
|
||||
}
|
||||
|
||||
function usage() {
|
||||
return `用法:
|
||||
node scripts/check_trace_api_coverage.js --case-dir case --markdown
|
||||
node scripts/check_trace_api_coverage.js --case-dir case --require-stage-audit --require-runtime-closure --json
|
||||
node scripts/check_trace_api_coverage.js --inventory case/notes/trace-api-inventory.json --matrix case/notes/env-coverage-matrix.md --markdown
|
||||
|
||||
说明:检查 Trace API inventory、env coverage matrix、实现文件和 Trace-runtime 行为闭环,以及后续阶段报告中计划外新增 WebAPI 是否说明原因。`;
|
||||
}
|
||||
|
||||
function exists(p) {
|
||||
try { return fs.existsSync(p); } catch { return false; }
|
||||
}
|
||||
|
||||
function readText(p) {
|
||||
return fs.readFileSync(p, 'utf8').replace(/^\uFEFF/, '');
|
||||
}
|
||||
|
||||
function readJson(p) {
|
||||
return JSON.parse(readText(p));
|
||||
}
|
||||
|
||||
function rel(root, file) {
|
||||
return (path.relative(root, file) || '.').replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
function normalizePriority(value) {
|
||||
const text = String(value || '').trim().toUpperCase();
|
||||
if (/^P?[0-2]$/.test(text)) return text.startsWith('P') ? text : `P${text}`;
|
||||
return text || 'UNKNOWN';
|
||||
}
|
||||
|
||||
function normalizeStatus(value) {
|
||||
return String(value || '').trim();
|
||||
}
|
||||
|
||||
function normalizeInventory(raw) {
|
||||
if (Array.isArray(raw)) return raw;
|
||||
if (Array.isArray(raw.apis)) return raw.apis;
|
||||
if (raw.inventory && Array.isArray(raw.inventory.apis)) return raw.inventory.apis;
|
||||
if (raw.apiInventory && Array.isArray(raw.apiInventory)) return raw.apiInventory;
|
||||
return [];
|
||||
}
|
||||
|
||||
function implementationPath(caseDir, file) {
|
||||
if (!file) return '';
|
||||
return path.isAbsolute(file) ? file : path.resolve(caseDir, file);
|
||||
}
|
||||
|
||||
function listMarkdown(dir) {
|
||||
if (!exists(dir)) return [];
|
||||
const out = [];
|
||||
for (const name of fs.readdirSync(dir)) {
|
||||
const file = path.join(dir, name);
|
||||
try {
|
||||
if (fs.statSync(file).isFile() && name.toLowerCase().endsWith('.md')) out.push(file);
|
||||
} catch {}
|
||||
}
|
||||
return out.sort();
|
||||
}
|
||||
|
||||
function hasTraceEvidence(caseDir) {
|
||||
const candidates = [
|
||||
path.join(caseDir, 'ruyi-trace'),
|
||||
path.join(caseDir, 'notes', 'ruyitrace-summary.md'),
|
||||
path.join(caseDir, 'tmp', 'env-trace.jsonl'),
|
||||
path.join(caseDir, 'tmp', 'missing-env.json'),
|
||||
];
|
||||
return candidates.some(exists);
|
||||
}
|
||||
|
||||
function auditMatrixText(text) {
|
||||
const required = ['Trace', 'baselineId', 'P0', 'P1', '首轮', '采样', '暂不挂载', '计划外'];
|
||||
return required.filter(key => !text.includes(key));
|
||||
}
|
||||
|
||||
function auditStageReports(caseDir) {
|
||||
const stageDir = path.join(caseDir, '阶段报告');
|
||||
const files = listMarkdown(stageDir);
|
||||
const findings = [];
|
||||
const reasonPattern = /trace-not-covered|dynamic-resource-new-branch|baseline-mismatch|trace-truncated|native-gap|missed-from-trace|Trace 未覆盖|动态资源新分支|baseline|截断|能力缺口|矩阵遗漏|流程缺陷/;
|
||||
for (const file of files) {
|
||||
const text = readText(file);
|
||||
const mentionsOldHeading = text.includes('本阶段新增 / 修改的 WebAPI');
|
||||
const hasPlanned = text.includes('Trace 计划内') || text.includes('计划内首轮');
|
||||
const hasUnplanned = text.includes('计划外新增');
|
||||
if (mentionsOldHeading && !hasPlanned && !hasUnplanned) {
|
||||
findings.push({
|
||||
file: rel(caseDir, file),
|
||||
problem: '阶段报告仍使用笼统的“本阶段新增 / 修改的 WebAPI”,未拆分 Trace 计划内与计划外新增。',
|
||||
});
|
||||
}
|
||||
if (hasUnplanned) {
|
||||
const section = text.slice(text.indexOf('计划外新增'));
|
||||
if (!reasonPattern.test(section)) {
|
||||
findings.push({
|
||||
file: rel(caseDir, file),
|
||||
problem: '计划外新增 WebAPI 缺少原因枚举或证据说明。',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return findings;
|
||||
}
|
||||
|
||||
function check(args) {
|
||||
const caseDir = args.caseDir ? path.resolve(args.caseDir) : process.cwd();
|
||||
const notesDir = path.join(caseDir, 'notes');
|
||||
const inventoryPath = path.resolve(args.inventory || path.join(notesDir, 'trace-api-inventory.json'));
|
||||
const matrixPath = path.resolve(args.matrix || path.join(notesDir, 'env-coverage-matrix.md'));
|
||||
const contractPath = path.resolve(args.contract || path.join(notesDir, 'trace-runtime-contract.json'));
|
||||
const nodeAuditPath = path.resolve(args.nodeAudit || path.join(caseDir, 'tmp', 'node-trace-runtime-audit.json'));
|
||||
const problems = [];
|
||||
const warnings = [];
|
||||
const traceDetected = hasTraceEvidence(caseDir);
|
||||
|
||||
if (traceDetected && !exists(inventoryPath)) problems.push(`存在 Trace 证据,但缺少 ${rel(caseDir, inventoryPath)}`);
|
||||
if (traceDetected && !exists(matrixPath)) problems.push(`存在 Trace 证据,但缺少 ${rel(caseDir, matrixPath)}`);
|
||||
|
||||
let apis = [];
|
||||
let inventoryMeta = {};
|
||||
if (exists(inventoryPath)) {
|
||||
try {
|
||||
const raw = readJson(inventoryPath);
|
||||
inventoryMeta = {
|
||||
schemaVersion: raw.schemaVersion || '',
|
||||
source: raw.source || '',
|
||||
baselineId: raw.baselineId || '',
|
||||
};
|
||||
apis = normalizeInventory(raw);
|
||||
if (!apis.length) problems.push('trace-api-inventory.json 没有可识别的 apis 数组');
|
||||
} catch (err) {
|
||||
problems.push(`trace-api-inventory.json 无法解析为 JSON:${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
const apiResults = [];
|
||||
for (const item of apis) {
|
||||
const api = item.api || item.path || item.name || '(unknown)';
|
||||
const priority = normalizePriority(item.priority || item.level);
|
||||
const status = normalizeStatus(item.implementationStatus || item.status || item.coverageStatus);
|
||||
const reason = String(item.reason || item.decisionReason || '').trim();
|
||||
const implementationFile = String(item.implementationFile || item.file || '').trim();
|
||||
const samplingRequired = Boolean(item.samplingRequired || status === 'needs-baseline-sampling');
|
||||
const resolvedImplementationFile = implementationPath(caseDir, implementationFile);
|
||||
const result = { api, priority, status, reason, implementationFile, resolvedImplementationFile, samplingRequired, clean: true, problems: [] };
|
||||
|
||||
if (!status || status === 'unplanned') {
|
||||
result.problems.push('缺少实现状态或状态为 unplanned');
|
||||
} else if (!ALLOWED_STATUSES.has(status)) {
|
||||
result.problems.push(`未知状态:${status}`);
|
||||
}
|
||||
if (PRIORITIES_REQUIRING_DECISION.has(priority) && status === 'planned-first-pass' && !implementationFile) {
|
||||
result.problems.push('P0/P1 首轮实现项缺少 implementationFile');
|
||||
}
|
||||
if (status === 'implemented-first-pass' && (!implementationFile || !exists(resolvedImplementationFile))) {
|
||||
result.problems.push(`implemented-first-pass 对应实现文件不存在:${implementationFile || '未记录'}`);
|
||||
}
|
||||
if (args.requireRuntimeClosure && PRIORITIES_REQUIRING_DECISION.has(priority) && status === 'planned-first-pass') {
|
||||
result.problems.push('进入 runtime closure 阶段后 P0/P1 不得仍为 planned-first-pass');
|
||||
}
|
||||
if (['needs-baseline-sampling', 'deferred-not-mounted', 'native-capability-gap', 'missed-from-trace', 'live-discovered'].includes(status) && !reason) {
|
||||
result.problems.push(`${status} 必须写明 reason`);
|
||||
}
|
||||
if (status === 'missed-from-trace') {
|
||||
result.problems.push('Trace 已命中但矩阵遗漏,必须作为流程缺陷修复后再继续');
|
||||
}
|
||||
if (samplingRequired && status !== 'needs-baseline-sampling' && !reason) {
|
||||
result.problems.push('samplingRequired=true 时必须说明采样策略或阻塞原因');
|
||||
}
|
||||
if (result.problems.length) {
|
||||
result.clean = false;
|
||||
for (const p of result.problems) problems.push(`${api}:${p}`);
|
||||
}
|
||||
apiResults.push(result);
|
||||
}
|
||||
|
||||
let matrixMissing = [];
|
||||
if (exists(matrixPath)) {
|
||||
const text = readText(matrixPath);
|
||||
matrixMissing = auditMatrixText(text);
|
||||
if (matrixMissing.length) problems.push(`env-coverage-matrix.md 缺少关键内容:${matrixMissing.join('、')}`);
|
||||
}
|
||||
|
||||
const stageFindings = args.requireStageAudit ? auditStageReports(caseDir) : [];
|
||||
for (const finding of stageFindings) problems.push(`${finding.file}:${finding.problem}`);
|
||||
|
||||
let runtimeConformance = null;
|
||||
if (args.requireRuntimeClosure) {
|
||||
runtimeConformance = checkRuntimeConformance({
|
||||
caseDir,
|
||||
contract: contractPath,
|
||||
nodeAudit: nodeAuditPath,
|
||||
strictP2: false,
|
||||
});
|
||||
for (const problem of runtimeConformance.problems) problems.push(`Trace-runtime:${problem}`);
|
||||
const inventoryApis = new Set(apiResults.map(item => item.api));
|
||||
if (exists(contractPath)) {
|
||||
try {
|
||||
const contractRaw = readJson(contractPath);
|
||||
const contractItems = normalizeInventory({ apis: contractRaw.contracts || [] });
|
||||
const missingFromInventory = [...new Set(contractItems.map(item => item.api || item.path || item.name).filter(Boolean))]
|
||||
.filter(api => !inventoryApis.has(api));
|
||||
if (missingFromInventory.length) {
|
||||
problems.push(`trace-api-inventory.json 缺少 runtime contract 中的 API:${missingFromInventory.slice(0, 50).join('、')}`);
|
||||
}
|
||||
} catch (err) {
|
||||
problems.push(`无法核对 runtime contract 与 inventory:${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
caseDir,
|
||||
traceDetected,
|
||||
inventoryPath,
|
||||
matrixPath,
|
||||
contractPath,
|
||||
nodeAuditPath,
|
||||
inventoryMeta,
|
||||
clean: problems.length === 0,
|
||||
problems,
|
||||
warnings,
|
||||
apiCount: apiResults.length,
|
||||
apiResults,
|
||||
matrixMissing,
|
||||
stageFindings,
|
||||
runtimeConformance,
|
||||
};
|
||||
}
|
||||
|
||||
function renderMarkdown(result) {
|
||||
const lines = [
|
||||
'# Trace API 覆盖矩阵检查结果',
|
||||
'',
|
||||
`case 目录:${result.caseDir}`,
|
||||
`检测到 Trace 证据:${result.traceDetected ? '是' : '否'}`,
|
||||
`inventory:${result.inventoryPath}`,
|
||||
`matrix:${result.matrixPath}`,
|
||||
`runtime contract:${result.contractPath}`,
|
||||
`Node runtime audit:${result.nodeAuditPath}`,
|
||||
`是否通过:${result.clean ? '是' : '否'}`,
|
||||
'',
|
||||
'## Inventory 摘要',
|
||||
`- schemaVersion:${result.inventoryMeta.schemaVersion || '未记录'}`,
|
||||
`- source:${result.inventoryMeta.source || '未记录'}`,
|
||||
`- baselineId:${result.inventoryMeta.baselineId || '未记录'}`,
|
||||
`- API 数量:${result.apiCount}`,
|
||||
];
|
||||
|
||||
if (result.apiResults.length) {
|
||||
lines.push('', '## API 状态');
|
||||
for (const item of result.apiResults.slice(0, 80)) {
|
||||
lines.push(`- ${item.clean ? '通过' : '失败'} ${item.priority} ${item.api}:${item.status || '未记录'}${item.implementationFile ? `;文件 ${item.implementationFile}` : ''}`);
|
||||
for (const problem of item.problems) lines.push(` - ${problem}`);
|
||||
}
|
||||
if (result.apiResults.length > 80) lines.push(`- 其余 ${result.apiResults.length - 80} 条略。`);
|
||||
}
|
||||
|
||||
if (result.matrixMissing.length) {
|
||||
lines.push('', '## Matrix 缺失项');
|
||||
for (const item of result.matrixMissing) lines.push(`- ${item}`);
|
||||
}
|
||||
if (result.stageFindings.length) {
|
||||
lines.push('', '## 阶段报告问题');
|
||||
for (const item of result.stageFindings) lines.push(`- ${item.file}:${item.problem}`);
|
||||
}
|
||||
if (result.runtimeConformance) {
|
||||
lines.push('', '## Trace-runtime 闭环');
|
||||
lines.push(`- 是否通过:${result.runtimeConformance.clean ? '是' : '否'}`);
|
||||
lines.push(`- 契约项:${result.runtimeConformance.summary.contractCount ?? 0}`);
|
||||
lines.push(`- 阻断差异:${result.runtimeConformance.summary.blockingMismatches ?? 0}`);
|
||||
}
|
||||
if (result.problems.length) {
|
||||
lines.push('', '## 问题');
|
||||
for (const problem of result.problems) lines.push(`- ${problem}`);
|
||||
}
|
||||
if (!result.problems.length) lines.push('', '## 结论', '- Trace API 覆盖矩阵检查通过。');
|
||||
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) 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