clawhub/scripts/docs-list.ts
Val Alexander deb592d4ce
docs: update repository guidelines and improve formatting across multiple files
- Enhanced AGENTS.md with clearer project structure and development commands.
- Updated CHANGELOG.md to reflect recent fixes and additions.
- Improved formatting in CONTRIBUTING.md for better readability.
- Adjusted package.json and configuration files for consistent command structure.
- Refined README.md and VISION.md for clarity and organization.
- Standardized code formatting in various TypeScript files for consistency.

These changes aim to enhance documentation clarity and maintainability across the repository.
2026-03-18 21:56:01 -05:00

149 lines
4.2 KiB
TypeScript

#!/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.',
);