feat: add reviews content collection
Move all 24 review entries from skills-review/catalog.js +
skills-review/submitted-catalog.js into a typed Astro content collection at
src/content/reviews/. Each entry is a Markdown file with frontmatter for the
review metadata (id, author, focus, wins, improve, extras, name, description)
and a body that holds the 'improved' SKILL.md content.
Re-point scripts/build-skill-review.mjs at the new collection. The generator
reads each .md file, parses its YAML frontmatter, and writes
skill-reviews/improved/{id}/SKILL.md in the same shape the legacy catalog
produced — verified byte-identical via 'git diff --exit-code skill-reviews/'.
The 'name' field is preserved separately from 'id' because two entries
renamed the skill during review (id angular-accessibility-root → name
angular-accessibility; id confectionary-skill-hub → name confectionery-orders).
Without it the generator output would drift on those two files.
Does not yet delete skills-review/catalog.js or submitted-catalog.js —
verify.mjs and the legacy review-desk page both still read them, so they
stay as a mirror until task 16 rewires the page to the collection. Adding a
new submission today requires editing both the .md file (new source of
truth) and the legacy catalog.js (until task 16).
Done-when:
- 24 entries under src/content/reviews/ ✓
- verify.mjs's id:' count assertion still passes ✓
- git diff --exit-code skill-reviews/ clean after regenerating ✓
- astro check passes (22 files: 0 errors, 0 warnings, 2 hints) ✓
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,68 @@
|
||||
import { mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { catalog } from '../skills-review/catalog.js';
|
||||
// Regenerate skill-reviews/improved/**/SKILL.md from the content collection.
|
||||
//
|
||||
// Source of truth: src/content/reviews/{id}.md. Each file's frontmatter holds
|
||||
// the review metadata (id, author, focus, wins, improve, extras,
|
||||
// description); its body is the Markdown that becomes the SKILL.md file.
|
||||
// This script reconstructs the SKILL.md frontmatter (name + description) and
|
||||
// concatenates it with the body, so the committed output stays byte-identical
|
||||
// to what the legacy catalog produced:
|
||||
//
|
||||
// --- <- SKILL.md frontmatter starts
|
||||
// name: <id>
|
||||
// description: <description>
|
||||
// ---
|
||||
//
|
||||
// # <id>
|
||||
// <- body, as written in the .md file
|
||||
// ...
|
||||
//
|
||||
// Run from the repository root:
|
||||
//
|
||||
// node scripts/build-skill-review.mjs && git diff --exit-code skill-reviews/
|
||||
//
|
||||
// A non-empty diff means the collection drifted from the committed output;
|
||||
// regenerate after every entry edit.
|
||||
|
||||
for (const entry of catalog) {
|
||||
const output = join('skill-reviews', 'improved', entry.id, 'SKILL.md');
|
||||
mkdirSync(dirname(output), { recursive: true });
|
||||
writeFileSync(output, entry.improved);
|
||||
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const root = dirname(here);
|
||||
const REVIEWS_DIR = join(root, 'src', 'content', 'reviews');
|
||||
const OUTPUT_DIR = join(root, 'skill-reviews', 'improved');
|
||||
|
||||
const parseFrontmatter = (text) => {
|
||||
// Strict shape: starts with `---\n`, ends at the next `---\n`, then body.
|
||||
if (!text.startsWith('---\n')) throw new Error('missing leading ---');
|
||||
const end = text.indexOf('\n---\n', 4);
|
||||
if (end === -1) throw new Error('missing closing ---');
|
||||
const yaml = text.slice(4, end);
|
||||
const body = text.slice(end + 5);
|
||||
const id = yaml.match(/^id:\s*"?([^"\n]+)"?\s*$/m)?.[1];
|
||||
const name = yaml.match(/^name:\s*"?([^"\n]+)"?\s*$/m)?.[1];
|
||||
const description = yaml.match(/^description:\s*"?((?:\\.|[^"\\])*)"?\s*$/m)?.[1];
|
||||
if (!id) throw new Error('frontmatter missing id');
|
||||
if (!name) throw new Error('frontmatter missing name');
|
||||
if (!description) throw new Error('frontmatter missing description');
|
||||
return { id, name, description, body };
|
||||
};
|
||||
|
||||
const files = readdirSync(REVIEWS_DIR)
|
||||
.filter((f) => f.endsWith('.md'))
|
||||
.sort();
|
||||
let count = 0;
|
||||
for (const file of files) {
|
||||
const source = readFileSync(join(REVIEWS_DIR, file), 'utf8');
|
||||
const { id, name, description, body } = parseFrontmatter(source);
|
||||
// The body in the .md file starts with `# <name>\n\n` then the Markdown
|
||||
// content; strip a single leading newline if present so the reconstructed
|
||||
// SKILL.md matches the legacy `skill(...)` output exactly.
|
||||
const stripped = body.startsWith('\n') ? body.slice(1) : body;
|
||||
const output = `---\nname: ${name}\ndescription: ${description}\n---\n\n${stripped.trim()}\n`;
|
||||
const outPath = join(OUTPUT_DIR, id, 'SKILL.md');
|
||||
mkdirSync(dirname(outPath), { recursive: true });
|
||||
writeFileSync(outPath, output);
|
||||
count += 1;
|
||||
}
|
||||
console.log(`wrote ${catalog.length} improved skill drafts`);
|
||||
console.log(`wrote ${count} improved skill drafts`);
|
||||
|
||||
Reference in New Issue
Block a user