Files
factory/lib/wiki-graph/src/corpus.ts
vitya ca669d96e1 feat(lib): bundle MCP servers from .common/lib into factory/lib/
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>
2026-06-11 13:17:29 +03:00

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');
}