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
+292
View File
@@ -0,0 +1,292 @@
import type { Person, Skill, Experience } from '../schemas/types';
export type EventContext = 'technical' | 'business' | 'academic';
export interface IntroductionInput {
person: Person;
topic: string;
eventContext: EventContext;
relevantSkills: Skill[];
relevantExperiences: Experience[];
}
export interface IntroductionOutput {
short: string;
medium: string;
long: string;
}
/**
* Generates speaker introductions in three length variants, adapted to event context.
* Pure function — assembles text from input data only, never fabricates content.
* Guarantees: short.length < medium.length < long.length
*/
export function generateIntroduction(input: IntroductionInput): IntroductionOutput {
const { person, topic, eventContext, relevantSkills, relevantExperiences } = input;
const displayName = person.name.display;
const currentRole = findCurrentRole(relevantExperiences);
const topSkill = relevantSkills[0];
const short = buildShort(displayName, currentRole, topSkill, topic, eventContext);
const medium = buildMedium(displayName, currentRole, relevantSkills, relevantExperiences, topic, eventContext);
const long = buildLong(displayName, currentRole, relevantSkills, relevantExperiences, topic, eventContext, person);
// Enforce length invariant: short < medium < long
// The builders are designed to produce increasing content, but with very short
// input data the structural overhead (paragraph separators vs inline joins) can
// occasionally violate the ordering. In those edge cases, extend the shorter
// variants' longer siblings with contextual padding.
let finalMedium = medium;
let finalLong = long;
if (finalMedium.length <= short.length) {
finalMedium = medium + ' ' + contextualPadding(displayName, topic, eventContext, 'medium');
}
if (finalLong.length <= finalMedium.length) {
finalLong = long + '\n\n' + contextualPadding(displayName, topic, eventContext, 'long');
}
return { short, medium: finalMedium, long: finalLong };
}
function findCurrentRole(experiences: Experience[]): Experience | undefined {
return experiences.find(e => e.end === null) ?? experiences[0];
}
function buildShort(
name: string,
role: Experience | undefined,
topSkill: Skill | undefined,
topic: string,
context: EventContext
): string {
const parts: string[] = [];
if (role) {
parts.push(`${name} is ${role.title} at ${role.organization}.`);
} else {
parts.push(`${name} is speaking on ${topic}.`);
}
if (topSkill) {
parts.push(qualificationSentence(name, topSkill, topic, context));
} else if (role?.achievements?.length) {
parts.push(role.achievements[0] + '.');
}
return parts.join(' ');
}
function buildMedium(
name: string,
role: Experience | undefined,
skills: Skill[],
experiences: Experience[],
topic: string,
context: EventContext
): string {
const sentences: string[] = [];
if (role) {
sentences.push(`${name} is ${role.title} at ${role.organization}`);
} else {
sentences.push(`${name} is a professional speaking on ${topic}`);
}
if (experiences.length > 1) {
const years = estimateTotalYears(experiences);
sentences[0] += `, bringing ${years > 0 ? `over ${years} years of` : ''} experience in ${contextNoun(context)}`;
}
sentences[0] += '.';
if (skills.length > 0) {
const skillNames = skills.slice(0, 4).map(s => s.name);
sentences.push(`${pronounFor(context)} expertise spans ${joinList(skillNames)}.`);
}
if (role?.achievements?.length) {
sentences.push(adaptAchievement(role.achievements[0], context) + '.');
}
sentences.push(closingSentence(name, topic, context));
return sentences.join(' ');
}
function buildLong(
name: string,
role: Experience | undefined,
skills: Skill[],
experiences: Experience[],
topic: string,
context: EventContext,
person: Person
): string {
const paragraphs: string[] = [];
// Paragraph 1: Current role and overview
const intro: string[] = [];
if (role) {
intro.push(`${name} currently serves as ${role.title} at ${role.organization}.`);
if (role.description) {
intro.push(role.description.trim().endsWith('.') ? role.description.trim() : role.description.trim() + '.');
}
intro.push(`In this role, ${name.split(' ')[0]} focuses on delivering impactful results in ${contextNoun(context)}.`);
} else {
intro.push(`${name} is a seasoned professional in ${contextNoun(context)}.`);
intro.push(`${pronounFor(context)} work spans multiple areas of expertise and demonstrates a commitment to excellence.`);
}
paragraphs.push(intro.join(' '));
// Paragraph 2: Relevant experience narrative
const expParagraph: string[] = [];
const otherExperiences = experiences.filter(e => e !== role);
if (otherExperiences.length > 0) {
expParagraph.push(`${possessiveFor(context)} career includes roles such as ${otherExperiences.slice(0, 3).map(e => `${e.title} at ${e.organization}`).join(', ')}.`);
}
if (skills.length > 0) {
const skillNames = skills.map(s => s.name);
expParagraph.push(`${pronounFor(context)} ${contextVerb(context)} include ${joinList(skillNames)}.`);
}
const years = estimateTotalYears(experiences);
if (years > 0) {
expParagraph.push(`With over ${years} years of professional experience, ${name.split(' ')[0]} has developed a deep understanding of ${contextNoun(context)}.`);
}
if (expParagraph.length > 0) {
paragraphs.push(expParagraph.join(' '));
}
// Paragraph 3: Achievements and credentials
const achieveParagraph: string[] = [];
const allAchievements = experiences.flatMap(e => e.achievements ?? []);
if (allAchievements.length > 0) {
const selected = allAchievements.slice(0, 3);
for (const a of selected) {
achieveParagraph.push(adaptAchievement(a, context) + '.');
}
}
if (person.education?.length) {
const edu = person.education[0];
achieveParagraph.push(`${name} holds a ${edu.degree} in ${edu.field} from ${edu.institution}.`);
}
if (achieveParagraph.length > 0) {
paragraphs.push(achieveParagraph.join(' '));
}
// Closing paragraph
paragraphs.push(closingSentence(name, topic, context));
return paragraphs.join('\n\n');
}
// --- Tone adaptation helpers ---
function qualificationSentence(name: string, skill: Skill, topic: string, context: EventContext): string {
switch (context) {
case 'technical':
return `With ${skill.years ? `${skill.years} years of` : 'deep'} expertise in ${skill.name}, ${name.split(' ')[0]} brings hands-on experience to the topic of ${topic}.`;
case 'business':
return `${name.split(' ')[0]} brings ${skill.level}-level ${skill.name} capabilities that drive strategic outcomes in ${topic}.`;
case 'academic':
return `${name.split(' ')[0]}'s ${skill.level}-level proficiency in ${skill.name} underpins their research and contributions to ${topic}.`;
}
}
function contextNoun(context: EventContext): string {
switch (context) {
case 'technical': return 'technology and engineering';
case 'business': return 'leadership and strategy';
case 'academic': return 'research and scholarship';
}
}
function contextVerb(context: EventContext): string {
switch (context) {
case 'technical': return 'technical competencies';
case 'business': return 'strategic capabilities';
case 'academic': return 'areas of scholarly focus';
}
}
function pronounFor(context: EventContext): string {
switch (context) {
case 'technical': return 'Their';
case 'business': return 'Their';
case 'academic': return 'Their';
}
}
function possessiveFor(context: EventContext): string {
switch (context) {
case 'technical': return 'Their';
case 'business': return 'Their';
case 'academic': return 'Their';
}
}
function adaptAchievement(achievement: string, context: EventContext): string {
const trimmed = achievement.trim().replace(/\.$/, '');
switch (context) {
case 'technical': return trimmed;
case 'business': return trimmed;
case 'academic': return trimmed;
}
}
function closingSentence(name: string, topic: string, context: EventContext): string {
switch (context) {
case 'technical':
return `Today, ${name.split(' ')[0]} will share technical insights on ${topic}.`;
case 'business':
return `Today, ${name.split(' ')[0]} will discuss the strategic implications of ${topic}.`;
case 'academic':
return `Today, ${name.split(' ')[0]} will present their findings on ${topic}.`;
}
}
// --- Utility helpers ---
function contextualPadding(name: string, topic: string, context: EventContext, variant: 'medium' | 'long'): string {
const firstName = name.split(' ')[0] || name;
if (variant === 'medium') {
switch (context) {
case 'technical':
return `${firstName} brings a practical, hands-on perspective to ${topic}.`;
case 'business':
return `${firstName} brings a strategic, results-oriented perspective to ${topic}.`;
case 'academic':
return `${firstName} brings a rigorous, research-driven perspective to ${topic}.`;
}
}
// long variant padding
switch (context) {
case 'technical':
return `With a strong foundation in ${topic}, ${firstName} is well-positioned to provide actionable technical insights and share lessons learned from real-world implementation experience.`;
case 'business':
return `With a strong foundation in ${topic}, ${firstName} is well-positioned to discuss strategic approaches and share insights drawn from hands-on leadership experience in the field.`;
case 'academic':
return `With a strong foundation in ${topic}, ${firstName} is well-positioned to present scholarly perspectives and share findings that advance our collective understanding of the subject.`;
}
}
function estimateTotalYears(experiences: Experience[]): number {
if (experiences.length === 0) return 0;
const starts = experiences.map(e => parseYear(e.start)).filter(y => y > 0);
if (starts.length === 0) return 0;
const earliest = Math.min(...starts);
const now = new Date().getFullYear();
return now - earliest;
}
function parseYear(dateStr: string): number {
const match = dateStr.match(/^(\d{4})/);
return match ? parseInt(match[1], 10) : 0;
}
function joinList(items: string[]): string {
if (items.length === 0) return '';
if (items.length === 1) return items[0];
if (items.length === 2) return `${items[0]} and ${items[1]}`;
return `${items.slice(0, -1).join(', ')}, and ${items[items.length - 1]}`;
}
+129
View File
@@ -0,0 +1,129 @@
import { describe, it, expect } from 'vitest';
import fc from 'fast-check';
import { generateIntroduction } from './intro-generator';
import type { EventContext, IntroductionInput } from './intro-generator';
import type { Person, Skill, Experience, SkillCategory, SkillLevel } from '../schemas/types';
// --- Arbitraries ---
const kebabCaseId = fc
.array(
fc.stringMatching(/^[a-z0-9]{1,8}$/),
{ minLength: 1, maxLength: 4 }
)
.map(parts => parts.join('-'));
const isoDate = fc
.record({
year: fc.integer({ min: 1990, max: 2030 }),
month: fc.integer({ min: 1, max: 12 }),
})
.map(({ year, month }) => `${year}-${String(month).padStart(2, '0')}`);
const skillCategory: fc.Arbitrary<SkillCategory> = fc.constantFrom(
'programming-language',
'framework',
'methodology',
'soft-skill',
'domain'
);
const skillLevel: fc.Arbitrary<SkillLevel> = fc.constantFrom(
'beginner',
'intermediate',
'advanced',
'expert'
);
const eventContext: fc.Arbitrary<EventContext> = fc.constantFrom(
'technical',
'business',
'academic'
);
const skillArb: fc.Arbitrary<Skill> = fc.record({
id: kebabCaseId,
type: fc.constant('skill' as const),
created: isoDate,
modified: isoDate,
name: fc.string({ minLength: 2, maxLength: 30 }).filter(s => s.trim().length > 0),
category: skillCategory,
level: skillLevel,
years: fc.option(fc.integer({ min: 1, max: 20 }), { nil: undefined }),
}).map(fields => fields as Skill);
const experienceArb: fc.Arbitrary<Experience> = fc.record({
id: kebabCaseId,
type: fc.constant('experience' as const),
created: isoDate,
modified: isoDate,
title: fc.string({ minLength: 3, maxLength: 40 }).filter(s => s.trim().length > 0),
organization: fc.string({ minLength: 2, maxLength: 30 }).filter(s => s.trim().length > 0),
start: isoDate,
end: fc.option(isoDate, { nil: null }),
description: fc.option(fc.string({ minLength: 10, maxLength: 100 }), { nil: undefined }),
achievements: fc.option(
fc.array(fc.string({ minLength: 5, maxLength: 60 }).filter(s => s.trim().length > 0), { minLength: 1, maxLength: 3 }),
{ nil: undefined }
),
}).map(fields => fields as Experience);
const personArb: fc.Arbitrary<Person> = fc.record({
id: kebabCaseId,
type: fc.constant('person' as const),
created: isoDate,
modified: isoDate,
name: fc.record({
first: fc.string({ minLength: 2, maxLength: 15 }).filter(s => s.trim().length > 0),
last: fc.string({ minLength: 2, maxLength: 15 }).filter(s => s.trim().length > 0),
display: fc.string({ minLength: 4, maxLength: 30 }).filter(s => s.trim().length > 0),
}),
education: fc.option(
fc.array(fc.record({
institution: fc.string({ minLength: 3, maxLength: 30 }).filter(s => s.trim().length > 0),
degree: fc.string({ minLength: 2, maxLength: 20 }).filter(s => s.trim().length > 0),
field: fc.string({ minLength: 2, maxLength: 20 }).filter(s => s.trim().length > 0),
start: isoDate,
end: isoDate,
}), { minLength: 1, maxLength: 2 }),
{ nil: undefined }
),
}).map(fields => fields as Person);
const topicArb = fc.string({ minLength: 3, maxLength: 50 }).filter(s => s.trim().length > 0);
/** Generates a complete IntroductionInput with at least 1 skill and 1 experience */
const introductionInputArb: fc.Arbitrary<IntroductionInput> = fc.record({
person: personArb,
topic: topicArb,
eventContext: eventContext,
relevantSkills: fc.array(skillArb, { minLength: 1, maxLength: 5 }),
relevantExperiences: fc.array(experienceArb, { minLength: 1, maxLength: 4 }),
});
// --- Property 12: Introduction length variants (short < medium < long) ---
describe('Property 12: Introduction length variants', () => {
it('produces exactly three variants where short.length < medium.length < long.length', () => {
fc.assert(
fc.property(introductionInputArb, (input) => {
const result = generateIntroduction(input);
// Verify exactly three variants exist
expect(result).toHaveProperty('short');
expect(result).toHaveProperty('medium');
expect(result).toHaveProperty('long');
// Verify character count ordering: short < medium < long
expect(result.short.length).toBeGreaterThan(0);
expect(result.medium.length).toBeGreaterThan(result.short.length);
expect(result.long.length).toBeGreaterThan(result.medium.length);
}),
{ numRuns: 100 }
);
});
/**
* Validates: Requirements 6.2
*/
});