Migrate all repos into monorepo context folders
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.
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user