pull-request-split-advisor 3.2.13 → 3.2.14

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.
@@ -132,6 +132,63 @@ export function buildBlocks(fileStats, config, deps) {
132
132
  depScore: 0
133
133
  });
134
134
  }
135
+ // ── Post-proceso: fusionar bloques de solo-tests con su bloque fuente ──────
136
+ // Cubre el caso en que test y fuente están en directorios distintos
137
+ // (ej: `tests/` vs `src/`) y getGroupKey los asigna a bloques distintos.
138
+ // Regla: un test no puede ir a una rama sin su fuente, salvo que la fuente
139
+ // no tenga cambios y no esté en el conjunto analizado.
140
+ {
141
+ const linesByPath = new Map(fileStats.map((f) => [f.path, f.lines]));
142
+ // Índice: nombre-base del fuente → bloque que lo contiene.
143
+ const sourceBlockByBaseName = new Map();
144
+ for (const block of blocks) {
145
+ for (const f of block.files) {
146
+ if (!isTestFile(f)) {
147
+ const baseName = basename(f).replace(/\.[^.]+$/, "").replace(/\.?(test|spec)$/, "");
148
+ if (!sourceBlockByBaseName.has(baseName)) {
149
+ sourceBlockByBaseName.set(baseName, block);
150
+ }
151
+ }
152
+ }
153
+ }
154
+ // Para cada bloque que contiene exclusivamente tests, intentar fusionarlo
155
+ // con el bloque de su archivo fuente si éste tiene cambios.
156
+ const blocksToDelete = new Set();
157
+ for (const block of blocks) {
158
+ if (blocksToDelete.has(block))
159
+ continue;
160
+ if (!block.files.every((f) => isTestFile(f)))
161
+ continue; // ya tiene fuente: OK
162
+ const moved = [];
163
+ for (const testFile of block.files) {
164
+ const testBase = basename(testFile)
165
+ .replace(/\.[^.]+$/, "")
166
+ .replace(/\.?(test|spec)$/, "")
167
+ .replace(/^test_/, "")
168
+ .replace(/_test$/, "");
169
+ const sourceBlock = sourceBlockByBaseName.get(testBase);
170
+ if (!sourceBlock || sourceBlock === block)
171
+ continue;
172
+ // Fusionar el test en el bloque del fuente.
173
+ sourceBlock.files.push(testFile);
174
+ sourceBlock.lines += linesByPath.get(testFile) ?? 0;
175
+ sourceBlock.divisible = false; // test + fuente → siempre indivisible
176
+ moved.push(testFile);
177
+ }
178
+ if (moved.length === block.files.length) {
179
+ // Todos los tests fueron reasignados: eliminar este bloque vacío.
180
+ blocksToDelete.add(block);
181
+ }
182
+ else if (moved.length > 0) {
183
+ // Quitar solo los archivos que se movieron.
184
+ block.files = block.files.filter((f) => !moved.includes(f));
185
+ block.lines = block.files.reduce((sum, f) => sum + (linesByPath.get(f) ?? 0), 0);
186
+ }
187
+ }
188
+ if (blocksToDelete.size > 0) {
189
+ blocks.splice(0, blocks.length, ...blocks.filter((b) => !blocksToDelete.has(b)));
190
+ }
191
+ }
135
192
  // Calcular depScore con un mapa de frecuencias: O(n+m) en lugar de O(n×m).
136
193
  const fileEdgeCount = new Map();
137
194
  for (const { from, to } of deps) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pull-request-split-advisor",
3
- "version": "3.2.13",
3
+ "version": "3.2.14",
4
4
  "description": "CLI that analyses your Git working tree and suggests how to split changes into smaller, reviewable pull requests and commits.",
5
5
  "keywords": [
6
6
  "git",