Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)
Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
126 lines
3.9 KiB
TypeScript
126 lines
3.9 KiB
TypeScript
/**
|
|
* Entity file management functions for the knowledge base.
|
|
*
|
|
* Maps entity types to directory paths, reads/writes/lists/deletes entity YAML files.
|
|
* Uses fs/promises for all file operations.
|
|
*/
|
|
|
|
import { readFile, writeFile, readdir, unlink, access, mkdir } from 'node:fs/promises';
|
|
import { join } from 'node:path';
|
|
import type { Entity, EntityType } from '../schemas/types';
|
|
import { serializeEntity, deserializeEntity } from './yaml-utils';
|
|
import { validateEntity } from '../schemas/validate';
|
|
|
|
/**
|
|
* Maps entity types to their kb/ subdirectory names.
|
|
*/
|
|
const ENTITY_TYPE_DIRS: Record<EntityType, string> = {
|
|
person: 'profiles',
|
|
experience: 'experiences',
|
|
skill: 'skills',
|
|
organization: 'organizations',
|
|
project: 'projects',
|
|
certification: 'certifications',
|
|
tandem: 'tandems',
|
|
};
|
|
|
|
/**
|
|
* Returns the directory path for a given entity type.
|
|
*/
|
|
export function getEntityDir(type: EntityType, basePath: string = process.cwd()): string {
|
|
const dir = ENTITY_TYPE_DIRS[type];
|
|
return join(basePath, 'kb', dir);
|
|
}
|
|
|
|
/**
|
|
* Constructs the full file path for an entity given its type and ID.
|
|
*/
|
|
export function getEntityPath(type: EntityType, id: string, basePath: string = process.cwd()): string {
|
|
return join(getEntityDir(type, basePath), `${id}.yaml`);
|
|
}
|
|
|
|
/**
|
|
* Reads an entity from its YAML file.
|
|
* Throws a descriptive error if the file is not found.
|
|
*/
|
|
export async function readEntity(type: EntityType, id: string, basePath: string = process.cwd()): Promise<Entity> {
|
|
const filePath = getEntityPath(type, id, basePath);
|
|
let content: string;
|
|
try {
|
|
content = await readFile(filePath, 'utf-8');
|
|
} catch (err: unknown) {
|
|
if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
throw new Error(`Entity not found: ${type} "${id}" (expected at ${filePath})`);
|
|
}
|
|
throw err;
|
|
}
|
|
return deserializeEntity(content);
|
|
}
|
|
|
|
/**
|
|
* Writes an entity to its YAML file.
|
|
* Validates the entity before writing; rejects invalid entities with validation errors.
|
|
*/
|
|
export async function writeEntity(entity: Entity, basePath: string = process.cwd()): Promise<void> {
|
|
const result = validateEntity(entity);
|
|
if (!result.valid) {
|
|
throw new Error(`Validation failed for entity "${entity.id}": ${result.errors.join('; ')}`);
|
|
}
|
|
|
|
const filePath = getEntityPath(entity.type, entity.id, basePath);
|
|
const dir = getEntityDir(entity.type, basePath);
|
|
|
|
await mkdir(dir, { recursive: true });
|
|
const yaml = serializeEntity(entity);
|
|
await writeFile(filePath, yaml, 'utf-8');
|
|
}
|
|
|
|
/**
|
|
* Lists all entity IDs in a given type directory.
|
|
* Returns an array of entity IDs (file names without .yaml extension).
|
|
*/
|
|
export async function listEntities(type: EntityType, basePath: string = process.cwd()): Promise<string[]> {
|
|
const dir = getEntityDir(type, basePath);
|
|
let files: string[];
|
|
try {
|
|
files = await readdir(dir);
|
|
} catch (err: unknown) {
|
|
if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
return [];
|
|
}
|
|
throw err;
|
|
}
|
|
return files
|
|
.filter((f) => f.endsWith('.yaml'))
|
|
.map((f) => f.replace(/\.yaml$/, ''));
|
|
}
|
|
|
|
/**
|
|
* Deletes an entity file.
|
|
* Throws a descriptive error if the file is not found.
|
|
*/
|
|
export async function deleteEntity(type: EntityType, id: string, basePath: string = process.cwd()): Promise<void> {
|
|
const filePath = getEntityPath(type, id, basePath);
|
|
try {
|
|
await unlink(filePath);
|
|
} catch (err: unknown) {
|
|
if (err instanceof Error && 'code' in err && (err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
throw new Error(`Entity not found: ${type} "${id}" (expected at ${filePath})`);
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if an entity file exists.
|
|
*/
|
|
export async function entityExists(type: EntityType, id: string, basePath: string = process.cwd()): Promise<boolean> {
|
|
const filePath = getEntityPath(type, id, basePath);
|
|
try {
|
|
await access(filePath);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|