this repo has no description
at main 119 lines 3.7 kB view raw
1#!/usr/bin/env node 2import { readdirSync, readFileSync } from 'node:fs'; 3import { dirname, join, relative } from 'node:path'; 4import { fileURLToPath } from 'node:url'; 5 6const DOCS_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'docs'); 7const EXCLUDED_DIRS = new Set(['archive', 'research']); 8 9function walkMarkdownFiles(dir, base = dir) { 10 const entries = readdirSync(dir, { withFileTypes: true }); 11 const files = []; 12 13 for (const entry of entries) { 14 if (entry.name.startsWith('.')) continue; 15 const fullPath = join(dir, entry.name); 16 17 if (entry.isDirectory()) { 18 if (EXCLUDED_DIRS.has(entry.name)) continue; 19 files.push(...walkMarkdownFiles(fullPath, base)); 20 } else if (entry.isFile() && entry.name.endsWith('.md')) { 21 files.push(relative(base, fullPath)); 22 } 23 } 24 25 return files.sort((a, b) => a.localeCompare(b)); 26} 27 28function extractMetadata(fullPath) { 29 const content = readFileSync(fullPath, 'utf8'); 30 31 if (!content.startsWith('---')) { 32 return { summary: null, readWhen: [], error: 'missing front matter' }; 33 } 34 35 const endIndex = content.indexOf('\n---', 3); 36 if (endIndex === -1) { 37 return { summary: null, readWhen: [], error: 'unterminated front matter' }; 38 } 39 40 const frontMatter = content.slice(3, endIndex).trim(); 41 const lines = frontMatter.split('\n'); 42 43 let summaryLine = null; 44 const readWhen = []; 45 let collectingReadWhen = false; 46 47 for (const rawLine of lines) { 48 const line = rawLine.trim(); 49 50 if (line.startsWith('summary:')) { 51 summaryLine = line; 52 collectingReadWhen = false; 53 continue; 54 } 55 56 if (line.startsWith('read_when:')) { 57 collectingReadWhen = true; 58 const inline = line.slice('read_when:'.length).trim(); 59 if (inline.startsWith('[') && inline.endsWith(']')) { 60 try { 61 const parsed = JSON.parse(inline.replace(/'/g, '"')); 62 if (Array.isArray(parsed)) { 63 parsed 64 .map((v) => String(v).trim()) 65 .filter(Boolean) 66 .forEach((v) => readWhen.push(v)); 67 } 68 } catch { 69 /* ignore malformed inline */ 70 } 71 } 72 continue; 73 } 74 75 if (collectingReadWhen) { 76 if (line.startsWith('- ')) { 77 const hint = line.slice(2).trim(); 78 if (hint) readWhen.push(hint); 79 } else if (line === '') { 80 // allow blank spacer lines inside list 81 } else { 82 collectingReadWhen = false; 83 } 84 } 85 } 86 87 if (!summaryLine) { 88 return { summary: null, readWhen, error: 'summary key missing' }; 89 } 90 91 const summaryValue = summaryLine.slice('summary:'.length).trim(); 92 const normalized = summaryValue.replace(/^['"]|['"]$/g, '').replace(/\s+/g, ' ').trim(); 93 94 if (!normalized) { 95 return { summary: null, readWhen, error: 'summary is empty' }; 96 } 97 98 return { summary: normalized, readWhen }; 99} 100 101console.log('Listing all markdown files in docs folder:'); 102 103const markdownFiles = walkMarkdownFiles(DOCS_DIR); 104 105for (const relativePath of markdownFiles) { 106 const fullPath = join(DOCS_DIR, relativePath); 107 const { summary, readWhen, error } = extractMetadata(fullPath); 108 if (summary) { 109 console.log(`${relativePath} - ${summary}`); 110 if (readWhen.length > 0) { 111 console.log(` Read when: ${readWhen.join('; ')}`); 112 } 113 } else { 114 const reason = error ? ` - [${error}]` : ''; 115 console.log(`${relativePath}${reason}`); 116 } 117} 118 119console.log('\nReminder: keep docs up to date as behavior changes. When your task matches any "Read when" hint above (React hooks, cache directives, database work, tests, etc.), read that doc before coding, and suggest new coverage when it is missing.');