Files
paperclip-docs/scripts/sync/backfill-screenshots.mjs
Aron PrinsandClaude Opus 4.7 c29d44a92e Add sync helper scripts and address deferred gotchas
Closes the five deferred items from the prior gotcha audit:

A. Link checker — scripts/sync/lint-links.mjs walks docs/**/*.md and
   verifies every relative markdown/image link resolves to an existing
   file. Skips external URLs, absolute SPA routes (/...), URI schemes
   (mailto:, agent://, etc.), and template placeholders. Found 3 real
   pre-existing broken links to missing screenshots — not fixed in
   this commit, but the tool will now block any new ones.

B. Nav verification — scripts/sync/verify-nav.mjs cross-checks
   site/content.json against the docs filesystem. Dangling entries
   (in nav but missing on disk) are errors; orphans (on disk but not
   in nav) are warnings. Found 1 real orphan (require-board-approval-
   before-spend.md from c0ff148 — never registered in content.json)
   that the user should triage separately.

C. Screenshot registry backfill — scripts/sync/backfill-screenshots.mjs
   scaffolds entries for every PNG under docs/user-guides/screenshots/.
   Run once; registry.json now ships with 274 scaffolded entries
   (137 light + 137 dark). Each has placeholder `depends_on: []` and
   a `_todo` field; humans fill in parent-repo path anchors over time
   to activate staleness detection per-entry. Re-runs preserve any
   entry with non-empty depends_on.

D. content.json edit serialisation — SKILL Phase 5 now requires
   subagents to return a structured `nav_addition` object alongside
   page content rather than editing site/content.json directly. The
   orchestrator merges all nav_additions on the main thread after
   subagents finish. Prevents the parallel-write race where two
   subagents creating new pages clobber each other's nav entries.

E. gh compare truncation — SKILL Phase 2 now explicitly documents
   the GitHub compare endpoint's ~300-file/~300-commit cap and the
   window-splitting fallback (walk intermediate release tags for
   release mode; binary-split commit list for nightly mode).

Also wires the scripts into package.json (sync:lint-links,
sync:verify-nav, sync:check, sync:backfill-screenshots) and into
SKILL Phase 7 (sync:check runs before commit; dangling/broken-link
failures abort the run).

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-13 10:37:42 +02:00

58 lines
2.2 KiB
JavaScript

#!/usr/bin/env node
// One-shot scaffolder for docs/user-guides/screenshots/registry.json.
//
// Walks every PNG under docs/user-guides/screenshots/{light,dark} and produces a registry entry
// with placeholder `depends_on: []` and a `_todo` note. Humans fill in the depends_on parent paths
// over time — until that happens, staleness detection for that entry is a no-op.
//
// Re-runs are safe: existing entries with non-empty depends_on are preserved verbatim.
//
// Usage: node scripts/sync/backfill-screenshots.mjs
import { readdirSync, readFileSync, writeFileSync, statSync } from "node:fs";
import { join, resolve, relative } from "node:path";
const ROOT = resolve(new URL("../..", import.meta.url).pathname);
const SHOT_DIR = join(ROOT, "docs/user-guides/screenshots");
const REGISTRY = join(SHOT_DIR, "registry.json");
function walk(dir, acc = []) {
for (const entry of readdirSync(dir)) {
const p = join(dir, entry);
const s = statSync(p);
if (s.isDirectory()) walk(p, acc);
else if (entry.endsWith(".png")) acc.push(p);
}
return acc;
}
const existing = JSON.parse(readFileSync(REGISTRY, "utf8"));
const preserve = new Map();
for (const e of existing.entries ?? []) {
if (Array.isArray(e.depends_on) && e.depends_on.length > 0) preserve.set(e.file, e);
}
const files = walk(SHOT_DIR).map((p) => relative(SHOT_DIR, p)).sort();
const entries = files.map((file) => {
if (preserve.has(file)) return preserve.get(file);
const theme = file.startsWith("dark/") ? "dark" : "light";
return {
file,
theme,
viewport: "1440x900",
route: null,
captured_against: null,
captured_sha: null,
depends_on: [],
_todo: "Fill in `route` (URL path captured) and `depends_on` (parent-repo paths whose changes invalidate this screenshot). Until depends_on is populated, staleness detection is a no-op for this entry."
};
});
const out = { ...existing, entries };
writeFileSync(REGISTRY, JSON.stringify(out, null, 2) + "\n");
const scaffolded = entries.filter((e) => !preserve.has(e.file)).length;
const preserved = entries.length - scaffolded;
console.log(`Wrote ${entries.length} entries to ${relative(ROOT, REGISTRY)} (${scaffolded} scaffolded, ${preserved} preserved).`);