Files
firefrost-services/scripts/backfill-task-descriptions.js

154 lines
4.7 KiB
JavaScript

#!/usr/bin/env node
/**
* Task #163 — Task Description Hygiene Pass
*
* Reads archived task markdown files from the ops manual, strips YAML
* frontmatter, and emits a SQL file with UPDATE statements to backfill
* descriptions for tasks in the `tasks` table.
*
* Run from Nitro (local) — outputs SQL for Michael to apply on dev panel.
*
* Usage:
* node scripts/backfill-task-descriptions.js
*
* Output:
* scripts/out/backfill-task-descriptions.sql
* scripts/out/backfill-task-descriptions-report.md
*/
const fs = require('fs');
const path = require('path');
const ARCHIVE_DIR = path.resolve(
__dirname,
'..',
'..',
'firefrost-operations-manual',
'docs',
'archive',
'tasks-index-archived-2026-04-11'
);
const OUT_DIR = path.resolve(__dirname, 'out');
// 16 target tasks with empty descriptions (from REQ-2026-04-14-task-description-hygiene)
const TARGETS = [
{ num: 22, title: 'Netdata Deployment' },
{ num: 23, title: 'Department Structure & Access Control' },
{ num: 32, title: 'Terraria Branding Arc' },
{ num: 48, title: 'n8n Rebuild' },
{ num: 49, title: 'NotebookLM Integration' },
{ num: 51, title: 'Ignis Protocol' },
{ num: 81, title: 'Memorial Writing Assistant' },
{ num: 89, title: 'DERP Protocol Review' },
{ num: 97, title: 'Trinity Console Social Hub' },
{ num: 99, title: 'Multi-Lineage Claude Architecture' },
{ num: 100, title: 'Skill Index & Recommender System' },
{ num: 104, title: 'Server-Side Mod Deployment Automation' },
{ num: 105, title: 'Trinity Console Review Workflow' },
{ num: 106, title: 'Minecraft Log Analyzer Bot' },
{ num: 113, title: 'Claude Projects Architecture' }
];
function stripFrontmatter(raw) {
// YAML frontmatter is the first block between --- ... ---
if (!raw.startsWith('---')) return raw.trim();
const end = raw.indexOf('\n---', 3);
if (end < 0) return raw.trim();
return raw.slice(end + 4).replace(/^\s*\n/, '').trim();
}
function sqlEscape(s) {
// Postgres dollar-quoted string — safest for markdown bodies
// Use a tag unlikely to appear in content
return `$body$${s}$body$`;
}
function indexArchive() {
if (!fs.existsSync(ARCHIVE_DIR)) {
console.error(`Archive dir not found: ${ARCHIVE_DIR}`);
process.exit(1);
}
const files = fs.readdirSync(ARCHIVE_DIR).filter(f => /^task-\d+-.+\.md$/.test(f));
const byNum = new Map();
for (const f of files) {
const m = f.match(/^task-(\d+)-/);
if (!m) continue;
byNum.set(parseInt(m[1], 10), f);
}
return byNum;
}
function main() {
fs.mkdirSync(OUT_DIR, { recursive: true });
const archiveIndex = indexArchive();
const matched = [];
const missing = [];
for (const t of TARGETS) {
const file = archiveIndex.get(t.num);
if (!file) {
missing.push(t);
continue;
}
const raw = fs.readFileSync(path.join(ARCHIVE_DIR, file), 'utf8');
const body = stripFrontmatter(raw);
matched.push({ ...t, file, body });
}
// Emit SQL
const sqlLines = [
'-- Task #163 — Task Description Hygiene Pass',
'-- Generated: ' + new Date().toISOString(),
'-- Source: firefrost-operations-manual/docs/archive/tasks-index-archived-2026-04-11/',
`-- Matched: ${matched.length}/${TARGETS.length} Missing: ${missing.length}`,
'',
'BEGIN;',
''
];
for (const m of matched) {
sqlLines.push(`-- Task #${m.num}: ${m.title} (from ${m.file})`);
sqlLines.push(
`UPDATE tasks SET description = ${sqlEscape(m.body)}, updated_at = NOW() ` +
`WHERE task_number = ${m.num} AND (description IS NULL OR description = '');`
);
sqlLines.push('');
}
sqlLines.push('COMMIT;', '');
const sqlPath = path.join(OUT_DIR, 'backfill-task-descriptions.sql');
fs.writeFileSync(sqlPath, sqlLines.join('\n'));
// Emit report
const report = [
'# Task #163 — Backfill Report',
'',
`**Generated:** ${new Date().toISOString()}`,
`**Matched:** ${matched.length}/${TARGETS.length}`,
`**Missing:** ${missing.length}`,
'',
'## Matched (SQL UPDATE emitted)',
'',
'| # | Title | Source file |',
'|---|-------|-------------|',
...matched.map(m => `| ${m.num} | ${m.title} | \`${m.file}\` |`),
'',
'## Missing (no archive file — needs manual description)',
'',
missing.length === 0
? '_(none)_'
: ['| # | Title |', '|---|-------|', ...missing.map(m => `| ${m.num} | ${m.title} |`)].join('\n'),
''
].join('\n');
const reportPath = path.join(OUT_DIR, 'backfill-task-descriptions-report.md');
fs.writeFileSync(reportPath, report);
console.log(`Matched ${matched.length}/${TARGETS.length}. Missing ${missing.length}.`);
console.log(`SQL: ${sqlPath}`);
console.log(`Report: ${reportPath}`);
}
main();