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:
2026-06-30 20:39:52 +02:00
parent 2f2b295531
commit a5f8fb49ab
1717 changed files with 447332 additions and 0 deletions
+201
View File
@@ -0,0 +1,201 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtemp, rm } from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import type { Skill, Person } from '../schemas/types';
import {
getEntityDir,
getEntityPath,
readEntity,
writeEntity,
listEntities,
deleteEntity,
entityExists,
} from './entity-files';
describe('entity-files', () => {
let tempDir: string;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), 'kb-test-'));
});
afterEach(async () => {
await rm(tempDir, { recursive: true, force: true });
});
describe('getEntityDir', () => {
it('maps person to kb/profiles/', () => {
expect(getEntityDir('person', '/base')).toBe(join('/base', 'kb', 'profiles'));
});
it('maps experience to kb/experiences/', () => {
expect(getEntityDir('experience', '/base')).toBe(join('/base', 'kb', 'experiences'));
});
it('maps skill to kb/skills/', () => {
expect(getEntityDir('skill', '/base')).toBe(join('/base', 'kb', 'skills'));
});
it('maps organization to kb/organizations/', () => {
expect(getEntityDir('organization', '/base')).toBe(join('/base', 'kb', 'organizations'));
});
it('maps project to kb/projects/', () => {
expect(getEntityDir('project', '/base')).toBe(join('/base', 'kb', 'projects'));
});
it('maps certification to kb/certifications/', () => {
expect(getEntityDir('certification', '/base')).toBe(join('/base', 'kb', 'certifications'));
});
it('maps tandem to kb/tandems/', () => {
expect(getEntityDir('tandem', '/base')).toBe(join('/base', 'kb', 'tandems'));
});
});
describe('getEntityPath', () => {
it('constructs correct file path', () => {
expect(getEntityPath('skill', 'python', '/base')).toBe(join('/base', 'kb', 'skills', 'python.yaml'));
});
});
describe('writeEntity + readEntity round-trip', () => {
const skill: Skill = {
id: 'typescript',
type: 'skill',
created: '2025-01-15',
modified: '2025-01-15',
name: 'TypeScript',
category: 'programming-language',
level: 'expert',
years: 5,
};
it('writes and reads back an entity', async () => {
await writeEntity(skill, tempDir);
const result = await readEntity('skill', 'typescript', tempDir);
expect(result).toEqual(skill);
});
it('rejects invalid entities on write', async () => {
const invalid = { id: 'bad', type: 'skill', created: '2025-01-15', modified: '2025-01-15' } as unknown as Skill;
await expect(writeEntity(invalid, tempDir)).rejects.toThrow('Validation failed');
});
});
describe('readEntity errors', () => {
it('throws descriptive error when entity not found', async () => {
await expect(readEntity('skill', 'nonexistent', tempDir)).rejects.toThrow(
/Entity not found: skill "nonexistent"/
);
});
});
describe('listEntities', () => {
it('returns empty array for empty/missing directory', async () => {
const ids = await listEntities('skill', tempDir);
expect(ids).toEqual([]);
});
it('lists entity IDs from directory', async () => {
const skill1: Skill = {
id: 'python',
type: 'skill',
created: '2025-01-15',
modified: '2025-01-15',
name: 'Python',
category: 'programming-language',
level: 'expert',
};
const skill2: Skill = {
id: 'rust',
type: 'skill',
created: '2025-01-15',
modified: '2025-01-15',
name: 'Rust',
category: 'programming-language',
level: 'intermediate',
};
await writeEntity(skill1, tempDir);
await writeEntity(skill2, tempDir);
const ids = await listEntities('skill', tempDir);
expect(ids.sort()).toEqual(['python', 'rust']);
});
});
describe('deleteEntity', () => {
it('deletes an existing entity file', async () => {
const skill: Skill = {
id: 'go-lang',
type: 'skill',
created: '2025-01-15',
modified: '2025-01-15',
name: 'Go',
category: 'programming-language',
level: 'advanced',
};
await writeEntity(skill, tempDir);
expect(await entityExists('skill', 'go-lang', tempDir)).toBe(true);
await deleteEntity('skill', 'go-lang', tempDir);
expect(await entityExists('skill', 'go-lang', tempDir)).toBe(false);
});
it('throws descriptive error when entity not found', async () => {
await expect(deleteEntity('skill', 'nonexistent', tempDir)).rejects.toThrow(
/Entity not found: skill "nonexistent"/
);
});
});
describe('entityExists', () => {
it('returns false for non-existent entity', async () => {
expect(await entityExists('person', 'nobody', tempDir)).toBe(false);
});
it('returns true for existing entity', async () => {
const person: Person = {
id: 'andre-knie',
type: 'person',
created: '2025-01-15',
modified: '2025-09-01',
name: { first: 'Andre', last: 'Knie', display: 'Andre Knie' },
};
await writeEntity(person, tempDir);
expect(await entityExists('person', 'andre-knie', tempDir)).toBe(true);
});
});
describe('multiple profiles coexist (Req 1.5)', () => {
it('supports multiple person profiles in the same KB', async () => {
const person1: Person = {
id: 'andre-knie',
type: 'person',
created: '2025-01-15',
modified: '2025-09-01',
name: { first: 'Andre', last: 'Knie', display: 'Andre Knie' },
};
const person2: Person = {
id: 'claudia-froldi',
type: 'person',
created: '2025-01-15',
modified: '2025-09-01',
name: { first: 'Claudia', last: 'Froldi', display: 'Claudia Froldi' },
};
await writeEntity(person1, tempDir);
await writeEntity(person2, tempDir);
const ids = await listEntities('person', tempDir);
expect(ids.sort()).toEqual(['andre-knie', 'claudia-froldi']);
const read1 = await readEntity('person', 'andre-knie', tempDir);
const read2 = await readEntity('person', 'claudia-froldi', tempDir);
expect(read1.id).toBe('andre-knie');
expect(read2.id).toBe('claudia-froldi');
});
});
});