pull-request-split-advisor 3.2.0 → 3.2.2
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.
- package/README.md +4 -1
- package/dist/ai/config-wizard.js +2 -2
- package/dist/cli.js +64 -15
- package/dist/config/config.js +1 -1
- package/dist/config/default-config.js +1 -2
- package/dist/core/file-stats.js +77 -0
- package/dist/core/planner.js +1 -1
- package/dist/output/report-styles.generated.js +1 -1
- package/dist/output/report.js +28 -3
- package/dist/output/ui.js +21 -2
- package/package.json +1 -1
- package/scripts/postinstall.cjs +39 -13
package/README.md
CHANGED
|
@@ -29,6 +29,8 @@ La v3.0.0 también mejora el **apply**: rama de respaldo con snapshot completo d
|
|
|
29
29
|
|
|
30
30
|
A partir de la **v3.2.0**, se añade el subcomando **`score`** que calcula y muestra el score del estado actual de la rama sin dividir en PRs, exportando también un reporte HTML independiente (`pr-split-score.html`) con desglose de métricas, valores brutos y detalle de archivos.
|
|
31
31
|
|
|
32
|
+
A partir de la **v3.2.1**, cada archivo analizado recibe una **clasificación de origen** basada en su posición en el árbol git: `REMOTO` (cambios ya publicados en el remoto), `LOCAL` (commiteado solo en local), `WIP` (modificación sin commitear aún) o `NUEVO` (archivo sin rastrear). Esta información aparece como badge de color en la salida terminal y en la columna *Origen* de las tablas de archivos en ambos reportes HTML. El subcomando `score` utiliza exclusivamente los archivos en estado `WIP` o `NUEVO` para las métricas; si el working tree está limpio (todos los cambios ya están en commits), el comando informa y sale sin calcular un score basado en datos erróneos.
|
|
33
|
+
|
|
32
34
|
A partir de la **v3.1.0**, el proyecto incluye una **suite completa de tests** (Vitest, 199 tests, 12 suites) que cubre todos los módulos core, AI, git y configuración con mocks de dependencias externas (git, fs). A partir de la **v3.1.1**, el proveedor `copilot` solo puede activarse dentro de **Visual Studio Code** — fuera del IDE se emite un aviso y se desactiva automáticamente sin interrumpir el análisis.
|
|
33
35
|
|
|
34
36
|
---
|
|
@@ -40,7 +42,8 @@ A partir de la **v3.1.0**, el proyecto incluye una **suite completa de tests** (
|
|
|
40
42
|
- Cálculo de score de calidad por rama (0–5) con factores ponderados
|
|
41
43
|
- Generación de nombres de rama y mensajes de commit convencionales
|
|
42
44
|
- Reporte HTML interactivo exportado automáticamente (8 secciones: hero ejecutivo, resumen de cambios, TOC con barras de score, detalle de archivos, dependencias, bloques, plan de commits con línea de tiempo y comandos git listos para ejecutar) — ver [formato completo](docs/FEATURES.md#reporte-html-interactivo)
|
|
43
|
-
- **`score`**: subcomando (v3.2.0+) que calcula el score del estado actual sin dividir y genera `pr-split-score.html` con desglose completo de métricas
|
|
45
|
+
- **`score`**: subcomando (v3.2.0+) que calcula el score del estado actual sin dividir y genera `pr-split-score.html` con desglose completo de métricas — evalúa **solo archivos WIP y sin rastrear**; si el working tree está limpio, informa al usuario y sale sin generar un score incorrecto
|
|
46
|
+
- **Clasificación de origen por archivo** (v3.2.1+): diferencia entre `REMOTO` (publicado), `LOCAL` (commit local no publicado), `WIP` (sin commitear) y `NUEVO` (sin rastrear) — badge de color en terminal y columna *Origen* en ambos reportes HTML
|
|
44
47
|
- Exportación del plan en formato JSON para integraciones externas
|
|
45
48
|
- Historial de scores con seguimiento entre análisis
|
|
46
49
|
- Validaciones de nomenclatura de ramas y estado del working tree
|
package/dist/ai/config-wizard.js
CHANGED
|
@@ -60,7 +60,7 @@ async function testApiKeyConnection(provider, model, apiKey, apiKeyEnvVar) {
|
|
|
60
60
|
model,
|
|
61
61
|
apiKey,
|
|
62
62
|
apiKeyEnvVar,
|
|
63
|
-
features: { commitMessages: false, branchDescriptions: false },
|
|
63
|
+
features: { commitMessages: false, branchDescriptions: false, planRebalance: false },
|
|
64
64
|
timeoutMs: 10000,
|
|
65
65
|
maxTokens: 16,
|
|
66
66
|
},
|
|
@@ -227,7 +227,7 @@ export async function runConfigWizard() {
|
|
|
227
227
|
* Valida la key contra el proveedor antes de guardar. Si falla, aborta con error.
|
|
228
228
|
*
|
|
229
229
|
* @param apiKey - La API key del proveedor (literal, no nombre de env var).
|
|
230
|
-
* @param provider - ID del proveedor: "groq" | "
|
|
230
|
+
* @param provider - ID del proveedor: "groq" | "copilot". Predeterminado: "groq".
|
|
231
231
|
*/
|
|
232
232
|
export async function runConfigWithKey(apiKey, provider = "groq") {
|
|
233
233
|
const configPath = resolve(process.cwd(), CONFIG_FILE);
|
package/dist/cli.js
CHANGED
|
@@ -29,7 +29,8 @@ import { loadConfig } from "./config/config.js";
|
|
|
29
29
|
import { APP_BIN, APP_NAME, APP_VERSION } from "./shared/constants.js";
|
|
30
30
|
import { applyPlan } from "./git/executor.js";
|
|
31
31
|
import { detectRemote, fetchQuiet, getCurrentBranch, localAheadCount, localBehindCount, remoteTrackingExists, requireCleanIndex, requireGitRepo } from "./git/git.js";
|
|
32
|
-
import { buildBlocks, buildDependencyEdges, findBestPlan, gatherChangedFiles, getFileStats } from "./core/planner.js";
|
|
32
|
+
import { buildBlocks, buildDependencyEdges, computeFileOrigins, findBestPlan, gatherChangedFiles, getFileStats } from "./core/planner.js";
|
|
33
|
+
import { resetTrackedFilesCache } from "./shared/utils.js";
|
|
33
34
|
import { exportJson, writeHtmlReport, writeScoreHtmlReport } from "./output/report.js";
|
|
34
35
|
import { appendHistory, readHistory } from "./core/history.js";
|
|
35
36
|
import { buildSuggestedMessage } from "./core/commit-planner.js";
|
|
@@ -129,9 +130,10 @@ function printSummary(fileStats, config, currentBranch, baseBranch) {
|
|
|
129
130
|
}
|
|
130
131
|
function printFileStats(fileStats) {
|
|
131
132
|
ui.section("DETALLE DE ARCHIVOS", "#");
|
|
132
|
-
ui.table(["Marca", "Archivo", "Lineas", "+", "-", "Prioridad"], fileStats.map((f) => [
|
|
133
|
+
ui.table(["Origen", "Marca", "Archivo", "Lineas", "+", "-", "Prioridad"], fileStats.map((f) => [
|
|
134
|
+
ui.originBadge(f.origin),
|
|
133
135
|
changeTypeLabel(f.changeType),
|
|
134
|
-
ui.truncateMiddle(f.path,
|
|
136
|
+
ui.truncateMiddle(f.path, 50),
|
|
135
137
|
f.lines,
|
|
136
138
|
f.additions,
|
|
137
139
|
f.deletions,
|
|
@@ -280,6 +282,7 @@ async function main() {
|
|
|
280
282
|
config.jsonOutputFile,
|
|
281
283
|
"pr-split-advisor.config.json",
|
|
282
284
|
"pr-split-report.html",
|
|
285
|
+
"pr-split-score.html",
|
|
283
286
|
config.historyFile
|
|
284
287
|
].filter(Boolean);
|
|
285
288
|
if (config.verbose) {
|
|
@@ -349,7 +352,7 @@ async function main() {
|
|
|
349
352
|
const changedFiles = gatherChangedFiles(config, baseBranch);
|
|
350
353
|
if (!changedFiles.length) {
|
|
351
354
|
ui.spinner.stop();
|
|
352
|
-
ui.warn("No hay cambios
|
|
355
|
+
ui.warn("No hay cambios respecto a la rama base.");
|
|
353
356
|
closeReadlineInterface();
|
|
354
357
|
return;
|
|
355
358
|
}
|
|
@@ -364,6 +367,16 @@ async function main() {
|
|
|
364
367
|
const plans = findBestPlan(blocks, currentBranch, config, deps);
|
|
365
368
|
config.testCoveragePercent = computeTestCoveragePercent(fileStats);
|
|
366
369
|
ui.spinner.stop("Analisis completado.");
|
|
370
|
+
// ─── Computar origen de cada archivo en el árbol git ─────────────────────
|
|
371
|
+
// Diferencia entre: ya publicado (REMOTO), commiteado local (LOCAL),
|
|
372
|
+
// cambio sin commitear (WIP) y archivo nuevo sin rastrear (NUEVO).
|
|
373
|
+
// Resetear la caché de tracked files para que computeFileOrigins use
|
|
374
|
+
// un Set fresco (evita stale data si el index cambió durante el análisis).
|
|
375
|
+
resetTrackedFilesCache();
|
|
376
|
+
const origins = computeFileOrigins(changedFiles, baseBranch, currentBranch, remote);
|
|
377
|
+
for (const stat of fileStats) {
|
|
378
|
+
stat.origin = origins.get(stat.path);
|
|
379
|
+
}
|
|
367
380
|
// ─── Enriquecimiento con IA (opcional) ───────────────────────────────────
|
|
368
381
|
// Si la IA no está habilitada o configurada, `aiEnrichPlans` retorna
|
|
369
382
|
// de inmediato sin modificar los planes (modo heurístico puro).
|
|
@@ -439,6 +452,7 @@ async function main() {
|
|
|
439
452
|
config.jsonOutputFile,
|
|
440
453
|
"pr-split-advisor.config.json",
|
|
441
454
|
"pr-split-report.html",
|
|
455
|
+
"pr-split-score.html",
|
|
442
456
|
config.historyFile
|
|
443
457
|
].filter(Boolean);
|
|
444
458
|
ui.spinner.start("Calculando score del estado actual...");
|
|
@@ -451,17 +465,43 @@ async function main() {
|
|
|
451
465
|
}
|
|
452
466
|
const fileStats = getFileStats(changedFiles, baseBranch);
|
|
453
467
|
ui.spinner.stop("Calculo completado.");
|
|
468
|
+
// ─── Computar origen de cada archivo en el árbol git ─────────────────────
|
|
469
|
+
// Resetear caché para asegurar datos frescos (el score subcommand no llama
|
|
470
|
+
// a requireCleanIndex, por lo que puede haber archivos staged/unstaged que
|
|
471
|
+
// cambien el estado del index entre la primera lectura y esta clasificación).
|
|
472
|
+
resetTrackedFilesCache();
|
|
473
|
+
const scoreRemote = detectRemote(currentBranch);
|
|
474
|
+
const scoreOrigins = computeFileOrigins(changedFiles, baseBranch, currentBranch, scoreRemote);
|
|
475
|
+
for (const stat of fileStats) {
|
|
476
|
+
stat.origin = scoreOrigins.get(stat.path);
|
|
477
|
+
}
|
|
454
478
|
const totalFiles = fileStats.length;
|
|
455
479
|
const totalLines = fileStats.reduce((sum, f) => sum + f.lines, 0);
|
|
480
|
+
// El score se calcula ÚNICAMENTE sobre archivos del working tree en ese momento.
|
|
481
|
+
// Los archivos ya commiteados (LOCAL o REMOTO) se muestran en el reporte
|
|
482
|
+
// con su badge de origen pero NO afectan las métricas del score.
|
|
483
|
+
const scoredStats = fileStats.filter((f) => f.origin === "working-tree" || f.origin === "untracked");
|
|
484
|
+
const scoredFiles = scoredStats.length;
|
|
485
|
+
const scoredLines = scoredStats.reduce((sum, f) => sum + f.lines, 0);
|
|
486
|
+
// Si no hay archivos WIP no hay nada que puntuar: todos los cambios ya
|
|
487
|
+
// están en commits (LOCAL o REMOTO) y el working tree está limpio.
|
|
488
|
+
if (scoredFiles === 0) {
|
|
489
|
+
ui.warn("No hay cambios en el working tree para analizar. " +
|
|
490
|
+
"Todos los archivos detectados ya están en commits (LOCAL o REMOTO). " +
|
|
491
|
+
"Realiza cambios sin commitear para obtener un score.");
|
|
492
|
+
closeReadlineInterface();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
456
495
|
const aheadCommits = localAheadCount(baseBranch);
|
|
457
496
|
const commitCount = Math.max(aheadCommits, 1);
|
|
458
|
-
const filesPerCommit =
|
|
459
|
-
const avgLinesPerCommit = Math.round(
|
|
460
|
-
const result = scorePullRequest({ commitCount, filesPerCommit, avgLinesPerCommit, totalLinesChanged:
|
|
497
|
+
const filesPerCommit = Number((scoredFiles / commitCount).toFixed(2));
|
|
498
|
+
const avgLinesPerCommit = Math.round(scoredLines / commitCount);
|
|
499
|
+
const result = scorePullRequest({ commitCount, filesPerCommit, avgLinesPerCommit, totalLinesChanged: scoredLines }, config);
|
|
500
|
+
const warnThreshold = config.targetScore > 4 ? 4 : Math.max(0, config.targetScore - 1);
|
|
461
501
|
const scoreColor = ui.scoreColor(result.complexity, config.targetScore);
|
|
462
502
|
const statusBadge = result.complexity >= config.targetScore
|
|
463
503
|
? ui.badge("ÓPTIMO", "green")
|
|
464
|
-
: result.complexity >=
|
|
504
|
+
: result.complexity >= warnThreshold
|
|
465
505
|
? ui.badge("ACEPTABLE", "yellow")
|
|
466
506
|
: ui.badge("RIESGO", "red");
|
|
467
507
|
const scoreHtmlFile = "pr-split-score.html";
|
|
@@ -473,18 +513,27 @@ async function main() {
|
|
|
473
513
|
commitCount,
|
|
474
514
|
filesPerCommit,
|
|
475
515
|
avgLinesPerCommit,
|
|
476
|
-
result
|
|
516
|
+
result,
|
|
517
|
+
scoredFileCount: scoredFiles !== totalFiles ? scoredFiles : undefined,
|
|
518
|
+
scoredLines: scoredFiles !== totalFiles ? scoredLines : undefined
|
|
477
519
|
});
|
|
478
520
|
ui.ok(`Reporte HTML generado: ${scoreHtmlFile}`);
|
|
521
|
+
const hasWipDistinction = scoredFiles !== totalFiles;
|
|
479
522
|
ui.card(`Score actual de la rama: ${currentBranch}`, [
|
|
480
523
|
`${statusBadge}`,
|
|
481
524
|
"",
|
|
482
|
-
`Rama base:
|
|
483
|
-
`Archivos
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
`Líneas
|
|
525
|
+
`Rama base: ${baseBranch}`,
|
|
526
|
+
`Archivos totales analizados: ${totalFiles}${hasWipDistinction ? ` (${totalFiles - scoredFiles} ya commiteados, excluidos del score)` : ""}`,
|
|
527
|
+
...(hasWipDistinction
|
|
528
|
+
? [`Archivos scored (WIP): ${scoredFiles} ← base del score`]
|
|
529
|
+
: []),
|
|
530
|
+
`Líneas totales analizadas: ${totalLines}`,
|
|
531
|
+
...(hasWipDistinction
|
|
532
|
+
? [`Líneas scored (WIP): ${scoredLines} ← base del score`]
|
|
533
|
+
: []),
|
|
534
|
+
`Commits adelantados: ${commitCount}`,
|
|
535
|
+
`Archivos / commit (scored): ${filesPerCommit}`,
|
|
536
|
+
`Líneas / commit (scored avg):${avgLinesPerCommit}`,
|
|
488
537
|
"",
|
|
489
538
|
`Score: ${ui.score(result.complexity, config.targetScore)}`,
|
|
490
539
|
`Score objetivo: ${config.targetScore}`,
|
package/dist/config/config.js
CHANGED
|
@@ -304,7 +304,7 @@ export function loadConfig(configPath = "pr-split-advisor.config.json") {
|
|
|
304
304
|
}
|
|
305
305
|
if (isObject(ai["features"])) {
|
|
306
306
|
const f = ai["features"];
|
|
307
|
-
for (const flag of ["commitMessages", "branchDescriptions"]) {
|
|
307
|
+
for (const flag of ["commitMessages", "branchDescriptions", "planRebalance"]) {
|
|
308
308
|
if (flag in f && typeof f[flag] !== "boolean") {
|
|
309
309
|
throw new Error(`Configuración inválida: "ai.features.${flag}" debe ser true o false (recibido: ${JSON.stringify(f[flag])}).`);
|
|
310
310
|
}
|
|
@@ -188,9 +188,8 @@ export const defaultConfig = {
|
|
|
188
188
|
//
|
|
189
189
|
// Configurar vía `pr-split-advisor config` o editando directamente el JSON.
|
|
190
190
|
//
|
|
191
|
-
// Opciones de `provider` soportadas: "groq" | "
|
|
191
|
+
// Opciones de `provider` soportadas: "groq" | "copilot"
|
|
192
192
|
// groq → requiere GROQ_API_KEY (console.groq.com, free tier)
|
|
193
|
-
// github → requiere GITHUB_TOKEN (github.com/marketplace/models, free)
|
|
194
193
|
// copilot → sin token — usa automáticamente credenciales de gh CLI
|
|
195
194
|
//
|
|
196
195
|
// API key: preferir `apiKeyEnvVar` (ej: GROQ_API_KEY) antes que literal `apiKey`.
|
package/dist/core/file-stats.js
CHANGED
|
@@ -339,3 +339,80 @@ export function getFileStats(files, baseBranch) {
|
|
|
339
339
|
};
|
|
340
340
|
});
|
|
341
341
|
}
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
// File origin classification
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
/**
|
|
346
|
+
* Clasifica cada archivo de la lista según su origen en el árbol git:
|
|
347
|
+
*
|
|
348
|
+
* - `"pushed"`: existe en commits ya publicados al remoto (base → remoto/rama).
|
|
349
|
+
* - `"local-commit"`: commiteado localmente pero aún no publicado (remoto/rama → HEAD,
|
|
350
|
+
* o base → HEAD cuando no hay ref de tracking remota).
|
|
351
|
+
* - `"working-tree"`: cambio staged o unstaged no commiteado en el working tree.
|
|
352
|
+
* - `"untracked"`: archivo nuevo sin seguimiento git (nunca añadido al índice).
|
|
353
|
+
*
|
|
354
|
+
* La asignación sigue un orden de prioridad:
|
|
355
|
+
* untracked > working-tree/staged > local-commit > pushed > fallback("working-tree")
|
|
356
|
+
*
|
|
357
|
+
* Cuando un archivo tiene cambios en el working tree además de estar en un commit
|
|
358
|
+
* ya publicado, se clasifica como `"working-tree"` porque tiene cambios pendientes
|
|
359
|
+
* por encima del commit remoto.
|
|
360
|
+
*
|
|
361
|
+
* @param files - Lista de rutas relativas a clasificar.
|
|
362
|
+
* @param baseBranch - Rama base del PR (ej. "main").
|
|
363
|
+
* @param currentBranch - Rama de trabajo actual.
|
|
364
|
+
* @param remote - Nombre del remoto (ej. "origin").
|
|
365
|
+
* @returns Mapa `ruta → FileOrigin` con una entrada por cada ruta de `files`.
|
|
366
|
+
*/
|
|
367
|
+
export function computeFileOrigins(files, baseBranch, currentBranch, remote) {
|
|
368
|
+
const origins = new Map();
|
|
369
|
+
// Archivos con cambios unstaged en el working tree (tracked pero no staged)
|
|
370
|
+
const workingTreeSet = new Set(shSafe("git diff --name-only")
|
|
371
|
+
.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
372
|
+
// Archivos staged (git add pero no commiteados) — también son "working-tree"
|
|
373
|
+
const stagedSet = new Set(shSafe("git diff --cached --name-only")
|
|
374
|
+
.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
375
|
+
// Archivos untracked (nuevos, sin git add)
|
|
376
|
+
const untrackedSet = new Set(shSafe("git ls-files --others --exclude-standard")
|
|
377
|
+
.split("\n").map((s) => s.trim()).filter(Boolean));
|
|
378
|
+
// Verificar si existe una ref de tracking remota para la rama actual
|
|
379
|
+
const remoteRef = `${remote}/${currentBranch}`;
|
|
380
|
+
const remoteExists = shSafe(`git rev-parse --verify ${q(`refs/remotes/${remoteRef}`)}`).length > 0;
|
|
381
|
+
// Archivos en commits ya publicados (base → remoto/rama)
|
|
382
|
+
const pushedSet = new Set();
|
|
383
|
+
if (remoteExists) {
|
|
384
|
+
shSafe(`git diff ${q(baseBranch)}..${q(remoteRef)} --name-only`)
|
|
385
|
+
.split("\n").map((s) => s.trim()).filter(Boolean)
|
|
386
|
+
.forEach((f) => pushedSet.add(f));
|
|
387
|
+
}
|
|
388
|
+
// Archivos en commits locales aún no publicados
|
|
389
|
+
// Si existe tracking remota: commits entre remoto/rama y HEAD.
|
|
390
|
+
// Si no existe tracking: todos los commits adelantados respecto a base son locales.
|
|
391
|
+
const localCommitSet = new Set();
|
|
392
|
+
const localBaseRef = remoteExists ? remoteRef : baseBranch;
|
|
393
|
+
shSafe(`git diff ${q(localBaseRef)}..HEAD --name-only`)
|
|
394
|
+
.split("\n").map((s) => s.trim()).filter(Boolean)
|
|
395
|
+
.forEach((f) => localCommitSet.add(f));
|
|
396
|
+
// Asignar origen por prioridad: untracked > working-tree/staged > local-commit > pushed
|
|
397
|
+
for (const file of files) {
|
|
398
|
+
const norm = normalizePathValue(file);
|
|
399
|
+
if (untrackedSet.has(file) || untrackedSet.has(norm)) {
|
|
400
|
+
origins.set(file, "untracked");
|
|
401
|
+
}
|
|
402
|
+
else if (workingTreeSet.has(file) || workingTreeSet.has(norm) ||
|
|
403
|
+
stagedSet.has(file) || stagedSet.has(norm)) {
|
|
404
|
+
origins.set(file, "working-tree");
|
|
405
|
+
}
|
|
406
|
+
else if (localCommitSet.has(file) || localCommitSet.has(norm)) {
|
|
407
|
+
origins.set(file, "local-commit");
|
|
408
|
+
}
|
|
409
|
+
else if (pushedSet.has(file) || pushedSet.has(norm)) {
|
|
410
|
+
origins.set(file, "pushed");
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
// Fallback: si el archivo no aparece en ningún diff conocido, asumir working-tree
|
|
414
|
+
origins.set(file, "working-tree");
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return origins;
|
|
418
|
+
}
|
package/dist/core/planner.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* Importar desde este barrel en lugar de los módulos individuales reduce el
|
|
20
20
|
* acoplamiento entre `cli.ts` y la estructura interna del directorio `core/`.
|
|
21
21
|
*/
|
|
22
|
-
export { gatherChangedFiles, getFileStats } from "./file-stats.js";
|
|
22
|
+
export { gatherChangedFiles, getFileStats, computeFileOrigins } from "./file-stats.js";
|
|
23
23
|
export { buildDependencyEdges } from "./dependency.js";
|
|
24
24
|
export { buildBlocks } from "./blocks.js";
|
|
25
25
|
export { findBestPlan } from "./strategy.js";
|
|
@@ -7,4 +7,4 @@
|
|
|
7
7
|
* Para aplicar cambios editá `src/output/report.scss` y ejecutá:
|
|
8
8
|
* npm run build:styles
|
|
9
9
|
*/
|
|
10
|
-
export const reportStyles = `@import"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap";:root{--bg: #f0f4f8;--surface: #ffffff;--surface-soft: #f7f9fc;--border: #dde3ed;--text: #1e293b;--muted: #64748b;--green: #15803d;--green-soft: #dcfce7;--green-border: #86efac;--yellow: #a16207;--yellow-soft: #fef3c7;--yellow-border: #fde68a;--orange: #c2410c;--orange-soft: #ffedd5;--orange-border: #fdba74;--red: #b91c1c;--red-soft: #fee2e2;--red-border: #fca5a5;--blue: #1d4ed8;--blue-soft: #dbeafe;--blue-border: #93c5fd;--purple: #6d28d9;--purple-soft: #ede9fe;--shadow-sm: 0 1px 3px rgba(15, 23, 42, 0.07), 0 1px 2px rgba(15, 23, 42, 0.05);--shadow: 0 4px 16px rgba(15, 23, 42, 0.08), 0 1px 3px rgba(15, 23, 42, 0.05);--shadow-lg: 0 10px 40px rgba(15, 23, 42, 0.12), 0 2px 8px rgba(15, 23, 42, 0.06);--radius: 14px;--radius-sm: 8px}*,*::before,*::after{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;padding:0;background:var(--bg);color:var(--text);font-family:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;font-size:15px;line-height:1.6}.container{max-width:1320px;margin:0 auto;padding:32px 24px 80px}.hero{background:linear-gradient(135deg, #0f172a 0%, #1e3a6e 50%, #1d4ed8 100%);color:#fff;border-radius:24px;padding:36px 36px 28px;box-shadow:var(--shadow-lg);margin-bottom:28px;position:relative;overflow:hidden}.hero::before{content:"";position:absolute;inset:0;background:url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");pointer-events:none}.hero-top{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;flex-wrap:wrap}.hero-title-area h1{margin:0 0 6px;font-size:34px;font-weight:800;letter-spacing:-0.5px;line-height:1.1}.hero-title-area p{margin:0;color:hsla(0,0%,100%,.75);font-size:14px;max-width:500px}.hero-timestamp{background:hsla(0,0%,100%,.12);border:1px solid hsla(0,0%,100%,.2);border-radius:10px;padding:8px 14px;font-size:12px;color:hsla(0,0%,100%,.85);white-space:nowrap}.hero-grid{margin-top:24px;display:grid;grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));gap:12px}.hero-item{background:hsla(0,0%,100%,.11);border:1px solid hsla(0,0%,100%,.16);border-radius:14px;padding:14px 18px;backdrop-filter:blur(4px);transition:background .2s}.hero-item:hover{background:hsla(0,0%,100%,.17)}.hero-item .label{font-size:11px;text-transform:uppercase;letter-spacing:.08em;opacity:.7;margin-bottom:5px}.hero-item .value{font-size:17px;font-weight:700;word-break:break-word}.disclaimer-banner{display:flex;align-items:flex-start;gap:16px;margin:24px 0 0;padding:20px 24px;background:#fefce8;border:2px solid #f59e0b;border-radius:12px;box-shadow:0 2px 8px rgba(245,158,11,.18)}.disclaimer-banner .disclaimer-icon{font-size:32px;line-height:1;flex-shrink:0;margin-top:2px}.disclaimer-banner .disclaimer-body{flex:1;color:#78350f}.disclaimer-banner .disclaimer-body strong:first-child{display:block;font-size:15px;font-weight:800;letter-spacing:.5px;text-transform:uppercase;color:#92400e;margin-bottom:8px}.disclaimer-banner .disclaimer-body p{margin:0 0 8px;font-size:14px;line-height:1.6}.disclaimer-banner .disclaimer-body p:last-child{margin-bottom:0}.disclaimer-banner .disclaimer-body ul{margin:6px 0 10px 18px;padding:0;font-size:14px;line-height:1.7}.disclaimer-banner .disclaimer-body .disclaimer-footer{font-size:12px;font-style:italic;color:#a16207;margin-top:8px}.section{margin-top:36px}.section-heading{display:flex;align-items:center;gap:10px;margin:0 0 16px;font-size:20px;font-weight:700;color:#0f172a}.section-heading::after{content:"";flex:1;height:1px;background:var(--border)}.summary-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(190px, 1fr));gap:12px}.stat-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px 18px;box-shadow:var(--shadow-sm);display:flex;flex-direction:column;gap:4px;transition:box-shadow .2s,transform .2s}.stat-card:hover{box-shadow:var(--shadow);transform:translateY(-1px)}.stat-card .label{font-size:12px;color:var(--muted);font-weight:500;text-transform:uppercase;letter-spacing:.04em}.stat-card .value{font-size:26px;font-weight:800;line-height:1.2}.stat-card .sub{font-size:12px;color:var(--muted)}.stat-card.blue .value{color:var(--blue)}.stat-card.green .value{color:var(--green)}.stat-card.yellow .value{color:var(--yellow)}.stat-card.red .value{color:var(--red)}.stat-card.purple .value{color:var(--purple)}.card{background:var(--surface);border:1px solid var(--border);border-radius:18px;padding:22px;box-shadow:var(--shadow);margin-bottom:20px}.badges{display:flex;flex-wrap:wrap;gap:8px}.badge{display:inline-flex;align-items:center;gap:5px;padding:5px 12px;border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;border:1px solid rgba(0,0,0,0)}.badge.green{background:var(--green-soft);color:var(--green);border-color:var(--green-border)}.badge.yellow{background:var(--yellow-soft);color:var(--yellow);border-color:var(--yellow-border)}.badge.orange{background:var(--orange-soft);color:var(--orange);border-color:var(--orange-border)}.badge.red{background:var(--red-soft);color:var(--red);border-color:var(--red-border)}.badge.blue{background:var(--blue-soft);color:var(--blue);border-color:var(--blue-border)}.badge.purple{background:var(--purple-soft);color:var(--purple);border-color:#c4b5fd}table{width:100%;border-collapse:collapse;background:var(--surface);border:1px solid var(--border);border-radius:14px;overflow:hidden;box-shadow:var(--shadow-sm);margin:10px 0 20px}table thead th{background:#f1f5fd;color:#374151;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;text-align:left;padding:11px 14px;border-bottom:1px solid var(--border)}table tbody td{padding:10px 14px;border-bottom:1px solid #e9eff7;vertical-align:middle;font-size:13px}table tbody tr:last-child td{border-bottom:none}table tbody tr:nth-child(even){background:#fafbff}table tbody tr:hover{background:#f0f6ff}.num-cell{text-align:right;font-variant-numeric:tabular-nums;font-weight:600}code{background:#eef2ff;color:#312e81;padding:2px 7px;border-radius:6px;font-size:12px;font-family:"SF Mono","Fira Code","Cascadia Code",monospace;word-break:break-word}.muted{color:var(--muted);font-size:13px}.toc-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px 20px;box-shadow:var(--shadow-sm);margin-bottom:20px}.toc-title{font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:10px}.toc-list{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}.toc-link{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:999px;font-size:12px;font-weight:600;text-decoration:none;border:1px solid rgba(0,0,0,0);transition:opacity .15s}.toc-link:hover{opacity:.8;text-decoration:none}.toc-ok{background:var(--green-soft);color:var(--green);border-color:var(--green-border)}.toc-warn{background:var(--yellow-soft);color:var(--yellow);border-color:var(--yellow-border)}.toc-bad{background:var(--red-soft);color:var(--red);border-color:var(--red-border)}.toc-score{background:rgba(0,0,0,.08);border-radius:999px;padding:1px 7px;font-size:11px;font-weight:800}.plan-card{border-radius:20px;overflow:hidden;box-shadow:var(--shadow);border:1px solid var(--border);margin-bottom:28px}.plan-card-header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:16px;padding:22px 26px}.plan-card-body{background:var(--surface);padding:22px 26px}.plan-header-green{background:linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);border-bottom:1px solid #86efac}.plan-header-yellow{background:linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);border-bottom:1px solid #fde68a}.plan-header-red{background:linear-gradient(135deg, #fff1f2 0%, #fee2e2 100%);border-bottom:1px solid #fca5a5}.plan-header-left{display:flex;align-items:center;gap:14px}.plan-branch-icon{font-size:28px;width:48px;height:48px;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.06);border-radius:14px;flex-shrink:0}.plan-branch-name{font-size:17px;font-weight:800;color:#0f172a;word-break:break-all;display:flex;align-items:center;gap:8px;flex-wrap:wrap}.plan-branch-sub{font-size:13px;color:var(--muted);margin-top:2px}.existing-badge{display:inline-block;background:#dbeafe;color:#1e40af;border:1px solid #93c5fd;padding:1px 8px;border-radius:999px;font-size:11px;font-weight:700;margin-right:4px}.plan-header-right{display:flex;flex-direction:column;align-items:center;gap:4px}.gauge-svg{display:block}.plan-score-label{font-size:11px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.score-label-green{color:var(--green)}.score-label-yellow{color:var(--yellow)}.score-label-red{color:var(--red)}.kpi-row{display:flex;flex-wrap:wrap;gap:12px;margin:16px 0 24px}.kpi{background:var(--surface-soft);border:1px solid var(--border);border-radius:12px;padding:10px 16px;min-width:90px;text-align:center}.kpi-v{font-size:22px;font-weight:800;color:#0f172a;line-height:1.1}.kpi-k{font-size:11px;color:var(--muted);margin-top:3px;text-transform:uppercase;letter-spacing:.04em}.recommendation-box{display:flex;align-items:flex-start;gap:10px;background:#f0f9ff;border:1px solid #bae6fd;border-radius:12px;padding:12px 16px;margin-bottom:16px;font-size:14px;color:#0369a1}.exclusion-note{display:flex;align-items:flex-start;gap:8px;background:#fffbeb;border:1px solid #fde68a;border-radius:10px;padding:10px 14px;margin-bottom:16px;font-size:13px;color:#92400e}.rec-icon{font-size:16px;flex-shrink:0}.section-title{font-size:14px;font-weight:700;color:#0f172a;margin:28px 0 10px;display:flex;align-items:center;gap:6px}.metrics-table thead th{background:#f8faff}.metric-code{background:#e0e7ff;color:#3730a3;font-weight:700}.metric-label{font-size:13px}.metric-value{font-weight:600;color:#374151}.metric-weight{font-weight:600;color:var(--muted)}.metric-contrib{font-weight:700;color:#0f172a;font-variant-numeric:tabular-nums}.pts-bar-wrap{height:6px;background:#e2e8f0;border-radius:999px;overflow:hidden;width:80px;display:inline-block;vertical-align:middle;margin-right:6px}.pts-bar{height:100%;border-radius:999px;transition:width .4s ease}.pts-good{color:var(--green);font-weight:700}.pts-warn{color:var(--yellow);font-weight:700}.pts-bad{color:var(--red);font-weight:700}.metrics-total-row td{background:#f8faff;font-size:13px}.metric-total-value{font-size:16px;font-weight:800;color:#0f172a}.ct-badge{display:inline-block;padding:3px 10px;border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.ct-added{background:#dcfce7;color:#15803d;border:1px solid #86efac}.ct-untracked{background:#ede9fe;color:#6d28d9;border:1px solid #c4b5fd}.ct-modified{background:#dbeafe;color:#1d4ed8;border:1px solid #93c5fd}.ct-deleted{background:#fee2e2;color:#b91c1c;border:1px solid #fca5a5}.ct-renamed{background:#fef3c7;color:#a16207;border:1px solid #fde68a}.ct-default{background:#f1f5f9;color:#475569}.ct-feat{background:#dbeafe;color:#1d4ed8}.ct-fix{background:#fee2e2;color:#b91c1c}.ct-chore{background:#f1f5f9;color:#475569}.ct-docs{background:#fef3c7;color:#a16207}.ct-test{background:#ede9fe;color:#6d28d9}.ct-refactor{background:#f0fdfa;color:#0f766e}.ct-style{background:#fdf4ff;color:#9333ea}.ct-perf{background:#fff7ed;color:#c2410c}.file-path code{max-width:380px;display:inline-block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.diff-bar{display:inline-flex;height:8px;border-radius:4px;overflow:hidden;background:#e2e8f0;vertical-align:middle;min-width:4px}.diff-add{background:#4ade80;height:100%}.diff-del{background:#f87171;height:100%}.diff-labels{font-size:11px;margin-left:6px;font-variant-numeric:tabular-nums}.add-lbl{color:var(--green);font-weight:700}.del-lbl{color:var(--red);font-weight:700;margin-left:3px}.prio-dot{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:999px;font-size:12px;font-weight:800}.prio-1{background:#fee2e2;color:#b91c1c}.prio-2{background:#ffedd5;color:#c2410c}.prio-3{background:#fef3c7;color:#a16207}.prio-4{background:#dbeafe;color:#1d4ed8}.prio-5{background:#f1f5f9;color:#475569}.divisible-yes{color:var(--green);font-size:13px;font-weight:700}.divisible-no{color:var(--red);font-size:13px;font-weight:700}.block-id{background:#f1f5f9;color:#334155;font-size:11px}.block-files code{font-size:10px;margin:1px}.timeline{position:relative;padding-left:28px}.timeline::before{content:"";position:absolute;left:9px;top:16px;bottom:16px;width:2px;background:#e2e8f0;border-radius:999px}.timeline-item{position:relative;margin-bottom:20px}.timeline-dot{position:absolute;left:-24px;top:14px;width:12px;height:12px;background:#3b82f6;border:2px solid #fff;border-radius:999px;box-shadow:0 0 0 2px #bfdbfe}.timeline-body{background:var(--surface-soft);border:1px solid var(--border);border-radius:12px;padding:12px 16px}.timeline-header{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-bottom:6px}.commit-index{background:#e2e8f0;color:#475569;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:800}.commit-type-badge{padding:3px 10px;border-radius:999px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.commit-msg{font-size:13px;color:#0f172a;background:#f8fafc;border:1px solid #e2e8f0;padding:2px 8px;border-radius:6px;word-break:break-word}.timeline-meta{display:flex;flex-wrap:wrap;gap:12px;font-size:12px;color:var(--muted);margin-bottom:8px}.timeline-meta span{display:flex;align-items:center;gap:3px}.commit-files-list{margin:0;padding-left:16px;list-style:disc}.commit-files-list li{margin:2px 0;font-size:12px}.commit-files-list code{font-size:11px;padding:1px 5px}.dep-arrow{color:var(--muted);font-size:16px;vertical-align:middle}.footer{margin-top:48px;border-top:1px solid var(--border);padding-top:20px;text-align:center;font-size:12px;color:var(--muted)}.copy-btn{background:none;border:1px solid rgba(0,0,0,.12);border-radius:6px;padding:1px 6px;cursor:pointer;font-size:11px;color:#64748b;line-height:1.4;transition:background .15s,color .15s;flex-shrink:0;vertical-align:middle}.copy-btn:hover{background:#f1f5f9;color:#0f172a;border-color:#94a3b8}.commit-type-pills{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}.commit-type-pill{display:inline-block;padding:1px 8px;border-radius:999px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;opacity:.85}.toc-bar-wrap{width:50px;height:4px;background:rgba(0,0,0,.1);border-radius:999px;overflow:hidden;flex-shrink:0}.toc-bar{height:100%;border-radius:999px;transition:width .4s ease}.toc-bar-ok{background:var(--green)}.toc-bar-warn{background:var(--yellow)}.toc-bar-bad{background:var(--red)}.toc-commits{font-size:11px;opacity:.65;font-weight:500;white-space:nowrap}.back-to-top{text-align:right;padding-top:12px;border-top:1px solid var(--border);margin-top:20px}.back-link{font-size:12px;color:var(--muted);text-decoration:none;font-weight:600}.back-link:hover{color:#3b82f6;text-decoration:underline}.git-commands-block{background:#0f172a;border-radius:14px;padding:18px 20px;overflow-x:auto;margin-top:8px;box-shadow:inset 0 1px 4px rgba(0,0,0,.3)}.git-commands-block pre{margin:0;font-family:"JetBrains Mono","Fira Mono",ui-monospace,monospace;font-size:12px;line-height:1.8;color:#e2e8f0;white-space:pre}.git-commands-block .gc-comment{color:#4b6988;font-style:italic}.git-commands-block .gc-cmd{color:#7dd3fc;font-weight:700}.git-commands-block .gc-branch{color:#86efac}.git-commands-block .gc-file{color:#fca5a5}.git-commands-block .gc-msg{color:#fde68a}.ct-ci{background:#ecfdf5;color:#059669}.ct-revert{background:#fdf4ff;color:#c026d3}@media(max-width: 960px){.hero{padding:24px 20px}.hero-title-area h1{font-size:26px}.plan-card-header{padding:18px}.plan-card-body{padding:18px}.file-path code{max-width:200px}}@media(max-width: 640px){.container{padding:16px 12px 60px}.kpi-row{gap:8px}.kpi{padding:8px 12px;min-width:72px}.kpi-v{font-size:18px}}@media print{body{background:#fff;font-size:12px}.container{max-width:100%;padding:0}.hero{border-radius:0;box-shadow:none}.plan-card,.card,.stat-card,table{box-shadow:none;break-inside:avoid}.section{break-before:auto}a{color:inherit;text-decoration:none}}`;
|
|
10
|
+
export const reportStyles = `@import"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap";:root{--bg: #f0f4f8;--surface: #ffffff;--surface-soft: #f7f9fc;--border: #dde3ed;--text: #1e293b;--muted: #64748b;--green: #15803d;--green-soft: #dcfce7;--green-border: #86efac;--yellow: #a16207;--yellow-soft: #fef3c7;--yellow-border: #fde68a;--orange: #c2410c;--orange-soft: #ffedd5;--orange-border: #fdba74;--red: #b91c1c;--red-soft: #fee2e2;--red-border: #fca5a5;--blue: #1d4ed8;--blue-soft: #dbeafe;--blue-border: #93c5fd;--purple: #6d28d9;--purple-soft: #ede9fe;--shadow-sm: 0 1px 3px rgba(15, 23, 42, 0.07), 0 1px 2px rgba(15, 23, 42, 0.05);--shadow: 0 4px 16px rgba(15, 23, 42, 0.08), 0 1px 3px rgba(15, 23, 42, 0.05);--shadow-lg: 0 10px 40px rgba(15, 23, 42, 0.12), 0 2px 8px rgba(15, 23, 42, 0.06);--radius: 14px;--radius-sm: 8px}*,*::before,*::after{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;padding:0;background:var(--bg);color:var(--text);font-family:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;font-size:15px;line-height:1.6}.container{max-width:1320px;margin:0 auto;padding:32px 24px 80px}.hero{background:linear-gradient(135deg, #0f172a 0%, #1e3a6e 50%, #1d4ed8 100%);color:#fff;border-radius:24px;padding:36px 36px 28px;box-shadow:var(--shadow-lg);margin-bottom:28px;position:relative;overflow:hidden}.hero::before{content:"";position:absolute;inset:0;background:url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.03'%3E%3Cpath d='M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E");pointer-events:none}.hero-top{display:flex;align-items:flex-start;justify-content:space-between;gap:20px;flex-wrap:wrap}.hero-title-area h1{margin:0 0 6px;font-size:34px;font-weight:800;letter-spacing:-0.5px;line-height:1.1}.hero-title-area p{margin:0;color:hsla(0,0%,100%,.75);font-size:14px;max-width:500px}.hero-timestamp{background:hsla(0,0%,100%,.12);border:1px solid hsla(0,0%,100%,.2);border-radius:10px;padding:8px 14px;font-size:12px;color:hsla(0,0%,100%,.85);white-space:nowrap}.hero-grid{margin-top:24px;display:grid;grid-template-columns:repeat(auto-fit, minmax(200px, 1fr));gap:12px}.hero-item{background:hsla(0,0%,100%,.11);border:1px solid hsla(0,0%,100%,.16);border-radius:14px;padding:14px 18px;backdrop-filter:blur(4px);transition:background .2s}.hero-item:hover{background:hsla(0,0%,100%,.17)}.hero-item .label{font-size:11px;text-transform:uppercase;letter-spacing:.08em;opacity:.7;margin-bottom:5px}.hero-item .value{font-size:17px;font-weight:700;word-break:break-word}.disclaimer-banner{display:flex;align-items:flex-start;gap:16px;margin:24px 0 0;padding:20px 24px;background:#fefce8;border:2px solid #f59e0b;border-radius:12px;box-shadow:0 2px 8px rgba(245,158,11,.18)}.disclaimer-banner .disclaimer-icon{font-size:32px;line-height:1;flex-shrink:0;margin-top:2px}.disclaimer-banner .disclaimer-body{flex:1;color:#78350f}.disclaimer-banner .disclaimer-body strong:first-child{display:block;font-size:15px;font-weight:800;letter-spacing:.5px;text-transform:uppercase;color:#92400e;margin-bottom:8px}.disclaimer-banner .disclaimer-body p{margin:0 0 8px;font-size:14px;line-height:1.6}.disclaimer-banner .disclaimer-body p:last-child{margin-bottom:0}.disclaimer-banner .disclaimer-body ul{margin:6px 0 10px 18px;padding:0;font-size:14px;line-height:1.7}.disclaimer-banner .disclaimer-body .disclaimer-footer{font-size:12px;font-style:italic;color:#a16207;margin-top:8px}.section{margin-top:36px}.section-heading{display:flex;align-items:center;gap:10px;margin:0 0 16px;font-size:20px;font-weight:700;color:#0f172a}.section-heading::after{content:"";flex:1;height:1px;background:var(--border)}.summary-grid{display:grid;grid-template-columns:repeat(auto-fit, minmax(190px, 1fr));gap:12px}.stat-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px 18px;box-shadow:var(--shadow-sm);display:flex;flex-direction:column;gap:4px;transition:box-shadow .2s,transform .2s}.stat-card:hover{box-shadow:var(--shadow);transform:translateY(-1px)}.stat-card .label{font-size:12px;color:var(--muted);font-weight:500;text-transform:uppercase;letter-spacing:.04em}.stat-card .value{font-size:26px;font-weight:800;line-height:1.2}.stat-card .sub{font-size:12px;color:var(--muted)}.stat-card.blue .value{color:var(--blue)}.stat-card.green .value{color:var(--green)}.stat-card.yellow .value{color:var(--yellow)}.stat-card.red .value{color:var(--red)}.stat-card.purple .value{color:var(--purple)}.card{background:var(--surface);border:1px solid var(--border);border-radius:18px;padding:22px;box-shadow:var(--shadow);margin-bottom:20px}.badges{display:flex;flex-wrap:wrap;gap:8px}.badge{display:inline-flex;align-items:center;gap:5px;padding:5px 12px;border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;border:1px solid rgba(0,0,0,0)}.badge.green{background:var(--green-soft);color:var(--green);border-color:var(--green-border)}.badge.yellow{background:var(--yellow-soft);color:var(--yellow);border-color:var(--yellow-border)}.badge.orange{background:var(--orange-soft);color:var(--orange);border-color:var(--orange-border)}.badge.red{background:var(--red-soft);color:var(--red);border-color:var(--red-border)}.badge.blue{background:var(--blue-soft);color:var(--blue);border-color:var(--blue-border)}.badge.purple{background:var(--purple-soft);color:var(--purple);border-color:#c4b5fd}table{width:100%;border-collapse:collapse;background:var(--surface);border:1px solid var(--border);border-radius:14px;overflow:hidden;box-shadow:var(--shadow-sm);margin:10px 0 20px}table thead th{background:#f1f5fd;color:#374151;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;text-align:left;padding:11px 14px;border-bottom:1px solid var(--border)}table tbody td{padding:10px 14px;border-bottom:1px solid #e9eff7;vertical-align:middle;font-size:13px}table tbody tr:last-child td{border-bottom:none}table tbody tr:nth-child(even){background:#fafbff}table tbody tr:hover{background:#f0f6ff}.num-cell{text-align:right;font-variant-numeric:tabular-nums;font-weight:600}code{background:#eef2ff;color:#312e81;padding:2px 7px;border-radius:6px;font-size:12px;font-family:"SF Mono","Fira Code","Cascadia Code",monospace;word-break:break-word}.muted{color:var(--muted);font-size:13px}.toc-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px 20px;box-shadow:var(--shadow-sm);margin-bottom:20px}.toc-title{font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:10px}.toc-list{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:6px}.toc-link{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:999px;font-size:12px;font-weight:600;text-decoration:none;border:1px solid rgba(0,0,0,0);transition:opacity .15s}.toc-link:hover{opacity:.8;text-decoration:none}.toc-ok{background:var(--green-soft);color:var(--green);border-color:var(--green-border)}.toc-warn{background:var(--yellow-soft);color:var(--yellow);border-color:var(--yellow-border)}.toc-bad{background:var(--red-soft);color:var(--red);border-color:var(--red-border)}.toc-score{background:rgba(0,0,0,.08);border-radius:999px;padding:1px 7px;font-size:11px;font-weight:800}.plan-card{border-radius:20px;overflow:hidden;box-shadow:var(--shadow);border:1px solid var(--border);margin-bottom:28px}.plan-card-header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:16px;padding:22px 26px}.plan-card-body{background:var(--surface);padding:22px 26px}.plan-header-green{background:linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);border-bottom:1px solid #86efac}.plan-header-yellow{background:linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);border-bottom:1px solid #fde68a}.plan-header-red{background:linear-gradient(135deg, #fff1f2 0%, #fee2e2 100%);border-bottom:1px solid #fca5a5}.plan-header-left{display:flex;align-items:center;gap:14px}.plan-branch-icon{font-size:28px;width:48px;height:48px;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.06);border-radius:14px;flex-shrink:0}.plan-branch-name{font-size:17px;font-weight:800;color:#0f172a;word-break:break-all;display:flex;align-items:center;gap:8px;flex-wrap:wrap}.plan-branch-sub{font-size:13px;color:var(--muted);margin-top:2px}.existing-badge{display:inline-block;background:#dbeafe;color:#1e40af;border:1px solid #93c5fd;padding:1px 8px;border-radius:999px;font-size:11px;font-weight:700;margin-right:4px}.plan-header-right{display:flex;flex-direction:column;align-items:center;gap:4px}.gauge-svg{display:block}.plan-score-label{font-size:11px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}.score-label-green{color:var(--green)}.score-label-yellow{color:var(--yellow)}.score-label-red{color:var(--red)}.kpi-row{display:flex;flex-wrap:wrap;gap:12px;margin:16px 0 24px}.kpi{background:var(--surface-soft);border:1px solid var(--border);border-radius:12px;padding:10px 16px;min-width:90px;text-align:center}.kpi-v{font-size:22px;font-weight:800;color:#0f172a;line-height:1.1}.kpi-k{font-size:11px;color:var(--muted);margin-top:3px;text-transform:uppercase;letter-spacing:.04em}.recommendation-box{display:flex;align-items:flex-start;gap:10px;background:#f0f9ff;border:1px solid #bae6fd;border-radius:12px;padding:12px 16px;margin-bottom:16px;font-size:14px;color:#0369a1}.exclusion-note{display:flex;align-items:flex-start;gap:8px;background:#fffbeb;border:1px solid #fde68a;border-radius:10px;padding:10px 14px;margin-bottom:16px;font-size:13px;color:#92400e}.rec-icon{font-size:16px;flex-shrink:0}.section-title{font-size:14px;font-weight:700;color:#0f172a;margin:28px 0 10px;display:flex;align-items:center;gap:6px}.metrics-table thead th{background:#f8faff}.metric-code{background:#e0e7ff;color:#3730a3;font-weight:700}.metric-label{font-size:13px}.metric-value{font-weight:600;color:#374151}.metric-weight{font-weight:600;color:var(--muted)}.metric-contrib{font-weight:700;color:#0f172a;font-variant-numeric:tabular-nums}.pts-bar-wrap{height:6px;background:#e2e8f0;border-radius:999px;overflow:hidden;width:80px;display:inline-block;vertical-align:middle;margin-right:6px}.pts-bar{height:100%;border-radius:999px;transition:width .4s ease}.pts-good{color:var(--green);font-weight:700}.pts-warn{color:var(--yellow);font-weight:700}.pts-bad{color:var(--red);font-weight:700}.metrics-total-row td{background:#f8faff;font-size:13px}.metric-total-value{font-size:16px;font-weight:800;color:#0f172a}.ct-badge{display:inline-block;padding:3px 10px;border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase}.ct-added{background:#dcfce7;color:#15803d;border:1px solid #86efac}.ct-untracked{background:#ede9fe;color:#6d28d9;border:1px solid #c4b5fd}.ct-modified{background:#dbeafe;color:#1d4ed8;border:1px solid #93c5fd}.ct-deleted{background:#fee2e2;color:#b91c1c;border:1px solid #fca5a5}.ct-renamed{background:#fef3c7;color:#a16207;border:1px solid #fde68a}.ct-default{background:#f1f5f9;color:#475569}.ct-feat{background:#dbeafe;color:#1d4ed8}.ct-fix{background:#fee2e2;color:#b91c1c}.ct-chore{background:#f1f5f9;color:#475569}.ct-docs{background:#fef3c7;color:#a16207}.ct-test{background:#ede9fe;color:#6d28d9}.ct-refactor{background:#f0fdfa;color:#0f766e}.ct-style{background:#fdf4ff;color:#9333ea}.ct-perf{background:#fff7ed;color:#c2410c}.file-path code{max-width:380px;display:inline-block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.diff-bar{display:inline-flex;height:8px;border-radius:4px;overflow:hidden;background:#e2e8f0;vertical-align:middle;min-width:4px}.diff-add{background:#4ade80;height:100%}.diff-del{background:#f87171;height:100%}.diff-labels{font-size:11px;margin-left:6px;font-variant-numeric:tabular-nums}.add-lbl{color:var(--green);font-weight:700}.del-lbl{color:var(--red);font-weight:700;margin-left:3px}.prio-dot{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:999px;font-size:12px;font-weight:800}.prio-1{background:#fee2e2;color:#b91c1c}.prio-2{background:#ffedd5;color:#c2410c}.prio-3{background:#fef3c7;color:#a16207}.prio-4{background:#dbeafe;color:#1d4ed8}.prio-5{background:#f1f5f9;color:#475569}.divisible-yes{color:var(--green);font-size:13px;font-weight:700}.divisible-no{color:var(--red);font-size:13px;font-weight:700}.block-id{background:#f1f5f9;color:#334155;font-size:11px}.block-files code{font-size:10px;margin:1px}.timeline{position:relative;padding-left:28px}.timeline::before{content:"";position:absolute;left:9px;top:16px;bottom:16px;width:2px;background:#e2e8f0;border-radius:999px}.timeline-item{position:relative;margin-bottom:20px}.timeline-dot{position:absolute;left:-24px;top:14px;width:12px;height:12px;background:#3b82f6;border:2px solid #fff;border-radius:999px;box-shadow:0 0 0 2px #bfdbfe}.timeline-body{background:var(--surface-soft);border:1px solid var(--border);border-radius:12px;padding:12px 16px}.timeline-header{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-bottom:6px}.commit-index{background:#e2e8f0;color:#475569;padding:2px 8px;border-radius:999px;font-size:11px;font-weight:800}.commit-type-badge{padding:3px 10px;border-radius:999px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}.commit-msg{font-size:13px;color:#0f172a;background:#f8fafc;border:1px solid #e2e8f0;padding:2px 8px;border-radius:6px;word-break:break-word}.timeline-meta{display:flex;flex-wrap:wrap;gap:12px;font-size:12px;color:var(--muted);margin-bottom:8px}.timeline-meta span{display:flex;align-items:center;gap:3px}.commit-files-list{margin:0;padding-left:16px;list-style:disc}.commit-files-list li{margin:2px 0;font-size:12px}.commit-files-list code{font-size:11px;padding:1px 5px}.dep-arrow{color:var(--muted);font-size:16px;vertical-align:middle}.footer{margin-top:48px;border-top:1px solid var(--border);padding-top:20px;text-align:center;font-size:12px;color:var(--muted)}.copy-btn{background:none;border:1px solid rgba(0,0,0,.12);border-radius:6px;padding:1px 6px;cursor:pointer;font-size:11px;color:#64748b;line-height:1.4;transition:background .15s,color .15s;flex-shrink:0;vertical-align:middle}.copy-btn:hover{background:#f1f5f9;color:#0f172a;border-color:#94a3b8}.commit-type-pills{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}.commit-type-pill{display:inline-block;padding:1px 8px;border-radius:999px;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;opacity:.85}.toc-bar-wrap{width:50px;height:4px;background:rgba(0,0,0,.1);border-radius:999px;overflow:hidden;flex-shrink:0}.toc-bar{height:100%;border-radius:999px;transition:width .4s ease}.toc-bar-ok{background:var(--green)}.toc-bar-warn{background:var(--yellow)}.toc-bar-bad{background:var(--red)}.toc-commits{font-size:11px;opacity:.65;font-weight:500;white-space:nowrap}.back-to-top{text-align:right;padding-top:12px;border-top:1px solid var(--border);margin-top:20px}.back-link{font-size:12px;color:var(--muted);text-decoration:none;font-weight:600}.back-link:hover{color:#3b82f6;text-decoration:underline}.git-commands-block{background:#0f172a;border-radius:14px;padding:18px 20px;overflow-x:auto;margin-top:8px;box-shadow:inset 0 1px 4px rgba(0,0,0,.3)}.git-commands-block pre{margin:0;font-family:"JetBrains Mono","Fira Mono",ui-monospace,monospace;font-size:12px;line-height:1.8;color:#e2e8f0;white-space:pre}.git-commands-block .gc-comment{color:#4b6988;font-style:italic}.git-commands-block .gc-cmd{color:#7dd3fc;font-weight:700}.git-commands-block .gc-branch{color:#86efac}.git-commands-block .gc-file{color:#fca5a5}.git-commands-block .gc-msg{color:#fde68a}.ct-ci{background:#ecfdf5;color:#059669}.ct-revert{background:#fdf4ff;color:#c026d3}.origin-badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:9999px;font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;white-space:nowrap;border:1px solid rgba(0,0,0,0)}.origin-pushed{background:#cffafe;color:#0e7490;border-color:#67e8f9}.origin-local-commit{background:#ede9fe;color:#6d28d9;border-color:#c4b5fd}.origin-working-tree{background:#fef3c7;color:#a16207;border-color:#fde68a}.origin-untracked{background:#f1f5f9;color:#475569;border-color:#cbd5e1}@media(max-width: 960px){.hero{padding:24px 20px}.hero-title-area h1{font-size:26px}.plan-card-header{padding:18px}.plan-card-body{padding:18px}.file-path code{max-width:200px}}@media(max-width: 640px){.container{padding:16px 12px 60px}.kpi-row{gap:8px}.kpi{padding:8px 12px;min-width:72px}.kpi-v{font-size:18px}}@media print{body{background:#fff;font-size:12px}.container{max-width:100%;padding:0}.hero{border-radius:0;box-shadow:none}.plan-card,.card,.stat-card,table{box-shadow:none;break-inside:avoid}.section{break-before:auto}a{color:inherit;text-decoration:none}}`;
|
package/dist/output/report.js
CHANGED
|
@@ -153,6 +153,19 @@ function buildMetricsBreakdownTable(plan, config) {
|
|
|
153
153
|
</tbody>
|
|
154
154
|
</table>`;
|
|
155
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* Devuelve el badge HTML para el origen de un archivo en el árbol git.
|
|
158
|
+
*
|
|
159
|
+
* @param origin - Valor de `FileOrigin` (o `undefined` → trata como `"working-tree"`).
|
|
160
|
+
*/
|
|
161
|
+
function originHtmlBadge(origin) {
|
|
162
|
+
const cls = `origin-badge origin-${origin ?? "working-tree"}`;
|
|
163
|
+
const label = origin === "pushed" ? "REMOTO"
|
|
164
|
+
: origin === "local-commit" ? "LOCAL"
|
|
165
|
+
: origin === "untracked" ? "NUEVO"
|
|
166
|
+
: "WIP";
|
|
167
|
+
return `<span class="${esc(cls)}">${label}</span>`;
|
|
168
|
+
}
|
|
156
169
|
/**
|
|
157
170
|
* Genera una fila `<tr>` para la tabla de archivos con mini-barras visuales
|
|
158
171
|
* proporcionales al máximo de líneas del conjunto.
|
|
@@ -173,6 +186,7 @@ function buildFilesBarsRow(f, maxLines) {
|
|
|
173
186
|
const addPct = f.lines > 0 ? Math.round((f.additions / f.lines) * totalBar) : 0;
|
|
174
187
|
const delPct = totalBar - addPct;
|
|
175
188
|
return `<tr>
|
|
189
|
+
<td>${originHtmlBadge(f.origin)}</td>
|
|
176
190
|
<td><span class="ct-badge ${ctClass}">${esc(changeTypeLabel(f.changeType))}</span></td>
|
|
177
191
|
<td class="file-path"><code>${esc(f.path)}</code></td>
|
|
178
192
|
<td class="num-cell">${f.lines}</td>
|
|
@@ -575,6 +589,7 @@ export function renderHtmlReport(input) {
|
|
|
575
589
|
<table>
|
|
576
590
|
<thead>
|
|
577
591
|
<tr>
|
|
592
|
+
<th>Origen</th>
|
|
578
593
|
<th>Tipo</th>
|
|
579
594
|
<th>Archivo</th>
|
|
580
595
|
<th>Líneas</th>
|
|
@@ -764,7 +779,10 @@ export function renderScoreHtmlReport(input) {
|
|
|
764
779
|
<div class="hero-item"><div class="label">Rama base</div><div class="value">⎇ ${esc(baseBranch)}</div></div>
|
|
765
780
|
<div class="hero-item"><div class="label">Score actual</div><div class="value">${result.complexity.toFixed(2)}</div></div>
|
|
766
781
|
<div class="hero-item"><div class="label">Score objetivo</div><div class="value">${config.targetScore} / 5.00</div></div>
|
|
767
|
-
<div class="hero-item"
|
|
782
|
+
<div class="hero-item">
|
|
783
|
+
<div class="label">${input.scoredFileCount !== undefined && input.scoredFileCount !== totalFiles ? "Archivos scored / total" : "Archivos"}</div>
|
|
784
|
+
<div class="value">${input.scoredFileCount !== undefined && input.scoredFileCount !== totalFiles ? `${input.scoredFileCount} / ${totalFiles}` : totalFiles}</div>
|
|
785
|
+
</div>
|
|
768
786
|
<div class="hero-item"><div class="label">Líneas totales</div><div class="value">${totalLines}</div></div>
|
|
769
787
|
<div class="hero-item"><div class="label">Commits adelantados</div><div class="value">${commitCount}</div></div>
|
|
770
788
|
<div class="hero-item"><div class="label">Estado</div><div class="value score-label-${scoreClass}">${esc(label)}</div></div>
|
|
@@ -773,6 +791,12 @@ export function renderScoreHtmlReport(input) {
|
|
|
773
791
|
|
|
774
792
|
<section class="section">
|
|
775
793
|
<h2 class="section-heading">🎯 Score</h2>
|
|
794
|
+
${input.scoredFileCount !== undefined && input.scoredFileCount !== totalFiles
|
|
795
|
+
? `<div class="recommendation-box" style="margin-bottom:16px">
|
|
796
|
+
<span class="rec-icon">ℹ️</span>
|
|
797
|
+
<span>Score calculado sobre <strong>${input.scoredFileCount} archivos del working tree</strong> (cambios no commiteados) de <strong>${totalFiles} archivos</strong> analizados. Los archivos <span class="origin-badge origin-pushed">REMOTO</span> y <span class="origin-badge origin-local-commit">LOCAL</span> se muestran en la tabla pero <strong>no afectan el score</strong>.</span>
|
|
798
|
+
</div>`
|
|
799
|
+
: ""}
|
|
776
800
|
<div class="card" style="display:flex;align-items:center;gap:32px;padding:24px">
|
|
777
801
|
${buildScoreGaugeSvg(result.complexity, config.targetScore)}
|
|
778
802
|
<div>
|
|
@@ -803,9 +827,10 @@ export function renderScoreHtmlReport(input) {
|
|
|
803
827
|
<h2 class="section-heading">📈 Valores brutos usados</h2>
|
|
804
828
|
<div class="summary-grid">
|
|
805
829
|
<div class="stat-card blue"><div class="label">Commits</div><div class="value">${commitCount}</div></div>
|
|
830
|
+
<div class="stat-card blue"><div class="label">Archivos scored (WIP)</div><div class="value">${input.scoredFileCount ?? totalFiles}</div></div>
|
|
806
831
|
<div class="stat-card blue"><div class="label">Archivos / commit</div><div class="value">${filesPerCommit}</div></div>
|
|
832
|
+
<div class="stat-card blue"><div class="label">Líneas scored (WIP)</div><div class="value">${input.scoredLines ?? totalLines}</div></div>
|
|
807
833
|
<div class="stat-card blue"><div class="label">Líneas / commit (avg)</div><div class="value">${avgLinesPerCommit}</div></div>
|
|
808
|
-
<div class="stat-card blue"><div class="label">Total de líneas</div><div class="value">${totalLines}</div></div>
|
|
809
834
|
<div class="stat-card green"><div class="label">Líneas agregadas</div><div class="value">+${totalAdditions}</div></div>
|
|
810
835
|
<div class="stat-card red"><div class="label">Líneas eliminadas</div><div class="value">-${totalDeletions}</div></div>
|
|
811
836
|
</div>
|
|
@@ -815,7 +840,7 @@ export function renderScoreHtmlReport(input) {
|
|
|
815
840
|
<h2 class="section-heading">📄 Detalle de archivos</h2>
|
|
816
841
|
<table>
|
|
817
842
|
<thead>
|
|
818
|
-
<tr><th>Tipo</th><th>Archivo</th><th>Líneas</th><th>Cambios (+/-)</th><th>Prioridad</th></tr>
|
|
843
|
+
<tr><th>Origen</th><th>Tipo</th><th>Archivo</th><th>Líneas</th><th>Cambios (+/-)</th><th>Prioridad</th></tr>
|
|
819
844
|
</thead>
|
|
820
845
|
<tbody>
|
|
821
846
|
${fileStats.map((f) => buildFilesBarsRow(f, maxLinesForBar)).join("")}
|
package/dist/output/ui.js
CHANGED
|
@@ -338,8 +338,7 @@ function summaryCard(title, items) {
|
|
|
338
338
|
});
|
|
339
339
|
card(title, lines, "cyan");
|
|
340
340
|
}
|
|
341
|
-
/**
|
|
342
|
-
* Genera la representación visual de un bloque para tablas y listas.
|
|
341
|
+
/** Genera la representación visual de un bloque para tablas y listas.
|
|
343
342
|
* Distingue bloques grandes (`large::`) de bloques agrupados normales.
|
|
344
343
|
*/
|
|
345
344
|
function blockLabel(id) {
|
|
@@ -352,6 +351,25 @@ function blockLabel(id) {
|
|
|
352
351
|
}
|
|
353
352
|
return `${badge("block", "blue")} ${chalk.whiteBright(id)}`;
|
|
354
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Genera una pastilla de terminal que indica el origen de un archivo en el árbol git.
|
|
356
|
+
*
|
|
357
|
+
* - REMOTO (cyan) : existe en commits ya publicados al remoto.
|
|
358
|
+
* - LOCAL (magenta) : commiteado localmente, aún no publicado.
|
|
359
|
+
* - WIP (yellow) : cambio en el working tree (staged o unstaged).
|
|
360
|
+
* - NUEVO (white) : archivo nuevo sin seguimiento git.
|
|
361
|
+
*
|
|
362
|
+
* @param origin - Valor de `FileOrigin` o `undefined` (trata como `"working-tree"`).
|
|
363
|
+
*/
|
|
364
|
+
function originBadge(origin) {
|
|
365
|
+
switch (origin) {
|
|
366
|
+
case "pushed": return badge("REMOTO", "cyan");
|
|
367
|
+
case "local-commit": return badge("LOCAL", "magenta");
|
|
368
|
+
case "untracked": return badge("NUEVO", "white");
|
|
369
|
+
case "working-tree":
|
|
370
|
+
default: return badge("WIP", "yellow");
|
|
371
|
+
}
|
|
372
|
+
}
|
|
355
373
|
/** Imprime una línea horizontal de 100 guiones en gris para separar entradas dentro de una sección. */
|
|
356
374
|
function divider() {
|
|
357
375
|
process.stdout.write(chalk.gray("─".repeat(100)) + "\n");
|
|
@@ -410,6 +428,7 @@ export const ui = {
|
|
|
410
428
|
scoreColor,
|
|
411
429
|
score,
|
|
412
430
|
blockLabel,
|
|
431
|
+
originBadge,
|
|
413
432
|
divider,
|
|
414
433
|
confirm,
|
|
415
434
|
prompt,
|
package/package.json
CHANGED
package/scripts/postinstall.cjs
CHANGED
|
@@ -83,30 +83,56 @@ async function main() {
|
|
|
83
83
|
);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
// ── Añadir
|
|
86
|
+
// ── Añadir entradas al .gitignore del proyecto consumidor ──────────────
|
|
87
87
|
// La config es local por equipo/desarrollador y no debe versionarse.
|
|
88
|
-
|
|
89
|
-
const
|
|
88
|
+
// Los artefactos generados (reportes HTML, plan JSON, historial) tampoco.
|
|
89
|
+
const gitignorePath = join(targetDir, ".gitignore");
|
|
90
|
+
|
|
91
|
+
const gitignoreEntries = [
|
|
92
|
+
{
|
|
93
|
+
entry: "pr-split-advisor.config.json",
|
|
94
|
+
comment: "# pr-split-advisor — config local (no compartir en el repositorio)"
|
|
95
|
+
},
|
|
96
|
+
{ entry: "pr-split-report.html", comment: null },
|
|
97
|
+
{ entry: "pr-split-score.html", comment: null },
|
|
98
|
+
{ entry: "pr-split-plan.json", comment: null },
|
|
99
|
+
{ entry: ".pr-split-history.json", comment: null }
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
// Cabecera del grupo de artefactos: solo se añade si al menos uno de ellos
|
|
103
|
+
// no está ya en el .gitignore, y solo una vez para todo el grupo.
|
|
104
|
+
const artifactEntries = ["pr-split-report.html", "pr-split-score.html", "pr-split-plan.json", ".pr-split-history.json"];
|
|
90
105
|
|
|
91
106
|
try {
|
|
92
107
|
const currentContent = existsSync(gitignorePath)
|
|
93
108
|
? readFileSync(gitignorePath, "utf-8")
|
|
94
109
|
: "";
|
|
95
110
|
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
const existingLines = currentContent.split("\n").map((l) => l.trim());
|
|
112
|
+
let block = currentContent.length && !currentContent.endsWith("\n") ? "\n" : "";
|
|
113
|
+
let addedAny = false;
|
|
114
|
+
let artifactHeaderAdded = false;
|
|
115
|
+
|
|
116
|
+
for (const { entry, comment } of gitignoreEntries) {
|
|
117
|
+
if (existingLines.includes(entry)) continue;
|
|
100
118
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
"\n# pr-split-advisor
|
|
105
|
-
|
|
119
|
+
const isArtifact = artifactEntries.includes(entry);
|
|
120
|
+
if (isArtifact && !artifactHeaderAdded && !comment) {
|
|
121
|
+
// Escribir la cabecera de artefactos la primera vez que haya uno nuevo
|
|
122
|
+
block += "\n# pr-split-advisor \u2014 artefactos generados\n";
|
|
123
|
+
artifactHeaderAdded = true;
|
|
124
|
+
} else if (comment) {
|
|
125
|
+
block += "\n" + comment + "\n";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
block += entry + "\n";
|
|
129
|
+
addedAny = true;
|
|
130
|
+
}
|
|
106
131
|
|
|
132
|
+
if (addedAny) {
|
|
107
133
|
appendFileSync(gitignorePath, block, "utf-8");
|
|
108
134
|
console.log(
|
|
109
|
-
"[pr-split-advisor] ✔
|
|
135
|
+
"[pr-split-advisor] ✔ Añadidas entradas de pr-split-advisor al .gitignore"
|
|
110
136
|
);
|
|
111
137
|
}
|
|
112
138
|
} catch (err) {
|