/ .github / scripts / narrative-generator.js
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 };