Files
proxycast/scripts/report-generated-slop.mjs
T

315 lines
8.4 KiB
JavaScript

#!/usr/bin/env node
import { execFileSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";
import {
buildGeneratedSlopReport,
renderGeneratedSlopMarkdown,
renderGeneratedSlopText,
} from "./lib/generated-slop-report-core.mjs";
import { buildLiveDocFreshnessReport } from "./check-doc-freshness.mjs";
import {
buildLegacySurfaceReport,
toSerializableLegacySurfaceReport,
} from "./report-legacy-surfaces.mjs";
const TREND_REPORT_PATH = "scripts/harness-eval-trend-report.mjs";
export {
buildGeneratedSlopReport,
renderGeneratedSlopMarkdown,
renderGeneratedSlopText,
} from "./lib/generated-slop-report-core.mjs";
function parseArgs(argv) {
const result = {
format: "text",
help: false,
docFreshnessInput: "",
legacyInput: "",
manifest: "",
outputJson: "",
outputMarkdown: "",
trendHistoryDir: "",
trendInput: "",
workspaceRoot: process.cwd(),
};
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === "--trend-input" && argv[index + 1]) {
result.trendInput = String(argv[index + 1]).trim();
index += 1;
continue;
}
if (arg === "--manifest" && argv[index + 1]) {
result.manifest = String(argv[index + 1]).trim();
index += 1;
continue;
}
if (arg === "--doc-freshness-input" && argv[index + 1]) {
result.docFreshnessInput = String(argv[index + 1]).trim();
index += 1;
continue;
}
if (arg === "--legacy-input" && argv[index + 1]) {
result.legacyInput = String(argv[index + 1]).trim();
index += 1;
continue;
}
if (arg === "--trend-history-dir" && argv[index + 1]) {
result.trendHistoryDir = String(argv[index + 1]).trim();
index += 1;
continue;
}
if (arg === "--workspace-root" && argv[index + 1]) {
result.workspaceRoot = String(argv[index + 1]).trim();
index += 1;
continue;
}
if (arg === "--format" && argv[index + 1]) {
result.format = String(argv[index + 1]).trim();
index += 1;
continue;
}
if (arg === "--output-json" && argv[index + 1]) {
result.outputJson = String(argv[index + 1]).trim();
index += 1;
continue;
}
if (arg === "--output-markdown" && argv[index + 1]) {
result.outputMarkdown = String(argv[index + 1]).trim();
index += 1;
continue;
}
if (arg === "--help" || arg === "-h") {
result.help = true;
}
}
return result;
}
function printHelp() {
console.log(`
Lime Harness Cleanup / Slop Report
用法:
node scripts/report-generated-slop.mjs
node scripts/report-generated-slop.mjs --manifest "./tmp/harness-evals.manifest.json"
node scripts/report-generated-slop.mjs --trend-input "./tmp/harness-eval-trend.json"
node scripts/report-generated-slop.mjs --doc-freshness-input "./tmp/doc-freshness.json"
node scripts/report-generated-slop.mjs --legacy-input "./tmp/legacy-surface-report.json"
node scripts/report-generated-slop.mjs --trend-history-dir "./artifacts/history"
node scripts/report-generated-slop.mjs --output-json "./tmp/harness-cleanup-report.json" --output-markdown "./tmp/harness-cleanup-report.md"
选项:
--manifest PATH 透传给 harness eval trend/runner,覆盖默认 manifest
--trend-input PATH 显式指定 harness eval trend JSON
--doc-freshness-input PATH 显式指定 doc freshness JSON
--legacy-input PATH 显式指定 governance / legacy surface report JSON
--trend-history-dir PATH 透传给 trend report,用历史 summary 生成趋势
--workspace-root PATH 未提供 trend-input 时,用该工作区生成当前 trend 报告
--format FMT 标准输出格式:text | json | markdown
--output-json PATH 将 cleanup/slop 报告 JSON 写入指定路径
--output-markdown PATH 将 cleanup/slop 报告 Markdown 写入指定路径
-h, --help 显示帮助
`);
}
function resolvePath(baseDir, targetPath) {
return path.resolve(baseDir, targetPath);
}
function ensureParentDirectory(filePath) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
}
function readJsonFile(filePath) {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}
function detectDefaultTrendHistoryDir(repoRoot) {
const candidates = [
resolvePath(repoRoot, "./.lime/harness/history"),
resolvePath(repoRoot, "./artifacts/history"),
];
for (const candidate of candidates) {
if (!fs.existsSync(candidate)) {
continue;
}
const hasJson = fs
.readdirSync(candidate, { withFileTypes: true })
.some((entry) => entry.isFile() && entry.name.endsWith(".json"));
if (hasJson) {
return candidate;
}
}
return "";
}
function buildTrendReport(repoRoot, options) {
if (options.trendInput) {
const resolvedPath = resolvePath(repoRoot, options.trendInput);
return {
report: readJsonFile(resolvedPath),
source: {
kind: "input-json",
path: resolvedPath,
},
};
}
const nodeCommand = process.execPath;
const trendScriptPath = resolvePath(repoRoot, TREND_REPORT_PATH);
const args = [trendScriptPath, "--format", "json"];
const effectiveHistoryDir =
options.trendHistoryDir || detectDefaultTrendHistoryDir(repoRoot);
if (options.manifest) {
args.push("--manifest", options.manifest);
}
if (effectiveHistoryDir) {
args.push("--history-dir", effectiveHistoryDir);
}
if (options.workspaceRoot) {
args.push("--workspace-root", options.workspaceRoot);
}
const output = execFileSync(nodeCommand, args, {
cwd: repoRoot,
encoding: "utf8",
stdio: ["ignore", "pipe", "inherit"],
});
return {
report: JSON.parse(output),
source: {
kind: effectiveHistoryDir ? "generated-history" : "generated-current",
path: effectiveHistoryDir
? resolvePath(repoRoot, effectiveHistoryDir)
: "",
},
};
}
function buildGovernanceReport(repoRoot, options) {
if (options.legacyInput) {
const resolvedPath = resolvePath(repoRoot, options.legacyInput);
return {
report: readJsonFile(resolvedPath),
source: {
kind: "input-json",
path: resolvedPath,
},
};
}
return {
report: toSerializableLegacySurfaceReport(buildLegacySurfaceReport()),
source: {
kind: "live-scan",
path: "",
},
};
}
function buildDocFreshnessReport(repoRoot, options) {
if (options.docFreshnessInput) {
const resolvedPath = resolvePath(repoRoot, options.docFreshnessInput);
return {
report: readJsonFile(resolvedPath),
source: {
kind: "input-json",
path: resolvedPath,
},
};
}
return {
report: buildLiveDocFreshnessReport(repoRoot),
source: {
kind: "live-scan",
path: "",
},
};
}
function renderOutput(report, format) {
if (format === "json") {
return `${JSON.stringify(report, null, 2)}\n`;
}
if (format === "markdown") {
return renderGeneratedSlopMarkdown(report);
}
return renderGeneratedSlopText(report);
}
function runGeneratedSlopReportCli() {
const options = parseArgs(process.argv.slice(2));
if (options.help) {
printHelp();
return;
}
const repoRoot = process.cwd();
const trendResult = buildTrendReport(repoRoot, options);
const docFreshnessResult = buildDocFreshnessReport(repoRoot, options);
const governanceResult = buildGovernanceReport(repoRoot, options);
const report = buildGeneratedSlopReport({
repoRoot,
trendReport: trendResult.report,
docFreshnessReport: docFreshnessResult.report,
governanceReport: governanceResult.report,
sources: {
trend: trendResult.source,
docFreshness: docFreshnessResult.source,
governance: governanceResult.source,
},
});
if (options.outputJson) {
const targetPath = resolvePath(repoRoot, options.outputJson);
ensureParentDirectory(targetPath);
fs.writeFileSync(targetPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
}
if (options.outputMarkdown) {
const targetPath = resolvePath(repoRoot, options.outputMarkdown);
ensureParentDirectory(targetPath);
fs.writeFileSync(targetPath, renderGeneratedSlopMarkdown(report), "utf8");
}
process.stdout.write(renderOutput(report, options.format));
}
const isMainModule =
process.argv[1] &&
path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
if (isMainModule) {
runGeneratedSlopReportCli();
}