Copies source (no node_modules, dist, .tasks, .wiki, __pycache__) for: - projects-meta-mcp v2.25.0 (TypeScript/Node) - wiki-graph v0.3.1 (TypeScript/Node) - interns-mcp v0.3.3 (Python/FastMCP) .gitignore: exclude lib build artefacts (node_modules, dist, .venv, __pycache__, *.pyc) bootstrap.ps1: add MCP build step — npm install+build for TS servers, venv+pip for Python Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
60 lines
2.1 KiB
TypeScript
60 lines
2.1 KiB
TypeScript
import { readdirSync, statSync, readFileSync } from 'node:fs';
|
|
import { join, dirname, basename } from 'node:path';
|
|
|
|
export interface PageFile {
|
|
/** Page basename without `.md`, original case. */
|
|
slug: string;
|
|
/** Directory relative to corpus root, '' for root-level pages. POSIX-ish, using OS sep. */
|
|
dir: string;
|
|
/** Absolute path on disk. */
|
|
path: string;
|
|
/** Last-modified time in ms (for cache revalidation). */
|
|
mtimeMs: number;
|
|
}
|
|
|
|
/**
|
|
* Top-level dirs excluded from the graph by default. In the Karpathy wiki schema
|
|
* `raw/` (verbatim ingested docs) and `sources/` (provenance summaries) are NOT
|
|
* graph pages — they share basenames with canonical concept/entity pages and would
|
|
* collapse into them (false collisions) and inject provenance edges. `assets/` is
|
|
* binary. The graph models the canonical concept/entity network only.
|
|
*/
|
|
export const DEFAULT_EXCLUDE: readonly string[] = ['raw', 'sources', 'assets'];
|
|
|
|
export interface ListOptions {
|
|
/** Top-level dir names to skip. Defaults to {@link DEFAULT_EXCLUDE}; pass `[]` to index everything. */
|
|
exclude?: readonly string[];
|
|
}
|
|
|
|
function firstSegment(dir: string): string {
|
|
if (dir === '') return '';
|
|
return dir.split(/[\\/]/)[0];
|
|
}
|
|
|
|
/** List every `.md` file under `corpusDir` (recursive, minus excluded dirs), with mtime. */
|
|
export function listPages(corpusDir: string, opts: ListOptions = {}): PageFile[] {
|
|
const exclude = new Set(opts.exclude ?? DEFAULT_EXCLUDE);
|
|
const entries = readdirSync(corpusDir, { recursive: true }) as string[];
|
|
const pages: PageFile[] = [];
|
|
for (const rel of entries) {
|
|
if (!rel.endsWith('.md')) continue;
|
|
const dirRaw = dirname(rel);
|
|
const dir = dirRaw === '.' ? '' : dirRaw;
|
|
if (exclude.has(firstSegment(dir))) continue;
|
|
const abs = join(corpusDir, rel);
|
|
const st = statSync(abs);
|
|
if (!st.isFile()) continue;
|
|
pages.push({
|
|
slug: basename(rel, '.md'),
|
|
dir,
|
|
path: abs,
|
|
mtimeMs: st.mtimeMs,
|
|
});
|
|
}
|
|
return pages;
|
|
}
|
|
|
|
export function readPageContent(page: PageFile): string {
|
|
return readFileSync(page.path, 'utf8');
|
|
}
|