Stage 2 завершён: problem-first ответы и follow-up continuity - ассистент переведён от entity-heavy логики к problem-first ответам с problem-unit слоем, удержанием контекста в follow-up и очисткой пользовательского ответа от сырых технических ссылок.
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
#!/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();
|
||||
Reference in New Issue
Block a user