narrative-generator.js
1 #!/usr/bin/env node 2 3 /** 4 * Professional Narrative Generator 5 * 6 * Converts GitHub intelligence data into compelling, evidence-backed 7 * professional narratives for CV enhancement. Transforms raw data patterns 8 * into authentic professional stories with verifiable achievements. 9 * 10 * Features: 11 * - Evidence-backed professional summary generation 12 * - Technical expertise validation with concrete examples 13 * - Leadership and collaboration story extraction 14 * - Quantified achievement generation from actual data 15 * - Authentic skill progression narratives 16 * 17 * Usage: node narrative-generator.js 18 */ 19 20 const fs = require('fs').promises; 21 const path = require('path'); 22 23 /** 24 * Professional Narrative Generator 25 * 26 * Transforms GitHub intelligence data into compelling professional narratives 27 * with evidence-backed claims and authentic achievement stories 28 */ 29 class NarrativeGenerator { 30 constructor() { 31 this.intelligenceDir = '../../data/intelligence'; 32 this.outputDir = '../../data/narratives'; 33 this.narratives = { 34 professional_summary: null, 35 technical_achievements: [], 36 leadership_examples: [], 37 collaboration_stories: [], 38 skill_validations: [], 39 growth_narrative: null 40 }; 41 } 42 43 /** 44 * Generate comprehensive professional narratives 45 */ 46 async generateNarratives() { 47 console.log('📖 **PROFESSIONAL NARRATIVE GENERATION INITIATED**'); 48 console.log(''); 49 50 try { 51 // Ensure output directory exists 52 await fs.mkdir(this.outputDir, { recursive: true }); 53 54 // Load intelligence data 55 const intelligenceData = await this.loadIntelligenceData(); 56 57 if (!intelligenceData) { 58 console.log('⚠️ No intelligence data found. Run github-data-miner.js first.'); 59 return; 60 } 61 62 // Generate different types of narratives 63 console.log('📝 Generating professional summary narrative...'); 64 this.narratives.professional_summary = this.generateProfessionalSummary(intelligenceData); 65 66 console.log('🎯 Extracting technical achievement stories...'); 67 this.narratives.technical_achievements = this.extractTechnicalAchievements(intelligenceData); 68 69 console.log('🤝 Creating leadership and collaboration narratives...'); 70 this.narratives.leadership_examples = this.extractLeadershipExamples(intelligenceData); 71 this.narratives.collaboration_stories = this.extractCollaborationStories(intelligenceData); 72 73 console.log('✅ Generating skill validation evidence...'); 74 this.narratives.skill_validations = this.generateSkillValidations(intelligenceData); 75 76 console.log('📈 Creating professional growth narrative...'); 77 this.narratives.growth_narrative = this.generateGrowthNarrative(intelligenceData); 78 79 // Save narratives 80 await this.saveNarratives(); 81 82 console.log('✅ Professional narratives generated successfully'); 83 return this.narratives; 84 85 } catch (error) { 86 console.error('❌ Narrative generation failed:', error.message); 87 throw error; 88 } 89 } 90 91 /** 92 * Load latest intelligence data 93 */ 94 async loadIntelligenceData() { 95 try { 96 // Find the latest intelligence file 97 const files = await fs.readdir(this.intelligenceDir); 98 const intelligenceFiles = files.filter(f => f.startsWith('github-intelligence-') && f.endsWith('.json')); 99 100 if (intelligenceFiles.length === 0) { 101 return null; 102 } 103 104 // Sort by filename (which includes timestamp) and get latest 105 const latestFile = intelligenceFiles.sort().pop(); 106 const filePath = path.join(this.intelligenceDir, latestFile); 107 108 const content = await fs.readFile(filePath, 'utf8'); 109 const data = JSON.parse(content); 110 111 console.log(`📊 Loaded intelligence data from: ${latestFile}`); 112 return data; 113 114 } catch (error) { 115 console.warn('⚠️ Failed to load intelligence data:', error.message); 116 return null; 117 } 118 } 119 120 /** 121 * Generate evidence-backed professional summary 122 */ 123 generateProfessionalSummary(intelligence) { 124 const issues = intelligence.issues_intelligence || {}; 125 const commits = intelligence.commits_intelligence || {}; 126 const _prs = intelligence.prs_intelligence || {}; 127 128 const totalIssues = issues.summary_metrics?.total_issues_analyzed || 0; 129 const totalCommits = commits.summary_metrics?.total_commits_analyzed || 0; 130 const technicalDepth = issues.summary_metrics?.technical_depth_score || 0; 131 const collaborationScore = issues.summary_metrics?.collaboration_score || 0; 132 133 // Generate evidence-backed claims 134 const evidenceBasedClaims = []; 135 136 if (totalIssues > 10) { 137 evidenceBasedClaims.push(`contributed to technical discussions across ${totalIssues}+ GitHub issues`); 138 } 139 140 if (totalCommits > 20) { 141 evidenceBasedClaims.push(`authored ${totalCommits}+ commits demonstrating consistent development activity`); 142 } 143 144 if (technicalDepth > 60) { 145 evidenceBasedClaims.push(`demonstrates high technical depth with ${technicalDepth}% of contributions involving complex technical solutions`); 146 } 147 148 if (collaborationScore > 50) { 149 evidenceBasedClaims.push(`maintains ${collaborationScore}% collaboration effectiveness in community interactions`); 150 } 151 152 // Extract technical themes from commit patterns 153 const technicalThemes = this.extractTechnicalThemes(intelligence); 154 const problemSolvingExamples = this.extractProblemSolvingExamples(intelligence); 155 156 return { 157 enhanced_summary: this.constructProfessionalSummary(evidenceBasedClaims, technicalThemes), 158 evidence_sources: { 159 issues_analyzed: totalIssues, 160 commits_analyzed: totalCommits, 161 technical_depth_score: technicalDepth, 162 collaboration_score: collaborationScore 163 }, 164 supporting_data: { 165 technical_themes: technicalThemes, 166 problem_solving_examples: problemSolvingExamples.slice(0, 3) // Top 3 examples 167 } 168 }; 169 } 170 171 /** 172 * Construct professional summary from evidence 173 */ 174 constructProfessionalSummary(evidenceClaims, technicalThemes) { 175 const themeList = technicalThemes.slice(0, 3).join(', '); 176 const evidenceText = evidenceClaims.length > 0 ? evidenceClaims.join(', ') : 'demonstrates consistent technical contribution'; 177 178 return `AI Engineer and Software Architect with proven track record in ${themeList}. Evidence from GitHub activity shows I have ${evidenceText}. Specializes in creating intelligent solutions that bridge theoretical AI/ML concepts with production-ready implementations, with particular focus on autonomous systems and human-AI collaboration frameworks.`; 179 } 180 181 /** 182 * Extract technical themes from commit and issue patterns 183 */ 184 extractTechnicalThemes(intelligence) { 185 const themes = new Set(); 186 187 // Extract from commit messages 188 const commits = intelligence.commits_intelligence?.development_philosophy || []; 189 commits.forEach(commit => { 190 const message = commit.message?.toLowerCase() || ''; 191 if (message.includes('ai') || message.includes('ml') || message.includes('machine learning')) { 192 themes.add('machine learning'); 193 } 194 if (message.includes('api') || message.includes('backend') || message.includes('server')) { 195 themes.add('backend development'); 196 } 197 if (message.includes('frontend') || message.includes('ui') || message.includes('react')) { 198 themes.add('frontend development'); 199 } 200 if (message.includes('docker') || message.includes('deploy') || message.includes('ci')) { 201 themes.add('DevOps'); 202 } 203 if (message.includes('test') || message.includes('quality')) { 204 themes.add('software quality'); 205 } 206 if (message.includes('performance') || message.includes('optimize')) { 207 themes.add('performance optimization'); 208 } 209 }); 210 211 // Extract from issue discussions 212 const issues = intelligence.issues_intelligence?.technical_discussions || []; 213 issues.forEach(issue => { 214 const content = (issue.title + ' ' + issue.body_excerpt).toLowerCase(); 215 if (content.includes('architecture') || content.includes('design')) { 216 themes.add('software architecture'); 217 } 218 if (content.includes('automation') || content.includes('workflow')) { 219 themes.add('automation'); 220 } 221 if (content.includes('security')) { 222 themes.add('security'); 223 } 224 }); 225 226 return Array.from(themes); 227 } 228 229 /** 230 * Extract problem-solving examples from issues and commits 231 */ 232 extractProblemSolvingExamples(intelligence) { 233 const examples = []; 234 235 // From issue discussions 236 const issues = intelligence.issues_intelligence?.technical_discussions || []; 237 issues.forEach((issue, _index) => { 238 if (issue.complexity_level === 'high' || issue.technical_score >= 3) { 239 examples.push({ 240 type: 'issue_resolution', 241 repository: issue.repository, 242 title: issue.title, 243 complexity: issue.complexity_level, 244 technical_score: issue.technical_score, 245 evidence_link: `github.com/adrianwedd/${issue.repository}/issues/${issue.issue_number}` 246 }); 247 } 248 }); 249 250 // From commit patterns 251 const commits = intelligence.commits_intelligence?.development_philosophy || []; 252 commits.forEach(commit => { 253 if (commit.commit_type === 'bugfix' || commit.commit_type === 'refactor') { 254 examples.push({ 255 type: 'code_improvement', 256 repository: commit.repository, 257 message: commit.message, 258 commit_type: commit.commit_type, 259 philosophy_score: commit.philosophy_score, 260 evidence_link: `github.com/adrianwedd/${commit.repository}/commit/${commit.sha}` 261 }); 262 } 263 }); 264 265 return examples.sort((a, b) => (b.technical_score || b.philosophy_score || 0) - (a.technical_score || a.philosophy_score || 0)); 266 } 267 268 /** 269 * Extract technical achievements with evidence 270 */ 271 extractTechnicalAchievements(intelligence) { 272 const achievements = []; 273 274 const issues = intelligence.issues_intelligence?.summary_metrics || {}; 275 const commits = intelligence.commits_intelligence?.summary_metrics || {}; 276 const prs = intelligence.prs_intelligence?.summary_metrics || {}; 277 278 // Technical contribution achievements 279 if (issues.total_issues_analyzed > 0) { 280 achievements.push({ 281 achievement: `Contributed to ${issues.total_issues_analyzed} technical discussions`, 282 evidence_type: 'issue_participation', 283 impact_score: Math.min(100, issues.total_issues_analyzed * 2), 284 details: `Participated in technical discussions across multiple repositories, with ${issues.technical_depth_score}% involving complex technical solutions` 285 }); 286 } 287 288 if (commits.total_commits_analyzed > 0) { 289 achievements.push({ 290 achievement: `Authored ${commits.total_commits_analyzed} commits in recent period`, 291 evidence_type: 'development_activity', 292 impact_score: Math.min(100, commits.total_commits_analyzed), 293 details: 'Consistent development activity showing sustained contribution to codebase evolution' 294 }); 295 } 296 297 if (prs.reviews_given > 0) { 298 achievements.push({ 299 achievement: `Provided ${prs.reviews_given} code reviews`, 300 evidence_type: 'technical_leadership', 301 impact_score: prs.reviews_given * 5, 302 details: 'Demonstrates technical leadership through active participation in code review process' 303 }); 304 } 305 306 // Repository-specific achievements 307 const repoAchievements = this.extractRepositoryAchievements(intelligence); 308 achievements.push(...repoAchievements); 309 310 return achievements.sort((a, b) => b.impact_score - a.impact_score); 311 } 312 313 /** 314 * Extract repository-specific achievements 315 */ 316 extractRepositoryAchievements(intelligence) { 317 const achievements = []; 318 const repoData = new Map(); 319 320 // Aggregate data by repository 321 const issues = intelligence.issues_intelligence?.technical_discussions || []; 322 const commits = intelligence.commits_intelligence?.development_philosophy || []; 323 324 issues.forEach(issue => { 325 if (!repoData.has(issue.repository)) { 326 repoData.set(issue.repository, { issues: 0, commits: 0, complexity: [] }); 327 } 328 const data = repoData.get(issue.repository); 329 data.issues++; 330 data.complexity.push(issue.complexity_level); 331 }); 332 333 commits.forEach(commit => { 334 if (!repoData.has(commit.repository)) { 335 repoData.set(commit.repository, { issues: 0, commits: 0, complexity: [] }); 336 } 337 const data = repoData.get(commit.repository); 338 data.commits++; 339 }); 340 341 // Generate achievements for significant repositories 342 repoData.forEach((data, repo) => { 343 if (data.issues > 3 || data.commits > 10) { 344 const highComplexity = data.complexity.filter(c => c === 'high').length; 345 achievements.push({ 346 achievement: `Led technical development in ${repo} repository`, 347 evidence_type: 'repository_leadership', 348 impact_score: data.issues * 3 + data.commits + highComplexity * 5, 349 details: `${data.commits} commits, ${data.issues} technical discussions, ${highComplexity} high-complexity issues resolved`, 350 repository: repo 351 }); 352 } 353 }); 354 355 return achievements; 356 } 357 358 /** 359 * Extract leadership examples from PR reviews and issue interactions 360 */ 361 extractLeadershipExamples(intelligence) { 362 const examples = []; 363 364 const prData = intelligence.prs_intelligence || {}; 365 const reviewsGiven = prData.summary_metrics?.reviews_given || 0; 366 const mentoringExamples = prData.technical_mentoring || []; 367 368 if (reviewsGiven > 0) { 369 examples.push({ 370 leadership_type: 'code_review_leadership', 371 example: `Provided ${reviewsGiven} code reviews demonstrating technical leadership`, 372 evidence_strength: 'high', 373 impact: 'Helped maintain code quality and mentored team members through review process', 374 quantified_metrics: `${reviewsGiven} reviews across multiple repositories` 375 }); 376 } 377 378 // Extract specific mentoring examples 379 mentoringExamples.forEach(example => { 380 if (example.leadership_score > 0) { 381 examples.push({ 382 leadership_type: 'technical_mentoring', 383 example: `Provided technical guidance in ${example.repository} repository`, 384 evidence_strength: example.leadership_score > 2 ? 'high' : 'medium', 385 impact: example.review_excerpt, 386 repository: example.repository, 387 date: example.submitted_at 388 }); 389 } 390 }); 391 392 return examples; 393 } 394 395 /** 396 * Extract collaboration stories from issue comments and discussions 397 */ 398 extractCollaborationStories(intelligence) { 399 const stories = []; 400 401 const collaborationExamples = intelligence.issues_intelligence?.collaboration_examples || []; 402 const collaborationScore = intelligence.issues_intelligence?.summary_metrics?.collaboration_score || 0; 403 404 if (collaborationScore > 50) { 405 stories.push({ 406 story_type: 'community_collaboration', 407 narrative: `Maintains ${collaborationScore}% collaboration effectiveness in community interactions`, 408 evidence: `Consistently provides helpful responses in technical discussions`, 409 impact: 'Contributes to positive community environment and knowledge sharing' 410 }); 411 } 412 413 // Extract specific collaboration examples 414 collaborationExamples.forEach(example => { 415 if (example.mentoring_score > 0 || example.helpfulness_level === 'high') { 416 stories.push({ 417 story_type: 'technical_assistance', 418 narrative: `Provided technical assistance in ${example.repository}`, 419 evidence: example.comment_excerpt, 420 helpfulness_level: example.helpfulness_level, 421 response_type: example.response_type, 422 repository: example.repository 423 }); 424 } 425 }); 426 427 return stories; 428 } 429 430 /** 431 * Generate skill validations with evidence 432 */ 433 generateSkillValidations(intelligence) { 434 const validations = []; 435 const skillEvidence = new Map(); 436 437 // Analyze commit patterns for skill evidence 438 const commits = intelligence.commits_intelligence?.development_philosophy || []; 439 commits.forEach(commit => { 440 const message = commit.message?.toLowerCase() || ''; 441 const repo = commit.repository; 442 443 // Language/tech skill detection 444 if (message.includes('javascript') || message.includes('js') || message.includes('react')) { 445 this.addSkillEvidence(skillEvidence, 'JavaScript', repo, 'commit', commit.date); 446 } 447 if (message.includes('python') || message.includes('py')) { 448 this.addSkillEvidence(skillEvidence, 'Python', repo, 'commit', commit.date); 449 } 450 if (message.includes('docker') || message.includes('container')) { 451 this.addSkillEvidence(skillEvidence, 'Docker', repo, 'commit', commit.date); 452 } 453 if (message.includes('api') || message.includes('backend')) { 454 this.addSkillEvidence(skillEvidence, 'API Development', repo, 'commit', commit.date); 455 } 456 if (message.includes('test') || message.includes('testing')) { 457 this.addSkillEvidence(skillEvidence, 'Testing', repo, 'commit', commit.date); 458 } 459 }); 460 461 // Analyze issue discussions for skill evidence 462 const issues = intelligence.issues_intelligence?.technical_discussions || []; 463 issues.forEach(issue => { 464 const content = (issue.title + ' ' + issue.body_excerpt).toLowerCase(); 465 const repo = issue.repository; 466 467 if (content.includes('architecture') || content.includes('design')) { 468 this.addSkillEvidence(skillEvidence, 'Software Architecture', repo, 'issue', issue.created_at); 469 } 470 if (content.includes('performance') || content.includes('optimization')) { 471 this.addSkillEvidence(skillEvidence, 'Performance Optimization', repo, 'issue', issue.created_at); 472 } 473 if (content.includes('security')) { 474 this.addSkillEvidence(skillEvidence, 'Security', repo, 'issue', issue.created_at); 475 } 476 }); 477 478 // Convert evidence to validations 479 skillEvidence.forEach((evidence, skill) => { 480 if (evidence.count >= 2) { // Minimum evidence threshold 481 validations.push({ 482 skill: skill, 483 evidence_strength: evidence.count >= 5 ? 'strong' : evidence.count >= 3 ? 'medium' : 'basic', 484 evidence_count: evidence.count, 485 repositories: [...evidence.repositories], 486 evidence_types: [...evidence.types], 487 recent_usage: evidence.dates.sort().pop(), // Most recent 488 validation_summary: `${skill} skill demonstrated through ${evidence.count} instances across ${evidence.repositories.size} repositories` 489 }); 490 } 491 }); 492 493 return validations.sort((a, b) => b.evidence_count - a.evidence_count); 494 } 495 496 /** 497 * Helper method to add skill evidence 498 */ 499 addSkillEvidence(skillEvidence, skill, repository, type, date) { 500 if (!skillEvidence.has(skill)) { 501 skillEvidence.set(skill, { 502 count: 0, 503 repositories: new Set(), 504 types: new Set(), 505 dates: [] 506 }); 507 } 508 509 const evidence = skillEvidence.get(skill); 510 evidence.count++; 511 evidence.repositories.add(repository); 512 evidence.types.add(type); 513 evidence.dates.push(date); 514 } 515 516 /** 517 * Generate professional growth narrative 518 */ 519 generateGrowthNarrative(intelligence) { 520 const commits = intelligence.commits_intelligence?.development_philosophy || []; 521 const issues = intelligence.issues_intelligence?.technical_discussions || []; 522 523 // Analyze temporal patterns 524 const timelineData = this.analyzeGrowthTimeline(commits, issues); 525 526 return { 527 growth_summary: 'Demonstrates consistent professional development through increasing technical complexity and community engagement', 528 key_indicators: [ 529 'Steady increase in technical contribution complexity', 530 'Growing involvement in architectural discussions', 531 'Evolution from individual contributor to community collaborator' 532 ], 533 timeline_highlights: timelineData.highlights, 534 metrics_progression: timelineData.metrics, 535 future_trajectory: 'Positioned for senior technical leadership roles based on demonstrated growth patterns' 536 }; 537 } 538 539 /** 540 * Analyze growth timeline from data patterns 541 */ 542 analyzeGrowthTimeline(commits, issues) { 543 const highlights = []; 544 const metrics = {}; 545 546 // Simple timeline analysis (can be enhanced with more sophisticated date analysis) 547 if (commits.length > 0) { 548 highlights.push(`${commits.length} commits showing consistent development activity`); 549 } 550 551 if (issues.length > 0) { 552 const highComplexity = issues.filter(i => i.complexity_level === 'high').length; 553 if (highComplexity > 0) { 554 highlights.push(`${highComplexity} high-complexity technical discussions`); 555 } 556 } 557 558 // Calculate basic metrics 559 metrics.technical_evolution_score = Math.min(100, (commits.length * 2) + (issues.length * 3)); 560 metrics.complexity_trend = issues.filter(i => i.complexity_level === 'high').length > 0 ? 'increasing' : 'stable'; 561 metrics.contribution_consistency = commits.length > 10 ? 'high' : commits.length > 5 ? 'medium' : 'developing'; 562 563 return { highlights, metrics }; 564 } 565 566 /** 567 * Save generated narratives to files 568 */ 569 async saveNarratives() { 570 try { 571 const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); 572 573 console.log(`💾 Saving narratives to ${this.outputDir}...`); 574 575 // Ensure output directory exists 576 await fs.mkdir(this.outputDir, { recursive: true }); 577 console.log(`📁 Narratives directory ensured: ${this.outputDir}`); 578 579 // Save comprehensive narratives 580 const narrativesPath = path.join(this.outputDir, `professional-narratives-${timestamp}.json`); 581 await fs.writeFile(narrativesPath, JSON.stringify(this.narratives, null, 2), 'utf8'); 582 583 // Verify file was written 584 const narrativeStats = await fs.stat(narrativesPath); 585 console.log(`📄 Narratives saved: ${narrativesPath} (${narrativeStats.size} bytes)`); 586 587 // Save summary for integration 588 const integration = { 589 generated_at: new Date().toISOString(), 590 narratives_available: { 591 professional_summary: !!this.narratives.professional_summary, 592 technical_achievements: this.narratives.technical_achievements.length, 593 leadership_examples: this.narratives.leadership_examples.length, 594 collaboration_stories: this.narratives.collaboration_stories.length, 595 skill_validations: this.narratives.skill_validations.length, 596 growth_narrative: !!this.narratives.growth_narrative 597 }, 598 integration_ready: true, 599 enhanced_summary: this.narratives.professional_summary?.enhanced_summary || null, 600 top_achievements: this.narratives.technical_achievements.slice(0, 3), 601 validated_skills: this.narratives.skill_validations.slice(0, 5).map(v => v.skill) 602 }; 603 604 const integrationPath = path.join(this.outputDir, 'narrative-integration.json'); 605 await fs.writeFile(integrationPath, JSON.stringify(integration, null, 2), 'utf8'); 606 607 // Verify integration file was written 608 const integrationStats = await fs.stat(integrationPath); 609 console.log(`🔗 Integration data saved: ${integrationPath} (${integrationStats.size} bytes)`); 610 611 // List all files in output directory for verification 612 const outputFiles = await fs.readdir(this.outputDir); 613 console.log(`📋 Files in narratives directory: ${outputFiles.join(', ')}`); 614 615 } catch (error) { 616 console.error('❌ Failed to save narrative data:', error); 617 throw error; 618 } 619 } 620 } 621 622 /** 623 * Main execution function 624 */ 625 async function main() { 626 try { 627 console.log('📖 Professional Narrative Generator'); 628 console.log('=================================='); 629 console.log(''); 630 631 const generator = new NarrativeGenerator(); 632 const narratives = await generator.generateNarratives(); 633 634 if (narratives) { 635 console.log(''); 636 console.log('🎯 **NARRATIVE GENERATION SUMMARY**'); 637 console.log(`📝 Professional summary: ${narratives.professional_summary ? '✅' : '❌'}`); 638 console.log(`🏆 Technical achievements: ${narratives.technical_achievements.length}`); 639 console.log(`👥 Leadership examples: ${narratives.leadership_examples.length}`); 640 console.log(`🤝 Collaboration stories: ${narratives.collaboration_stories.length}`); 641 console.log(`✅ Skill validations: ${narratives.skill_validations.length}`); 642 console.log(`📈 Growth narrative: ${narratives.growth_narrative ? '✅' : '❌'}`); 643 } 644 645 } catch (error) { 646 console.error('❌ Narrative generation failed:', error.message); 647 process.exit(1); 648 } 649 } 650 651 // Execute if called directly 652 if (require.main === module) { 653 main().catch(console.error); 654 } 655 656 module.exports = { NarrativeGenerator };