#!/usr/bin/env bun import { existsSync, readdirSync, readFileSync } from 'node:fs' import { dirname, join, relative } from 'node:path' import { fileURLToPath } from 'node:url' const DOCS_DIR = resolveDocsDir() const EXCLUDED_DIRS = new Set(['archive', 'research']) function resolveDocsDir() { const env = process.env.DOCS_DIR?.trim() if (env) return env const fromCwd = join(process.cwd(), 'docs') if (existsSync(fromCwd)) return fromCwd const docsListFile = fileURLToPath(import.meta.url) const docsListDir = dirname(docsListFile) return join(docsListDir, '..', 'docs') } function compactStrings(values: unknown[]): string[] { const result: string[] = [] for (const value of values) { if (value === null || value === undefined) continue const normalized = String(value).trim() if (normalized.length > 0) result.push(normalized) } return result } function walkMarkdownFiles(dir: string, base: string = dir): string[] { const entries = readdirSync(dir, { withFileTypes: true }) const files: string[] = [] for (const entry of entries) { if (entry.name.startsWith('.')) continue const fullPath = join(dir, entry.name) if (entry.isDirectory()) { if (EXCLUDED_DIRS.has(entry.name)) continue files.push(...walkMarkdownFiles(fullPath, base)) continue } if (entry.isFile() && entry.name.endsWith('.md')) { files.push(relative(base, fullPath)) } } return files.sort((a, b) => a.localeCompare(b)) } function extractMetadata(fullPath: string): { summary: string | null readWhen: string[] error?: string } { const content = readFileSync(fullPath, 'utf8') if (!content.startsWith('---')) { return { summary: null, readWhen: [], error: 'missing front matter' } } const endIndex = content.indexOf('\n---', 3) if (endIndex === -1) { return { summary: null, readWhen: [], error: 'unterminated front matter' } } const frontMatter = content.slice(3, endIndex).trim() const lines = frontMatter.split('\n') let summaryLine: string | null = null const readWhen: string[] = [] let collectingField: 'read_when' | null = null for (const rawLine of lines) { const line = rawLine.trim() if (line.startsWith('summary:')) { summaryLine = line collectingField = null continue } if (line.startsWith('read_when:')) { collectingField = 'read_when' const inline = line.slice('read_when:'.length).trim() if (inline.startsWith('[') && inline.endsWith(']')) { try { const parsed = JSON.parse(inline.replace(/'/g, '"')) as unknown if (Array.isArray(parsed)) { readWhen.push(...compactStrings(parsed)) } } catch { // ignore malformed inline arrays } } continue } if (collectingField === 'read_when') { if (line.startsWith('- ')) { const hint = line.slice(2).trim() if (hint) readWhen.push(hint) } else if (line === '') { // ignore } else { collectingField = null } } } if (!summaryLine) { return { summary: null, readWhen, error: 'summary key missing' } } const summaryValue = summaryLine.slice('summary:'.length).trim() const normalized = summaryValue .replace(/^['"]|['"]$/g, '') .replace(/\s+/g, ' ') .trim() if (!normalized) { return { summary: null, readWhen, error: 'summary is empty' } } return { summary: normalized, readWhen } } console.log('Listing all markdown files in docs folder:') const markdownFiles = walkMarkdownFiles(DOCS_DIR) for (const relativePath of markdownFiles) { const fullPath = join(DOCS_DIR, relativePath) const { summary, readWhen, error } = extractMetadata(fullPath) if (summary) { console.log(`${relativePath} - ${summary}`) if (readWhen.length > 0) { console.log(` Read when: ${readWhen.join('; ')}`) } } else { const reason = error ? ` - [${error}]` : '' console.log(`${relativePath}${reason}`) } } console.log( '\nReminder: keep docs up to date as behavior changes. When your task matches any "Read when" hint above, read that doc before coding, and suggest new coverage when it is missing.', )