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
|
|
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
|
|
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
|