← back to Approvals Viewer
freshness-guard-cli.js
82 lines
#!/usr/bin/env node
'use strict';
// freshness-guard-cli.js — run the READ-ONLY approval-time freshness re-check.
//
// node freshness-guard-cli.js <memo.md> # one memo (path or queue basename)
// node freshness-guard-cli.js --all # every open memo in the queue
// node freshness-guard-cli.js --all --stale # only memos flagged STALE
// node freshness-guard-cli.js <memo> --json # machine-readable
//
// It PRINTS an advisory. It NEVER approves, rejects, edits, moves, or executes
// anything. A STALE line means "re-enumerate before you fire," not "blocked."
const fs = require('fs');
const path = require('path');
const G = require('./freshness-guard');
const args = process.argv.slice(2);
const asJson = args.includes('--json');
const staleOnly = args.includes('--stale');
const all = args.includes('--all');
const target = args.find((a) => !a.startsWith('--'));
function resolveMemo(t) {
if (!t) return null;
if (fs.existsSync(t)) return t;
const inQueue = path.join(G.defaultConfig().queueDir, t);
if (fs.existsSync(inQueue)) return inQueue;
return t; // let checkMemo report NOT_MEASURED
}
const BADGE = { PASS: 'PASS', WARN: 'WARN', FAIL: 'FAIL' };
function line(r) {
const parts = [`[${BADGE[r.status] || r.status}]`, r.verdict.padEnd(11), r.file];
if (r.verdict === 'STALE') parts.push(`— ${r.staleCount}/${r.targetCount} stale`);
else if (r.verdict === 'FRESH') parts.push(`— 0/${r.targetCount} touched`);
return parts.join(' ');
}
function detail(r) {
const out = [line(r)];
if (r.headline) out.push(' ' + r.headline);
if (r.reason) out.push(' ' + r.reason);
if (r.staleTargets) {
for (const s of r.staleTargets) {
out.push(` • ${s.type}:${s.target} [${s.worst}]`);
for (const h of s.hits.slice(0, 4)) {
out.push(` ↳ ${h.reason} — ${h.source}${h.ticket ? ' (' + h.ticket + ')' : ''} @ ${h.ts}`);
if (h.snippet) out.push(` "${h.snippet}"`);
}
}
}
if (r.reverifyHint) out.push(' RE-ENUMERATE (run this yourself, read-only): ' + r.reverifyHint);
return out.join('\n');
}
let reports;
if (all) reports = G.checkQueue();
else if (target) reports = [G.checkMemo(resolveMemo(target))];
else {
console.error('usage: freshness-guard-cli.js <memo.md> | --all [--stale] [--json]');
process.exit(2);
}
if (staleOnly) reports = reports.filter((r) => r.verdict === 'STALE');
if (asJson) {
console.log(JSON.stringify(all ? reports : reports[0], null, 2));
} else {
const stale = reports.filter((r) => r.verdict === 'STALE');
if (all) {
for (const r of reports.sort((a, b) => (a.verdict === 'STALE' ? -1 : 1) - (b.verdict === 'STALE' ? -1 : 1))) {
console.log(r.verdict === 'STALE' ? detail(r) : line(r));
}
console.log(`\n${reports.length} memos checked · ${stale.length} STALE (advisory — re-enumerate before firing those).`);
} else {
console.log(detail(reports[0]));
}
}
// exit 0 always: advisory tool, a STALE finding is not a failure of the guard.
process.exit(0);