275 lines
7.2 KiB
JavaScript
275 lines
7.2 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
|
|
function parseArgs(argv) {
|
|
const args = {
|
|
clean: false,
|
|
label: "",
|
|
dryRun: false
|
|
};
|
|
for (let i = 0; i < argv.length; i += 1) {
|
|
const token = argv[i];
|
|
if (token === "--clean") {
|
|
args.clean = true;
|
|
continue;
|
|
}
|
|
if (token === "--dry-run") {
|
|
args.dryRun = true;
|
|
continue;
|
|
}
|
|
if (token === "--label") {
|
|
args.label = String(argv[i + 1] ?? "").trim();
|
|
i += 1;
|
|
}
|
|
}
|
|
return args;
|
|
}
|
|
|
|
function sanitizeLabel(value) {
|
|
return value
|
|
.trim()
|
|
.replace(/\s+/g, "_")
|
|
.replace(/[^a-zA-Z0-9_-]/g, "")
|
|
.slice(0, 48);
|
|
}
|
|
|
|
function formatTimestamp(date) {
|
|
const pad = (v) => String(v).padStart(2, "0");
|
|
return [
|
|
date.getFullYear(),
|
|
pad(date.getMonth() + 1),
|
|
pad(date.getDate())
|
|
].join("-")
|
|
+ "_"
|
|
+ [pad(date.getHours()), pad(date.getMinutes()), pad(date.getSeconds())].join("-");
|
|
}
|
|
|
|
function ensureDir(dirPath) {
|
|
fs.mkdirSync(dirPath, { recursive: true });
|
|
}
|
|
|
|
function listFilesRecursively(rootDir) {
|
|
if (!fs.existsSync(rootDir)) {
|
|
return [];
|
|
}
|
|
const output = [];
|
|
const stack = [rootDir];
|
|
while (stack.length > 0) {
|
|
const current = stack.pop();
|
|
if (!current) continue;
|
|
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
const full = path.join(current, entry.name);
|
|
if (entry.isDirectory()) {
|
|
stack.push(full);
|
|
} else if (entry.isFile()) {
|
|
output.push(full);
|
|
}
|
|
}
|
|
}
|
|
return output;
|
|
}
|
|
|
|
function copyFilePreserveRelative(fromPath, baseDir, targetBaseDir) {
|
|
const rel = path.relative(baseDir, fromPath);
|
|
const target = path.join(targetBaseDir, rel);
|
|
ensureDir(path.dirname(target));
|
|
fs.copyFileSync(fromPath, target);
|
|
return target;
|
|
}
|
|
|
|
function copyDirectoryPreserveRelative(fromDir, baseDir, targetBaseDir) {
|
|
const files = listFilesRecursively(fromDir);
|
|
const copied = [];
|
|
for (const filePath of files) {
|
|
copied.push(copyFilePreserveRelative(filePath, baseDir, targetBaseDir));
|
|
}
|
|
return copied;
|
|
}
|
|
|
|
function removePath(targetPath) {
|
|
if (!fs.existsSync(targetPath)) {
|
|
return;
|
|
}
|
|
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
}
|
|
|
|
function pickReportEntries(reportsDir) {
|
|
if (!fs.existsSync(reportsDir)) {
|
|
return [];
|
|
}
|
|
|
|
const staticKeep = new Set([
|
|
"v2_0_1_clarification_eval_plan.md",
|
|
"v2_0_2_eval_plan.md",
|
|
"v2_pilot_eval_plan.md"
|
|
]);
|
|
|
|
const entries = [];
|
|
for (const entry of fs.readdirSync(reportsDir, { withFileTypes: true })) {
|
|
const full = path.join(reportsDir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
if (entry.name.startsWith("minimal_package_")) {
|
|
entries.push(full);
|
|
}
|
|
continue;
|
|
}
|
|
if (!entry.isFile()) {
|
|
continue;
|
|
}
|
|
if (staticKeep.has(entry.name)) {
|
|
continue;
|
|
}
|
|
|
|
const isGenerated =
|
|
entry.name.startsWith("assistant-stage1-")
|
|
|| entry.name.startsWith("assistant-compare-")
|
|
|| entry.name.startsWith("normalizer_eval_")
|
|
|| entry.name.startsWith("normalizer_v")
|
|
|| entry.name.startsWith("minimal_package_");
|
|
|
|
if (isGenerated) {
|
|
entries.push(full);
|
|
}
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
function pickLogFiles(logDir, filePattern) {
|
|
if (!fs.existsSync(logDir)) {
|
|
return [];
|
|
}
|
|
return fs
|
|
.readdirSync(logDir, { withFileTypes: true })
|
|
.filter((entry) => entry.isFile() && filePattern.test(entry.name))
|
|
.map((entry) => path.join(logDir, entry.name));
|
|
}
|
|
|
|
function writeJson(filePath, payload) {
|
|
ensureDir(path.dirname(filePath));
|
|
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf-8");
|
|
}
|
|
|
|
function main() {
|
|
const args = parseArgs(process.argv.slice(2));
|
|
const scriptFile = fileURLToPath(import.meta.url);
|
|
const root = path.resolve(path.dirname(scriptFile), "..");
|
|
const reportsDir = path.resolve(root, "reports");
|
|
const docsRunsDir = path.resolve(root, "docs", "runs");
|
|
const tracesDir = path.resolve(root, "data", "traces");
|
|
const sessionsDir = path.resolve(root, "data", "assistant_sessions");
|
|
|
|
const stamp = formatTimestamp(new Date());
|
|
const label = sanitizeLabel(args.label);
|
|
const runFolderName = label ? `${stamp}_${label}` : stamp;
|
|
const runDir = path.join(docsRunsDir, runFolderName);
|
|
|
|
const reportEntries = pickReportEntries(reportsDir);
|
|
const traceFiles = pickLogFiles(tracesDir, /\.json$/i);
|
|
const sessionFiles = pickLogFiles(sessionsDir, /\.json$/i);
|
|
|
|
const copiedReports = [];
|
|
const copiedLogs = [];
|
|
|
|
if (!args.dryRun) {
|
|
ensureDir(runDir);
|
|
ensureDir(path.join(runDir, "reports"));
|
|
ensureDir(path.join(runDir, "logs", "traces"));
|
|
ensureDir(path.join(runDir, "logs", "assistant_sessions"));
|
|
}
|
|
|
|
for (const sourcePath of reportEntries) {
|
|
const stat = fs.statSync(sourcePath);
|
|
if (args.dryRun) {
|
|
copiedReports.push(sourcePath);
|
|
continue;
|
|
}
|
|
if (stat.isDirectory()) {
|
|
copiedReports.push(...copyDirectoryPreserveRelative(sourcePath, reportsDir, path.join(runDir, "reports")));
|
|
} else {
|
|
copiedReports.push(copyFilePreserveRelative(sourcePath, reportsDir, path.join(runDir, "reports")));
|
|
}
|
|
}
|
|
|
|
for (const sourcePath of traceFiles) {
|
|
if (!args.dryRun) {
|
|
copiedLogs.push(copyFilePreserveRelative(sourcePath, tracesDir, path.join(runDir, "logs", "traces")));
|
|
} else {
|
|
copiedLogs.push(sourcePath);
|
|
}
|
|
}
|
|
|
|
for (const sourcePath of sessionFiles) {
|
|
if (!args.dryRun) {
|
|
copiedLogs.push(copyFilePreserveRelative(sourcePath, sessionsDir, path.join(runDir, "logs", "assistant_sessions")));
|
|
} else {
|
|
copiedLogs.push(sourcePath);
|
|
}
|
|
}
|
|
|
|
if (args.clean && !args.dryRun) {
|
|
for (const entryPath of reportEntries) {
|
|
removePath(entryPath);
|
|
}
|
|
for (const entryPath of traceFiles) {
|
|
removePath(entryPath);
|
|
}
|
|
for (const entryPath of sessionFiles) {
|
|
removePath(entryPath);
|
|
}
|
|
}
|
|
|
|
const manifest = {
|
|
schema_version: "run_artifact_manifest_v0_1",
|
|
run_folder: runDir,
|
|
generated_at: new Date().toISOString(),
|
|
options: {
|
|
clean: args.clean,
|
|
dry_run: args.dryRun,
|
|
label: label || null
|
|
},
|
|
counts: {
|
|
report_entries_found: reportEntries.length,
|
|
trace_logs_found: traceFiles.length,
|
|
session_logs_found: sessionFiles.length,
|
|
report_files_copied: copiedReports.length,
|
|
log_files_copied: copiedLogs.length
|
|
},
|
|
preserved_static_reports: [
|
|
"v2_0_1_clarification_eval_plan.md",
|
|
"v2_0_2_eval_plan.md",
|
|
"v2_pilot_eval_plan.md"
|
|
]
|
|
};
|
|
|
|
if (!args.dryRun) {
|
|
writeJson(path.join(runDir, "manifest.json"), manifest);
|
|
fs.writeFileSync(
|
|
path.join(runDir, "README.md"),
|
|
[
|
|
"# Run Artifact Bundle",
|
|
"",
|
|
`- generated_at: ${manifest.generated_at}`,
|
|
`- clean_mode: ${args.clean}`,
|
|
`- reports_copied: ${manifest.counts.report_files_copied}`,
|
|
`- logs_copied: ${manifest.counts.log_files_copied}`
|
|
].join("\n"),
|
|
"utf-8"
|
|
);
|
|
}
|
|
|
|
const summary = [
|
|
`run_folder=${runDir}`,
|
|
`report_entries_found=${reportEntries.length}`,
|
|
`trace_logs_found=${traceFiles.length}`,
|
|
`session_logs_found=${sessionFiles.length}`,
|
|
`clean_mode=${args.clean}`,
|
|
`dry_run=${args.dryRun}`
|
|
].join("\n");
|
|
process.stdout.write(`${summary}\n`);
|
|
}
|
|
|
|
main();
|