#!/usr/bin/env node /** * @agent-manifest * name: approve * title: Draft Approver * class: plumbing * trigger: manual [--force] * description: Approve a draft: SEO gate, then promote it into the blog, build, go live. No LLM. * model: - * prompt: - * skills: - * tools: lib/publish.mjs * reads: - * writes: - * created: 2026-07-04 * @end */ // Manually approve a draft: SEO gate, then promote it into the blog, build, go live. // Usage: node agents/scripts/approve.mjs [--force] import { existsSync } from "node:fs"; import { dirname, resolve, join } from "node:path"; import { fileURLToPath } from "node:url"; import { loadJson } from "./lib/util.mjs"; import { promoteDraft } from "./lib/publish.mjs"; import { runSeoReview } from "./seo-review.mjs"; const HERE = dirname(fileURLToPath(import.meta.url)); const PIPELINE = resolve(HERE, ".."); const cfg = loadJson(join(PIPELINE, "config.json")); const root = cfg.paths.projectRoot; const args = process.argv.slice(2); const force = args.includes("--force"); const slug = args.find((a) => !a.startsWith("--")); if (!slug) { console.error("Usage: approve.mjs [--force]"); process.exit(1); } const draftDir = join(root, cfg.paths.draftsDir, slug); if (!existsSync(join(draftDir, "index.mdx"))) { console.error(`[approve] no draft at ${join(draftDir, "index.mdx")}`); process.exit(1); } // SEO Specialist gate. if (cfg.seo?.blockApproveOnFail && !force) { const auditPath = join(draftDir, "seo-review.json"); let audit = existsSync(auditPath) ? loadJson(auditPath) : null; if (!audit) { console.log("[approve] no SEO audit found — running the SEO Specialist now…"); audit = await runSeoReview(slug, { published: false }); } if (!audit) { console.error("[approve] could not obtain an SEO audit. Re-run, or use --force to override."); process.exit(1); } const minScore = cfg.seo.minScore ?? 85; if (audit.verdict === "fail" || (audit.overall ?? 0) < minScore) { console.error( `[approve] BLOCKED by SEO gate: ${audit.verdict} · ${audit.overall}/${minScore} min.\n` + (audit.blocking?.length ? " blocking:\n - " + audit.blocking.join("\n - ") + "\n" : "") + ` Review ${auditPath}, revise the draft, then re-approve (or use --force to override).` ); process.exit(1); } console.log(`[approve] SEO gate passed: ${audit.verdict} · ${audit.overall}`); } try { console.log("[approve] publishing…"); const { url, taxonomyAdded } = await promoteDraft(cfg, slug); if (taxonomyAdded.length) console.log(`[approve] registered in blog-data.js: ${taxonomyAdded.join(", ")}`); console.log(`[approve] LIVE → ${url}`); } catch (e) { console.error(`[approve] ${e.message}`); process.exit(1); }