#!/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();