@aisdlc/core 1.4.0 → 1.4.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/dist/index.cjs CHANGED
@@ -7512,6 +7512,31 @@ function inferHumanTrailers(trailers, linesAdded, linesDeleted) {
7512
7512
  function inferMissingTrailers(trailers, linesAdded, linesDeleted) {
7513
7513
  return trailers.authorType === "agent" ? inferAgentTrailers(trailers, linesAdded, linesDeleted) : inferHumanTrailers(trailers, linesAdded, linesDeleted);
7514
7514
  }
7515
+ function resolveGitRef(cwd, ref) {
7516
+ if (!ref || ref === "HEAD") return ref;
7517
+ const candidates = [
7518
+ ref,
7519
+ `feat/${ref}`,
7520
+ `release/${ref}`,
7521
+ `bug/${ref}`,
7522
+ `task/${ref}`,
7523
+ `origin/${ref}`,
7524
+ `origin/feat/${ref}`,
7525
+ `origin/release/${ref}`,
7526
+ `origin/bug/${ref}`
7527
+ ];
7528
+ for (const cand of candidates) {
7529
+ try {
7530
+ child_process.execFileSync("git", ["rev-parse", "--verify", "--quiet", cand], {
7531
+ cwd,
7532
+ stdio: ["pipe", "pipe", "pipe"]
7533
+ });
7534
+ return cand;
7535
+ } catch {
7536
+ }
7537
+ }
7538
+ return ref;
7539
+ }
7515
7540
  function fetchGitLogOutput(cwd, baseRef, headRef, format) {
7516
7541
  const rangeArg = baseRef ? `${baseRef}..${headRef}` : headRef;
7517
7542
  try {
@@ -7555,8 +7580,10 @@ function buildCommitKpiRecord(chunk) {
7555
7580
  }
7556
7581
  function extractCommitKpisFromRange(baseRef, headRef = "HEAD", options = {}) {
7557
7582
  const cwd = options.cwd || process.cwd();
7583
+ const resolvedBase = resolveGitRef(cwd, baseRef);
7584
+ const resolvedHead = resolveGitRef(cwd, headRef);
7558
7585
  const format = "---AI_SDLC_COMMIT---%n%H%x1f%an%x1f%ae%x1f%aI%x1f%s%n%B%n---AI_SDLC_BODY_END---";
7559
- const rawOutput = fetchGitLogOutput(cwd, baseRef, headRef, format);
7586
+ const rawOutput = fetchGitLogOutput(cwd, resolvedBase, resolvedHead, format);
7560
7587
  if (!rawOutput) return [];
7561
7588
  const chunks = parseRawLogChunks(rawOutput);
7562
7589
  const records = [];
@@ -7880,8 +7907,7 @@ function accumulateRecord(target, record) {
7880
7907
  target.completionTokens += record.trailers.completionTokens;
7881
7908
  target.estimatedCostUsd = calculateEstimatedCost(target.promptTokens, target.completionTokens);
7882
7909
  }
7883
- function aggregatePrKpis(baseRef, headRef = "HEAD", options = {}) {
7884
- const records = extractCommitKpisFromRange(baseRef, headRef, options);
7910
+ function aggregateCommitKpiRecords(records, baseRef = "main", headRef = "HEAD") {
7885
7911
  const humanSummary = createEmptySummary();
7886
7912
  const byModel = {};
7887
7913
  let totalLinesAdded = 0;
@@ -7907,6 +7933,8 @@ function aggregatePrKpis(baseRef, headRef = "HEAD", options = {}) {
7907
7933
  accumulateRecord(humanSummary, record);
7908
7934
  }
7909
7935
  }
7936
+ const totalTokens = totalPrompt + totalCompletion;
7937
+ const estimatedCostUsd = calculateEstimatedCost(totalPrompt, totalCompletion);
7910
7938
  return {
7911
7939
  baseRef,
7912
7940
  headRef,
@@ -7916,14 +7944,18 @@ function aggregatePrKpis(baseRef, headRef = "HEAD", options = {}) {
7916
7944
  totalActiveTimeSeconds: totalActiveTime,
7917
7945
  totalPromptTokens: totalPrompt,
7918
7946
  totalCompletionTokens: totalCompletion,
7919
- totalTokens: totalPrompt + totalCompletion,
7920
- estimatedCostUsd: calculateEstimatedCost(totalPrompt, totalCompletion),
7947
+ totalTokens,
7948
+ estimatedCostUsd,
7921
7949
  humanSummary,
7922
7950
  byModel,
7923
7951
  isBugPr,
7924
7952
  commitRecords: records
7925
7953
  };
7926
7954
  }
7955
+ function aggregatePrKpis(baseRef, headRef = "HEAD", options = {}) {
7956
+ const records = extractCommitKpisFromRange(baseRef, headRef, options);
7957
+ return aggregateCommitKpiRecords(records, baseRef, headRef);
7958
+ }
7927
7959
  function formatMinutes(seconds) {
7928
7960
  const mins = Math.round(seconds / 60);
7929
7961
  return mins > 0 ? `${mins} min` : `${seconds}s`;
@@ -8059,7 +8091,10 @@ function computeReworkRatios(totalTime, totalTokens, totalCost, reworkTime, rewo
8059
8091
  };
8060
8092
  }
8061
8093
  function aggregateReleaseKpis(releaseBranch, baseBranch = "main", options = {}) {
8062
- const records = extractCommitKpisFromRange(baseBranch, releaseBranch, options);
8094
+ const cwd = options.cwd || process.cwd();
8095
+ const resolvedRelease = resolveGitRef(cwd, releaseBranch);
8096
+ const resolvedBase = resolveGitRef(cwd, baseBranch);
8097
+ const records = extractCommitKpisFromRange(resolvedBase, resolvedRelease, options);
8063
8098
  const statsMap = {};
8064
8099
  const bugList = [];
8065
8100
  const totals = { linesAdded: 0, linesDeleted: 0, time: 0, tokens: 0, reworkTime: 0, reworkTokens: 0 };
@@ -8074,8 +8109,8 @@ function aggregateReleaseKpis(releaseBranch, baseBranch = "main", options = {})
8074
8109
  const reworkCostUsd = calculateCost(totals.reworkTokens * 0.8, totals.reworkTokens * 0.2);
8075
8110
  const ratios = computeReworkRatios(totals.time, totals.tokens, totalCostUsd, totals.reworkTime, totals.reworkTokens, reworkCostUsd);
8076
8111
  return {
8077
- releaseBranch,
8078
- baseBranch,
8112
+ releaseBranch: resolvedRelease,
8113
+ baseBranch: resolvedBase,
8079
8114
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
8080
8115
  totalCommits: records.length,
8081
8116
  totalLinesAdded: totals.linesAdded,
@@ -8142,7 +8177,7 @@ function formatReleaseKpiMarkdown(report) {
8142
8177
  ].join("\n");
8143
8178
  }
8144
8179
  function writeReleaseKpiReport(report, options = {}) {
8145
- const root = options.outputDir || path5__namespace.join(process.cwd(), "reports", "releases");
8180
+ const root = options.outputDir || (options.rootDir ? path5__namespace.join(options.rootDir, "reports", "releases") : path5__namespace.join(process.cwd(), "reports", "releases"));
8146
8181
  if (!fs6__namespace.existsSync(root)) {
8147
8182
  fs6__namespace.mkdirSync(root, { recursive: true });
8148
8183
  }
@@ -9796,6 +9831,86 @@ var DASHBOARD_CLIENT_SCRIPT = `
9796
9831
  if (backdrop) backdrop.style.display = 'block';
9797
9832
  }
9798
9833
 
9834
+ function filterTelemetryByBranch(branchKey) {
9835
+ if (typeof telemetryData === 'undefined' || !telemetryData) return;
9836
+ var data = telemetryData[branchKey] || null;
9837
+ if (!data) return;
9838
+
9839
+ var isTotal = branchKey === '__total__';
9840
+ var label = isTotal ? 'Totalizador' : branchKey;
9841
+ var badgeText = isTotal ? 'Vista: Totalizador Global' : ('Rama: ' + branchKey);
9842
+
9843
+ var cardBranchLabel = document.getElementById('card-branch-label');
9844
+ if (cardBranchLabel) cardBranchLabel.textContent = label;
9845
+
9846
+ var tableBranchLabel = document.getElementById('table-branch-label');
9847
+ if (tableBranchLabel) tableBranchLabel.textContent = isTotal ? 'Totalizador (Todas las ramas)' : branchKey;
9848
+
9849
+ var badgeElem = document.getElementById('telemetry-badge-container');
9850
+ if (badgeElem) {
9851
+ badgeElem.innerHTML = '<span class="badge" style="background: rgba(56, 189, 248, 0.2); color: #38bdf8; border: 1px solid #38bdf8; font-size: 0.75rem; padding: 0.25rem 0.6rem; border-radius: 4px;">' + badgeText + '</span>';
9852
+ }
9853
+
9854
+ var elCommits = document.getElementById('card-commits');
9855
+ if (elCommits) elCommits.textContent = data.totalCommits;
9856
+ var elLines = document.getElementById('card-lines');
9857
+ if (elLines) elLines.textContent = '+' + data.linesAdded + ' / -' + data.linesDeleted + ' lines';
9858
+
9859
+ var elTime = document.getElementById('card-time');
9860
+ if (elTime) elTime.textContent = Math.round(data.totalActiveTimeSeconds / 60) + 'm';
9861
+ var elTimeSec = document.getElementById('card-time-seconds');
9862
+ if (elTimeSec) elTimeSec.textContent = data.totalActiveTimeSeconds + 's accumulated';
9863
+
9864
+ var elTokens = document.getElementById('card-tokens');
9865
+ if (elTokens) elTokens.textContent = Number(data.totalTokens).toLocaleString('en-US');
9866
+ var elTokensDetail = document.getElementById('card-tokens-detail');
9867
+ if (elTokensDetail) elTokensDetail.textContent = 'In: ' + Number(data.totalPromptTokens).toLocaleString('en-US') + ' | Out: ' + Number(data.totalCompletionTokens).toLocaleString('en-US');
9868
+
9869
+ var elCost = document.getElementById('card-cost');
9870
+ if (elCost) elCost.textContent = '$' + Number(data.estimatedCostUsd).toFixed(2);
9871
+
9872
+ var tbody = document.getElementById('telemetry-table-tbody');
9873
+ if (tbody) {
9874
+ var html = '';
9875
+ if (data.humanSummary && data.humanSummary.commits > 0) {
9876
+ html += '<tr>' +
9877
+ '<td><strong>Human (Developer)</strong></td>' +
9878
+ '<td><span class="badge" style="background:#334155;">HUMAN</span></td>' +
9879
+ '<td>' + data.humanSummary.commits + '</td>' +
9880
+ '<td>+' + data.humanSummary.linesAdded + ' / -' + data.humanSummary.linesDeleted + '</td>' +
9881
+ '<td>' + Math.round(data.humanSummary.activeTimeSeconds / 60) + 'm</td>' +
9882
+ '<td>0</td><td>\u2014</td>' +
9883
+ '</tr>';
9884
+ }
9885
+ if (data.byModel) {
9886
+ var models = Object.keys(data.byModel);
9887
+ for (var i = 0; i < models.length; i++) {
9888
+ var m = models[i];
9889
+ var summary = data.byModel[m];
9890
+ var safeModel = String(m).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
9891
+ html += '<tr>' +
9892
+ '<td><strong><code>' + safeModel + '</code></strong></td>' +
9893
+ '<td><span class="badge badge-green">AGENT</span></td>' +
9894
+ '<td>' + summary.commits + '</td>' +
9895
+ '<td>+' + summary.linesAdded + ' / -' + summary.linesDeleted + '</td>' +
9896
+ '<td>' + Math.round(summary.activeTimeSeconds / 60) + 'm</td>' +
9897
+ '<td>' + (summary.promptTokens + summary.completionTokens).toLocaleString('en-US') + '</td>' +
9898
+ '<td>$' + Number(summary.estimatedCostUsd).toFixed(2) + '</td>' +
9899
+ '</tr>';
9900
+ }
9901
+ }
9902
+ html += '<tr style="font-weight: bold; background: rgba(255,255,255,0.05);">' +
9903
+ '<td>' + (isTotal ? 'TOTAL REPOSITORIO' : 'TOTAL RAMA') + '</td><td>\u2014</td>' +
9904
+ '<td>' + data.totalCommits + '</td>' +
9905
+ '<td>+' + data.linesAdded + ' / -' + data.linesDeleted + '</td>' +
9906
+ '<td>' + Math.round(data.totalActiveTimeSeconds / 60) + 'm</td>' +
9907
+ '<td>' + Number(data.totalTokens).toLocaleString('en-US') + '</td>' +
9908
+ '<td>$' + Number(data.estimatedCostUsd).toFixed(2) + ' USD</td>' +
9909
+ '</tr>';
9910
+ tbody.innerHTML = html;
9911
+ }
9912
+ }
9913
+
9799
9914
  function closeTaskSidebar() {
9800
9915
  var sidebar = document.getElementById('task-sidebar');
9801
9916
  var backdrop = document.getElementById('task-backdrop');
@@ -10110,13 +10225,39 @@ function renderManualsTab(manuals) {
10110
10225
  `<div class="chart-container" id="manual-viewer-container" style="display: none;"><h3 id="manual-viewer-title"></h3><div id="manual-viewer" style="font-size: 0.82rem; line-height: 1.5; color: #cbd5e1;"></div></div>`
10111
10226
  ].join("\n");
10112
10227
  }
10113
- function renderTelemetryCardsHtml(kpi) {
10228
+ function renderBranchOption(b, activeBranch) {
10229
+ const isAct = b === activeBranch ? " (Rama activa)" : "";
10230
+ return ` <option value="${escapeHtml(b)}">${escapeHtml(b)}${isAct}</option>`;
10231
+ }
10232
+ function renderTelemetrySelect(options) {
10233
+ return [
10234
+ ` <div style="display: flex; align-items: center; gap: 0.6rem;">`,
10235
+ ` <label for="telemetry-branch-select" style="font-size: 0.85rem; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em;">Filtrar por Rama:</label>`,
10236
+ ` <select id="telemetry-branch-select" onchange="filterTelemetryByBranch(this.value)" class="form-select" style="background: var(--panel-bg, #1e293b); color: var(--text-main, #f8fafc); border: 1px solid var(--border-color, #475569); border-radius: 6px; padding: 0.4rem 0.8rem; font-size: 0.85rem; cursor: pointer; outline: none; font-weight: 500;">`,
10237
+ options,
10238
+ ` </select>`,
10239
+ ` </div>`
10240
+ ].join("\n");
10241
+ }
10242
+ function renderTelemetryFilterBarHtml(branches, activeBranch) {
10243
+ const options = [
10244
+ ` <option value="__total__" selected>\u{1F31F} Totalizador (Todas las ramas)</option>`,
10245
+ ...branches.map((b) => renderBranchOption(b, activeBranch))
10246
+ ].join("\n");
10247
+ const selectHtml = renderTelemetrySelect(options);
10248
+ const badgeHtml = ` <div id="telemetry-badge-container"><span class="badge" style="background: rgba(56, 189, 248, 0.2); color: #38bdf8; border: 1px solid #38bdf8; font-size: 0.75rem; padding: 0.25rem 0.6rem; border-radius: 4px;">Vista: Totalizador Global</span></div>`;
10249
+ return `<div class="telemetry-filter-bar" style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.25rem; flex-wrap: wrap; gap: 0.75rem;">
10250
+ ${selectHtml}
10251
+ ${badgeHtml}
10252
+ </div>`;
10253
+ }
10254
+ function renderTelemetryCardsHtml(kpi, displayLabel = "Totalizador") {
10114
10255
  return [
10115
10256
  `<div class="grid-cards">`,
10116
- ` <div class="metric-card"><span class="title">Commits in Active Branch</span><span class="value">${kpi.totalCommits}</span><span class="subtitle">+${kpi.linesAdded} / -${kpi.linesDeleted} lines</span></div>`,
10117
- ` <div class="metric-card"><span class="title">Active Development Time</span><span class="value">${Math.round(kpi.totalActiveTimeSeconds / 60)}m</span><span class="subtitle">${kpi.totalActiveTimeSeconds}s accumulated</span></div>`,
10118
- ` <div class="metric-card"><span class="title">Consumed Tokens</span><span class="value">${kpi.totalTokens.toLocaleString("en-US")}</span><span class="subtitle">In: ${kpi.totalPromptTokens.toLocaleString()} | Out: ${kpi.totalCompletionTokens.toLocaleString()}</span></div>`,
10119
- ` <div class="metric-card"><span class="title">Estimated Compute Cost</span><span class="value">$${kpi.estimatedCostUsd.toFixed(2)}</span><span class="subtitle">Weighted USD</span></div>`,
10257
+ ` <div class="metric-card"><span class="title">Commits (<span id="card-branch-label">${escapeHtml(displayLabel)}</span>)</span><span class="value" id="card-commits">${kpi.totalCommits}</span><span class="subtitle" id="card-lines">+${kpi.linesAdded} / -${kpi.linesDeleted} lines</span></div>`,
10258
+ ` <div class="metric-card"><span class="title">Active Development Time</span><span class="value" id="card-time">${Math.round(kpi.totalActiveTimeSeconds / 60)}m</span><span class="subtitle" id="card-time-seconds">${kpi.totalActiveTimeSeconds}s accumulated</span></div>`,
10259
+ ` <div class="metric-card"><span class="title">Consumed Tokens</span><span class="value" id="card-tokens">${kpi.totalTokens.toLocaleString("en-US")}</span><span class="subtitle" id="card-tokens-detail">In: ${kpi.totalPromptTokens.toLocaleString()} | Out: ${kpi.totalCompletionTokens.toLocaleString()}</span></div>`,
10260
+ ` <div class="metric-card"><span class="title">Estimated Compute Cost</span><span class="value" id="card-cost">$${kpi.estimatedCostUsd.toFixed(2)}</span><span class="subtitle">Weighted USD</span></div>`,
10120
10261
  `</div>`
10121
10262
  ].join("\n");
10122
10263
  }
@@ -10146,27 +10287,27 @@ function renderHumanTelemetryRow(human) {
10146
10287
  `</tr>`
10147
10288
  ].join("");
10148
10289
  }
10149
- function renderTotalBranchRow(kpi) {
10290
+ function renderTotalBranchRow(kpi, isTotal = false) {
10150
10291
  const mins = Math.round(kpi.totalActiveTimeSeconds / 60);
10151
10292
  const tokens = kpi.totalTokens.toLocaleString("en-US");
10152
10293
  const cost = kpi.estimatedCostUsd.toFixed(2);
10294
+ const label = isTotal ? "TOTAL REPOSITORIO" : "TOTAL RAMA";
10153
10295
  return [
10154
10296
  `<tr style="font-weight: bold; background: rgba(255,255,255,0.05);">`,
10155
- ` <td>TOTAL BRANCH</td><td>\u2014</td><td>${kpi.totalCommits}</td><td>+${kpi.linesAdded} / -${kpi.linesDeleted}</td>`,
10297
+ ` <td>${label}</td><td>\u2014</td><td>${kpi.totalCommits}</td><td>+${kpi.linesAdded} / -${kpi.linesDeleted}</td>`,
10156
10298
  ` <td>${mins}m</td><td>${tokens}</td><td>$${cost} USD</td>`,
10157
10299
  `</tr>`
10158
10300
  ].join("");
10159
10301
  }
10160
- function renderActiveTelemetrySection(kpi, branch) {
10161
- const cardsHtml = renderTelemetryCardsHtml(kpi);
10302
+ function renderTelemetryTable(kpi, displayLabel) {
10162
10303
  const humanRow = renderHumanTelemetryRow(kpi.humanSummary);
10163
10304
  const modelRows = Object.entries(kpi.byModel).map(([m, s]) => renderModelRow(m, s)).join("\n");
10164
- const totalRow = renderTotalBranchRow(kpi);
10305
+ const totalRow = renderTotalBranchRow(kpi, displayLabel === "Totalizador");
10306
+ const titleLabel = "Totalizador (Todas las ramas)" ;
10165
10307
  return [
10166
- cardsHtml,
10167
10308
  `<div class="chart-container">`,
10168
- ` <h3>Telemetry Breakdown by Author and Model (${branch || "HEAD"})</h3>`,
10169
- ` <table class="data-table"><thead><tr><th>Entity / Model</th><th>Type</th><th>Commits</th><th>Lines (+/-)</th><th>Active Time</th><th>Consumed Tokens</th><th>Estimated Cost ($ USD)</th></tr></thead><tbody>`,
10309
+ ` <h3>Telemetry Breakdown by Author and Model (<span id="table-branch-label">${escapeHtml(titleLabel)}</span>)</h3>`,
10310
+ ` <table class="data-table"><thead><tr><th>Entity / Model</th><th>Type</th><th>Commits</th><th>Lines (+/-)</th><th>Active Time</th><th>Consumed Tokens</th><th>Estimated Cost ($ USD)</th></tr></thead><tbody id="telemetry-table-tbody">`,
10170
10311
  humanRow,
10171
10312
  modelRows,
10172
10313
  totalRow,
@@ -10174,6 +10315,14 @@ function renderActiveTelemetrySection(kpi, branch) {
10174
10315
  `</div>`
10175
10316
  ].join("\n");
10176
10317
  }
10318
+ function renderActiveTelemetrySection(kpi, displayLabel, branches, activeBranch) {
10319
+ const filterBarHtml = renderTelemetryFilterBarHtml(branches, activeBranch);
10320
+ const cardsHtml = renderTelemetryCardsHtml(kpi, displayLabel);
10321
+ const tableHtml = renderTelemetryTable(kpi, displayLabel);
10322
+ return `${filterBarHtml}
10323
+ ${cardsHtml}
10324
+ ${tableHtml}`;
10325
+ }
10177
10326
  function renderReleaseHistoryRow(h) {
10178
10327
  return [
10179
10328
  `<tr>`,
@@ -10182,10 +10331,25 @@ function renderReleaseHistoryRow(h) {
10182
10331
  `</tr>`
10183
10332
  ].join("");
10184
10333
  }
10334
+ function renderHistoricalKpisTable(history) {
10335
+ if (history.length === 0) {
10336
+ return `<p style="color: var(--text-muted); font-size: 0.85rem;">No consolidated release reports recorded yet in <code>reports/releases/*.kpis.json</code>.</p>`;
10337
+ }
10338
+ const rows = history.map(renderReleaseHistoryRow).join("\n");
10339
+ return `<table class="data-table"><thead><tr><th>Release</th><th>Date</th><th>KLoC</th><th>Commits</th><th>Confirmed Bugs</th><th>DIR (Bugs/KLoC)</th><th>Rework (%)</th><th>Cost ($ USD)</th></tr></thead><tbody>${rows}</tbody></table>`;
10340
+ }
10341
+ function resolveBranchFilterKeys(metricsData) {
10342
+ const keys = Object.keys(metricsData.branchTelemetries || {});
10343
+ if (metricsData.activeBranch && metricsData.activeBranch !== "HEAD" && !keys.includes(metricsData.activeBranch)) {
10344
+ keys.unshift(metricsData.activeBranch);
10345
+ }
10346
+ return keys;
10347
+ }
10185
10348
  function renderKpisTabHtml(metricsData) {
10186
- const telemetryHtml = metricsData.activePrKpi ? renderActiveTelemetrySection(metricsData.activePrKpi, metricsData.activeBranch) : "";
10187
- const historyRows = metricsData.history.map(renderReleaseHistoryRow).join("\n");
10188
- const historyContent = metricsData.history.length > 0 ? `<table class="data-table"><thead><tr><th>Release</th><th>Date</th><th>KLoC</th><th>Commits</th><th>Confirmed Bugs</th><th>DIR (Bugs/KLoC)</th><th>Rework (%)</th><th>Cost ($ USD)</th></tr></thead><tbody>${historyRows}</tbody></table>` : `<p style="color: var(--text-muted); font-size: 0.85rem;">No consolidated release reports recorded yet in <code>reports/releases/*.kpis.json</code>.</p>`;
10349
+ const displayKpi = metricsData.totalTelemetry || metricsData.activePrKpi;
10350
+ const branchKeys = resolveBranchFilterKeys(metricsData);
10351
+ const telemetryHtml = displayKpi ? renderActiveTelemetrySection(displayKpi, "Totalizador", branchKeys, metricsData.activeBranch) : '<p style="color: var(--text-muted); font-size: 0.85rem;">No telemetry recorded yet in this workspace.</p>';
10352
+ const historyContent = renderHistoricalKpisTable(metricsData.history);
10189
10353
  return [
10190
10354
  ` <div id="tab-kpis" class="tab-panel">`,
10191
10355
  ` <div class="content-container">`,
@@ -10206,18 +10370,29 @@ function renderTaskSidebarHtml() {
10206
10370
  ` </aside>`
10207
10371
  ].join("\n");
10208
10372
  }
10373
+ function buildTelemetryMap(metricsData) {
10374
+ const map = {};
10375
+ const total = metricsData.totalTelemetry || metricsData.activePrKpi;
10376
+ if (total) map["__total__"] = total;
10377
+ Object.assign(map, metricsData.branchTelemetries || {});
10378
+ if (metricsData.activePrKpi && metricsData.activeBranch && !map[metricsData.activeBranch]) {
10379
+ map[metricsData.activeBranch] = metricsData.activePrKpi;
10380
+ }
10381
+ return map;
10382
+ }
10209
10383
  function renderDashboardScripts(graphData, metricsData) {
10210
- const cytoscapeScript = getCytoscapeScript();
10211
10384
  const elementsJson = safeJsonForScript(graphData.elements);
10212
10385
  const govTasksJson = safeJsonForScript(metricsData.gov.tasks || []);
10213
10386
  const allManuals = [...metricsData.manuals.userManuals, ...metricsData.manuals.productionManuals];
10214
10387
  const manualsJson = safeJsonForScript(allManuals);
10388
+ const telemetryJson = safeJsonForScript(buildTelemetryMap(metricsData));
10215
10389
  return [
10216
- ` <script>${cytoscapeScript}</script>`,
10390
+ ` <script>${getCytoscapeScript()}</script>`,
10217
10391
  ` <script>`,
10218
10392
  ` var elements = ${elementsJson};`,
10219
10393
  ` var govTasks = ${govTasksJson};`,
10220
10394
  ` var manualsData = ${manualsJson};`,
10395
+ ` var telemetryData = ${telemetryJson};`,
10221
10396
  DASHBOARD_CLIENT_SCRIPT,
10222
10397
  ` </script>`
10223
10398
  ].join("\n");
@@ -10260,16 +10435,55 @@ ${scripts}
10260
10435
  }
10261
10436
 
10262
10437
  // src/reporters/dashboard.ts
10438
+ var IGNORED_SCAN_DIRS = /* @__PURE__ */ new Set([
10439
+ "node_modules",
10440
+ ".git",
10441
+ "dist",
10442
+ "coverage",
10443
+ "bin",
10444
+ "obj",
10445
+ ".gemini",
10446
+ ".agent"
10447
+ ]);
10448
+ function readKpiFilesInDir(dir) {
10449
+ if (!fs6__namespace.existsSync(dir)) return [];
10450
+ try {
10451
+ return fs6__namespace.readdirSync(dir).filter((f) => f.endsWith(".kpis.json")).map((f) => path5__namespace.join(dir, f));
10452
+ } catch {
10453
+ return [];
10454
+ }
10455
+ }
10456
+ function scanSubdirKpiFiles(dir, depth, acc) {
10457
+ if (depth > 4) return;
10458
+ try {
10459
+ const entries = fs6__namespace.readdirSync(dir, { withFileTypes: true });
10460
+ for (const entry of entries) {
10461
+ if (!entry.isDirectory() || IGNORED_SCAN_DIRS.has(entry.name)) continue;
10462
+ const sub = path5__namespace.join(dir, entry.name);
10463
+ if (entry.name === "releases" && path5__namespace.basename(dir) === "reports") {
10464
+ acc.push(...readKpiFilesInDir(sub));
10465
+ } else {
10466
+ scanSubdirKpiFiles(sub, depth + 1, acc);
10467
+ }
10468
+ }
10469
+ } catch {
10470
+ }
10471
+ }
10472
+ function findKpiJsonFiles(rootDir) {
10473
+ const primaryFiles = readKpiFilesInDir(path5__namespace.join(rootDir, "reports", "releases"));
10474
+ if (primaryFiles.length > 0) return primaryFiles;
10475
+ const found = [];
10476
+ scanSubdirKpiFiles(rootDir, 0, found);
10477
+ return found;
10478
+ }
10263
10479
  function collectHistoricalKpis(rootDir) {
10264
- const releasesDir = path5__namespace.join(rootDir, "reports", "releases");
10265
10480
  const items = [];
10266
- if (fs6__namespace.existsSync(releasesDir)) {
10481
+ const kpiFiles = findKpiJsonFiles(rootDir);
10482
+ for (const fullPath of kpiFiles) {
10267
10483
  try {
10268
- const files = fs6__namespace.readdirSync(releasesDir).filter((f) => f.endsWith(".kpis.json"));
10269
- for (const file of files) {
10270
- const fullPath = path5__namespace.join(releasesDir, file);
10271
- const content = fs6__namespace.readFileSync(fullPath, "utf-8");
10272
- const report = JSON.parse(content);
10484
+ const content = fs6__namespace.readFileSync(fullPath, "utf-8");
10485
+ const report = JSON.parse(content);
10486
+ if (!items.some((it) => it.id === report.releaseBranch && it.timestamp === report.generatedAt)) {
10273
10487
  items.push({
10274
10488
  id: report.releaseBranch,
10275
10489
  label: report.releaseBranch,
@@ -10338,6 +10552,82 @@ function resolveActivePrKpi(rootDir, activeBranch) {
10338
10552
  return null;
10339
10553
  }
10340
10554
  }
10555
+ function collectGitUnmergedBranches(rootDir) {
10556
+ for (const base of ["main", "master"]) {
10557
+ try {
10558
+ const output = child_process.execFileSync("git", ["branch", "--no-merged", base], {
10559
+ cwd: rootDir,
10560
+ encoding: "utf-8",
10561
+ stdio: ["pipe", "pipe", "pipe"]
10562
+ });
10563
+ return output.split("\n").map((l) => l.replace("*", "").trim()).filter((b) => b && !b.includes("detached"));
10564
+ } catch {
10565
+ }
10566
+ }
10567
+ return [];
10568
+ }
10569
+ function collectGitPrefixedBranches(rootDir) {
10570
+ try {
10571
+ const raw = child_process.execFileSync("git", ["for-each-ref", "--format=%(refname:short)", "refs/heads/"], {
10572
+ cwd: rootDir,
10573
+ encoding: "utf-8",
10574
+ stdio: ["pipe", "pipe", "pipe"]
10575
+ });
10576
+ return raw.split("\n").map((s) => s.trim()).filter((b) => b.startsWith("feat/") || b.startsWith("bug/") || b.startsWith("task/") || b.startsWith("release/"));
10577
+ } catch {
10578
+ return [];
10579
+ }
10580
+ }
10581
+ function resolveCandidateBranches(rootDir, activeBranch, history) {
10582
+ const branchSet = /* @__PURE__ */ new Set();
10583
+ if (activeBranch && activeBranch !== "HEAD") {
10584
+ branchSet.add(activeBranch);
10585
+ }
10586
+ for (const h of history) {
10587
+ if (h.id && h.id !== "HEAD") branchSet.add(h.id);
10588
+ }
10589
+ for (const b of collectGitUnmergedBranches(rootDir)) branchSet.add(b);
10590
+ for (const b of collectGitPrefixedBranches(rootDir)) branchSet.add(b);
10591
+ return Array.from(branchSet);
10592
+ }
10593
+ function fetchBranchKpi(rootDir, branch) {
10594
+ try {
10595
+ const isDefault = branch === "main" || branch === "master";
10596
+ const base = isDefault ? resolveDefaultBranchBase(rootDir) : "main";
10597
+ const kpi = aggregatePrKpis(base, branch, { cwd: rootDir });
10598
+ return kpi && kpi.totalCommits > 0 ? kpi : null;
10599
+ } catch {
10600
+ return null;
10601
+ }
10602
+ }
10603
+ function recordBranchKpi(telemetries, commitMap, branch, kpi) {
10604
+ telemetries[branch] = kpi;
10605
+ for (const rec of kpi.commitRecords || []) commitMap.set(rec.sha, rec);
10606
+ }
10607
+ function populateCandidateTelemetries(rootDir, candidateBranches, telemetries, commitMap) {
10608
+ for (const branch of candidateBranches) {
10609
+ if (telemetries[branch]) continue;
10610
+ const kpi = fetchBranchKpi(rootDir, branch);
10611
+ if (kpi) recordBranchKpi(telemetries, commitMap, branch, kpi);
10612
+ }
10613
+ }
10614
+ function computeConsolidatedTelemetry(uniqueCommits, fallback) {
10615
+ if (uniqueCommits.length > 0) {
10616
+ return aggregateCommitKpiRecords(uniqueCommits, "all", "Totalizador");
10617
+ }
10618
+ return fallback;
10619
+ }
10620
+ function collectAllBranchTelemetries(rootDir, activeBranch, history, activePrKpi) {
10621
+ const branchTelemetries = {};
10622
+ const uniqueCommits = /* @__PURE__ */ new Map();
10623
+ if (activePrKpi && activeBranch && activeBranch !== "HEAD") {
10624
+ recordBranchKpi(branchTelemetries, uniqueCommits, activeBranch, activePrKpi);
10625
+ }
10626
+ const candidateBranches = resolveCandidateBranches(rootDir, activeBranch, history);
10627
+ populateCandidateTelemetries(rootDir, candidateBranches, branchTelemetries, uniqueCommits);
10628
+ const totalTelemetry = computeConsolidatedTelemetry(Array.from(uniqueCommits.values()), activePrKpi);
10629
+ return { branchTelemetries, totalTelemetry };
10630
+ }
10341
10631
  function collectDashboardMetrics(rootDir) {
10342
10632
  const gate = verifyQualityGate({ rootDir });
10343
10633
  const gov = verifyTasksGovernance({ rootDir });
@@ -10348,6 +10638,12 @@ function collectDashboardMetrics(rootDir) {
10348
10638
  const activeBranch = resolveActiveBranch(rootDir);
10349
10639
  const activePrKpi = resolveActivePrKpi(rootDir, activeBranch);
10350
10640
  const codeCoverage = readCodeCoverageSummary(rootDir);
10641
+ const { branchTelemetries, totalTelemetry } = collectAllBranchTelemetries(
10642
+ rootDir,
10643
+ activeBranch,
10644
+ history,
10645
+ activePrKpi
10646
+ );
10351
10647
  return {
10352
10648
  gate,
10353
10649
  gov,
@@ -10357,7 +10653,9 @@ function collectDashboardMetrics(rootDir) {
10357
10653
  manuals,
10358
10654
  history,
10359
10655
  activePrKpi,
10360
- activeBranch
10656
+ activeBranch,
10657
+ branchTelemetries,
10658
+ totalTelemetry
10361
10659
  };
10362
10660
  }
10363
10661
  function computeSummaryRatios(metricsData) {
@@ -12757,6 +13055,7 @@ exports.VALID_ARCHITECTURE_GRANULARITIES = VALID_ARCHITECTURE_GRANULARITIES;
12757
13055
  exports.VALID_AUTONOMY_MODES = VALID_AUTONOMY_MODES;
12758
13056
  exports.VALID_CHANGE_PROFILES = VALID_CHANGE_PROFILES;
12759
13057
  exports.VALID_CI_PROVIDERS = VALID_CI_PROVIDERS;
13058
+ exports.aggregateCommitKpiRecords = aggregateCommitKpiRecords;
12760
13059
  exports.aggregatePrKpis = aggregatePrKpis;
12761
13060
  exports.aggregateReleaseKpis = aggregateReleaseKpis;
12762
13061
  exports.appendRevisionHistoryRow = appendRevisionHistoryRow;
@@ -12863,6 +13162,7 @@ exports.resolveBranchHierarchy = resolveBranchHierarchy;
12863
13162
  exports.resolveCiEnvironment = resolveCiEnvironment;
12864
13163
  exports.resolveCytoscapePath = resolveCytoscapePath;
12865
13164
  exports.resolveFeatureFilePath = resolveFeatureFilePath;
13165
+ exports.resolveGitRef = resolveGitRef;
12866
13166
  exports.resolveLicenseFromFile = resolveLicenseFromFile;
12867
13167
  exports.resolveSchemaForArtifact = resolveSchemaForArtifact;
12868
13168
  exports.resolveTargetFiles = resolveTargetFiles;