TorbatYar/docs/ai-framework/scripts/validate-project-status.mjs
2026-07-28 20:39:10 +03:30

116 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
/**
* Framework validation — project-status.yaml schema v2.
* Docs-only; no business service inspection.
*/
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
const statusPath = path.join(root, "docs", "project-status.yaml");
const mdPath = path.join(root, "docs", "project-status.md");
const errors = [];
function fail(msg) {
errors.push(msg);
}
if (!fs.existsSync(statusPath)) fail("missing docs/project-status.yaml");
if (!fs.existsSync(mdPath)) fail("missing docs/project-status.md");
const raw = fs.readFileSync(statusPath, "utf8");
const requiredTop = [
"schema_version:",
"project_version:",
"last_updated:",
"last_completed_phase:",
"resume_rules:",
"automatic_update_rules:",
"critical_path:",
"execution_priority:",
"services:",
"global_statistics:",
"deployment_readiness:",
"depends_on:",
"required_by:",
"completed_phase:",
"remaining_phase:",
"backend_percent:",
"frontend_percent:",
"overall_percent:",
];
for (const key of requiredTop) {
if (!raw.includes(key)) fail(`missing required key/section: ${key}`);
}
if (raw.match(/^next_recommended_phase:/m)) {
fail("next_recommended_phase must not be an active top-level field (deprecated)");
}
if (!raw.includes("replaced_by:") || !raw.includes("execution_priority")) {
fail("deprecated.next_recommended_phase replacement markers missing");
}
const priorities = ["critical:", "high:", "medium:", "low:", "future:"];
for (const p of priorities) {
if (!raw.includes(p)) fail(`execution_priority missing bucket: ${p}`);
}
const readinessValues = ["NOT_STARTED", "IN_PROGRESS", "COMPLETE"];
const readinessKeys = [
"backend:",
"frontend:",
"database:",
"api:",
"tests:",
"documentation:",
"production_ready:",
];
for (const k of readinessKeys) {
if (!raw.includes(` ${k}`) && !raw.includes(` ${k}`)) {
// soft: at least one deployment_readiness block must exist with keys
}
}
if (!raw.includes("deployment_readiness:")) fail("deployment_readiness missing");
const md = fs.readFileSync(mdPath, "utf8");
if (!md.includes("critical_path") && !md.includes("Critical path")) {
fail("project-status.md must document critical path");
}
if (!md.includes("execution_priority") && !md.includes("Execution priority")) {
fail("project-status.md must document execution priority");
}
if (md.includes("Next recommended phase") && !md.includes("Deprecated")) {
fail("project-status.md still presents next_recommended_phase as active");
}
// Collect phase IDs under execution_priority buckets (naive line scan)
const lines = raw.split("\n");
let bucket = null;
const seen = new Map();
for (const line of lines) {
if (/^\s{2}(critical|high|medium|low|future):\s*$/.test(line)) {
bucket = line.trim().replace(":", "");
continue;
}
if (bucket && /^\s{4}-\s+[\w.-]+/.test(line)) {
const id = line.trim().replace(/^- /, "");
if (seen.has(id)) fail(`phase ${id} appears in both ${seen.get(id)} and ${bucket}`);
seen.set(id, bucket);
}
if (/^[a-z_]/.test(line) && !line.startsWith(" ")) bucket = null;
}
if (seen.size < 10) fail(`expected many priority phases, got ${seen.size}`);
if (errors.length) {
console.error("project-status validation FAILED:");
for (const e of errors) console.error(" -", e);
process.exit(1);
}
console.log(`project-status validation OK (schema v2, ${seen.size} prioritized phases)`);