sinapse-ai 9.0.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,12 +6,16 @@
6
6
  * - Business logic
7
7
  * - Security considerations
8
8
  * - UX/UI implications
9
+ * - Git-blame hotspot analysis (v2.0)
9
10
  *
10
11
  * @module core/quality-gates/focus-area-recommender
11
- * @version 1.0.0
12
+ * @version 2.0.0
12
13
  * @story 3.5 - Human Review Orchestration (Layer 3)
14
+ * @story 9.0 - Ecosystem Quality (git-blame enrichment)
13
15
  */
14
16
 
17
+ const { execSync } = require('child_process');
18
+
15
19
  /**
16
20
  * Focus Area Recommender
17
21
  * Generates intelligent focus areas based on code changes and context
@@ -54,17 +58,48 @@ class FocusAreaRecommender {
54
58
  skip: this.skipAreas,
55
59
  summary: '',
56
60
  highlightedAspects: [],
61
+ hotspots: [],
57
62
  };
58
63
 
59
64
  // Analyze changed files
60
65
  const fileAnalysis = this.analyzeChangedFiles(prContext.changedFiles || []);
61
66
  recommendations.highlightedAspects.push(...fileAnalysis.highlights);
62
67
 
68
+ // Git-blame hotspot analysis (v2.0)
69
+ const blameInsights = this.getGitBlameInsights(prContext.changedFiles || []);
70
+ if (blameInsights.hotspots.length > 0) {
71
+ recommendations.hotspots = blameInsights.hotspots;
72
+ recommendations.highlightedAspects.push(...blameInsights.highlights);
73
+ }
74
+
63
75
  // Add primary focus areas
64
76
  recommendations.primary = this.determinePrimaryAreas(fileAnalysis, layer2Result);
65
77
 
78
+ // Boost priority for hotspot files
79
+ if (blameInsights.hotspots.length > 0) {
80
+ const hotspotArea = {
81
+ area: 'change-hotspot',
82
+ reason: `${blameInsights.hotspots.length} file(s) changed frequently (high churn)`,
83
+ files: blameInsights.hotspots.map((h) => h.file).slice(0, 5),
84
+ questions: [
85
+ 'Is this file changing too often? Consider refactoring.',
86
+ 'Are these changes addressing root cause or symptoms?',
87
+ 'Would a different architecture reduce churn here?',
88
+ ],
89
+ };
90
+ // Insert hotspot as secondary if not already critical
91
+ if (!recommendations.primary.some((p) => p.area === 'architecture')) {
92
+ recommendations.primary.push(hotspotArea);
93
+ recommendations.primary = recommendations.primary.slice(0, 3);
94
+ } else {
95
+ recommendations.secondary = [hotspotArea, ...recommendations.secondary].slice(0, 2);
96
+ }
97
+ }
98
+
66
99
  // Add secondary focus areas
67
- recommendations.secondary = this.determineSecondaryAreas(fileAnalysis, layer2Result);
100
+ if (recommendations.secondary.length === 0) {
101
+ recommendations.secondary = this.determineSecondaryAreas(fileAnalysis, layer2Result);
102
+ }
68
103
 
69
104
  // Generate summary
70
105
  recommendations.summary = this.generateSummary(recommendations);
@@ -72,6 +107,74 @@ class FocusAreaRecommender {
72
107
  return recommendations;
73
108
  }
74
109
 
110
+ /**
111
+ * Get git-blame insights for changed files
112
+ * Identifies hotspots (frequently changed files) and recent contributors
113
+ * @param {Array} changedFiles - List of changed file paths
114
+ * @returns {Object} Blame insights with hotspots and highlights
115
+ */
116
+ getGitBlameInsights(changedFiles = []) {
117
+ const insights = {
118
+ hotspots: [],
119
+ highlights: [],
120
+ contributors: new Map(),
121
+ };
122
+
123
+ if (changedFiles.length === 0) return insights;
124
+
125
+ for (const file of changedFiles.slice(0, 20)) {
126
+ try {
127
+ // Count commits touching this file in last 30 days
128
+ const commitCount = execSync(
129
+ `git log --oneline --since="30 days ago" -- "${file}" 2>/dev/null | wc -l`,
130
+ { encoding: 'utf8', timeout: 5000 },
131
+ ).trim();
132
+
133
+ const count = parseInt(commitCount, 10) || 0;
134
+
135
+ if (count >= 5) {
136
+ insights.hotspots.push({
137
+ file,
138
+ commits30d: count,
139
+ severity: count >= 10 ? 'critical' : 'high',
140
+ });
141
+ }
142
+
143
+ // Get unique contributors for this file
144
+ const authors = execSync(
145
+ `git log --format="%aN" --since="30 days ago" -- "${file}" 2>/dev/null | sort -u`,
146
+ { encoding: 'utf8', timeout: 5000 },
147
+ ).trim().split('\n').filter(Boolean);
148
+
149
+ for (const author of authors) {
150
+ insights.contributors.set(author, (insights.contributors.get(author) || 0) + 1);
151
+ }
152
+ } catch {
153
+ // Git not available or file not tracked — skip silently
154
+ continue;
155
+ }
156
+ }
157
+
158
+ // Sort hotspots by commit count
159
+ insights.hotspots.sort((a, b) => b.commits30d - a.commits30d);
160
+
161
+ // Generate highlights
162
+ if (insights.hotspots.length > 0) {
163
+ const topHotspot = insights.hotspots[0];
164
+ insights.highlights.push(
165
+ `Hotspot: ${topHotspot.file} changed ${topHotspot.commits30d}x in 30 days`,
166
+ );
167
+ }
168
+
169
+ if (insights.contributors.size > 1) {
170
+ insights.highlights.push(
171
+ `${insights.contributors.size} contributors touched these files recently`,
172
+ );
173
+ }
174
+
175
+ return insights;
176
+ }
177
+
75
178
  /**
76
179
  * Analyze changed files to determine focus areas
77
180
  * @param {Array} changedFiles - List of changed file paths