commit-insights 0.1.1 → 0.1.4

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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildProgram
4
- } from "../chunk-IVFHXIJZ.js";
4
+ } from "../chunk-LNNF6KV7.js";
5
5
  import "../chunk-JSBRDJBE.js";
6
6
 
7
7
  // src/bin/commit-insights.ts
@@ -767,58 +767,112 @@ function escapeHtml(s) {
767
767
  }
768
768
 
769
769
  // src/report/templates/sections/header.ts
770
- function renderHeader(repoName, period) {
771
- return `<header class="dashboard-header">
772
- <h1 class="repo-name">${escapeHtml(repoName)}</h1>
773
- <p class="repo-period">${escapeHtml(period.start)} \u2013 ${escapeHtml(period.end)}</p>
770
+ function renderHeader(repoName, period, generatedAt, totals) {
771
+ const date = generatedAt.slice(0, 10);
772
+ return `<header class="hero">
773
+ <div class="hero-row"><span class="hero-number">${totals.commits}</span><span class="hero-label">commits</span></div>
774
+ <div class="hero-repo">${escapeHtml(repoName)}</div>
775
+ <div class="hero-period">${escapeHtml(period.start)} \u2013 ${escapeHtml(period.end)} <span class="hero-period-gen">\xB7 ${escapeHtml(date)}</span></div>
774
776
  </header>`;
775
777
  }
776
778
 
777
- // src/report/templates/sections/metricCards.ts
778
- function renderMetricCards(totals) {
779
- return `<div class="metric-cards">
780
- <div class="metric-card"><span class="metric-value">${totals.commits}</span><span class="metric-label">Commits</span></div>
781
- <div class="metric-card"><span class="metric-value">${totals.tickets}</span><span class="metric-label">Tickets</span></div>
782
- <div class="metric-card"><span class="metric-value">${totals.authors}</span><span class="metric-label">Authors</span></div>
783
- </div>`;
779
+ // src/report/templates/sections/statsBar.ts
780
+ function renderStatsBar(totals) {
781
+ const items = [
782
+ `<div class="stats-item"><span class="stats-value">${totals.commits}</span><span class="stats-label">commits</span></div>`,
783
+ `<div class="stats-item"><span class="stats-value">${totals.authors}</span><span class="stats-label">authors</span></div>`
784
+ ];
785
+ if (totals.tickets > 0) {
786
+ items.push(`<div class="stats-item"><span class="stats-value">${totals.tickets}</span><span class="stats-label">tickets</span></div>`);
787
+ }
788
+ return `<div class="stats-bar">${items.join("")}</div>`;
784
789
  }
785
790
 
786
791
  // src/report/templates/sections/charts.ts
787
- function renderChartContainers() {
788
- return `<section class="charts-section">
789
- <h2 class="section-label">Activity</h2>
790
- <div class="chart-full">
791
- <div class="chart-wrapper">
792
- <div class="chart-container"><canvas id="chart-monthly" height="240"></canvas></div>
793
- </div>
794
- </div>
795
- <div class="charts-split">
796
- <div class="chart-wrapper">
797
- <h3>Type</h3>
798
- <div class="chart-container"><canvas id="chart-types" height="240"></canvas></div>
799
- </div>
800
- <div class="chart-wrapper">
801
- <h3>Area</h3>
802
- <div class="chart-container"><canvas id="chart-areas" height="240"></canvas></div>
803
- </div>
804
- </div>
792
+ var TYPE_ORDER = ["feat", "fix", "docs", "refactor", "style", "test", "perf", "ci", "build", "chore", "revert", "merge", "other"];
793
+ function renderMonthlyChart() {
794
+ return `<section>
795
+ <h2 class="eyebrow">Activity</h2>
796
+ <div class="chart-container"><canvas id="chart-monthly" height="200"></canvas></div>
797
+ </section>`;
798
+ }
799
+ function renderTypeBars(typeCounts) {
800
+ const max = Math.max(...Object.values(typeCounts), 1);
801
+ const rows = TYPE_ORDER.filter((t) => typeCounts[t]).map((t) => {
802
+ const pct = typeCounts[t] / max * 100;
803
+ return `<div class="bar-row">
804
+ <span class="bar-label">${escapeHtml(t)}</span>
805
+ <div class="bar-track"><div class="bar-fill bar-type-${escapeHtml(t)}" style="width:${pct}%"></div></div>
806
+ <span class="bar-count">${typeCounts[t]}</span>
807
+ </div>`;
808
+ }).join("");
809
+ return `<section>
810
+ <h2 class="eyebrow">Types</h2>
811
+ <div class="type-bars">${rows}</div>
812
+ </section>`;
813
+ }
814
+ function renderAreaBars(areaCounts) {
815
+ if (areaCounts.length === 0) return "";
816
+ const max = Math.max(...areaCounts.map((a) => a.count), 1);
817
+ const rows = areaCounts.map((a) => {
818
+ const pct = a.count / max * 100;
819
+ return `<div class="bar-row">
820
+ <span class="bar-label">${escapeHtml(a.area)}</span>
821
+ <div class="bar-track"><div class="bar-fill bar-type-area" style="width:${pct}%"></div></div>
822
+ <span class="bar-count">${a.count}</span>
823
+ </div>`;
824
+ }).join("");
825
+ return `<section>
826
+ <h2 class="eyebrow">Areas</h2>
827
+ <div class="area-bars">${rows}</div>
805
828
  </section>`;
806
829
  }
807
830
 
808
- // src/report/templates/sections/tables.ts
809
- function renderTopTickets(tickets) {
831
+ // src/report/templates/sections/tickets.ts
832
+ function renderTickets(tickets) {
810
833
  if (tickets.length === 0) return "";
811
- const rows = tickets.map(
812
- (t) => `<tr><td class="ticket-id">${escapeHtml(t.id)}</td><td class="ticket-count">${t.count}</td></tr>`
834
+ const items = tickets.map(
835
+ (t) => `<li><span class="ticket-id">${escapeHtml(t.id)}</span><span class="ticket-count">${t.count}</span></li>`
836
+ ).join("");
837
+ return `<section>
838
+ <h2 class="eyebrow">Top Tickets</h2>
839
+ <ul class="ticket-list">${items}</ul>
840
+ </section>`;
841
+ }
842
+
843
+ // src/report/templates/sections/reviewers.ts
844
+ function renderReviewers(reviewers) {
845
+ if (reviewers.length === 0) return "";
846
+ const items = reviewers.map(
847
+ (r) => `<li><span class="reviewer-name">${escapeHtml(r.name)}</span><span class="reviewer-count">${r.collaborations}</span></li>`
813
848
  ).join("");
814
- return `<section class="tickets-table"><h2 class="section-label">Top Tickets</h2><table><thead><tr><th>Ticket</th><th>Commits</th></tr></thead><tbody>${rows}</tbody></table></section>`;
849
+ return `<section>
850
+ <h2 class="eyebrow">Reviewers</h2>
851
+ <ul class="reviewer-list">${items}</ul>
852
+ </section>`;
815
853
  }
854
+
855
+ // src/report/templates/sections/tables.ts
816
856
  function renderRecentCommits(commits) {
817
857
  const limited = commits.slice(0, 200);
858
+ const header = `<div class="commit-grid-header">
859
+ <div class="commit-cell hash">Hash</div>
860
+ <div class="commit-cell date">Date</div>
861
+ <div class="commit-cell type">Type</div>
862
+ <div class="commit-cell subject">Subject</div>
863
+ </div>`;
818
864
  const rows = limited.map(
819
- (c) => `<tr><td class="commit-hash"><code>${escapeHtml(c.hash.slice(0, 7))}</code></td><td class="commit-date">${escapeHtml(c.date)}</td><td class="commit-subject">${escapeHtml(c.subject)}</td><td><span class="type-badge type-${escapeHtml(c.type)}">${escapeHtml(c.type)}</span></td><td class="commit-area">${escapeHtml(c.area)}</td><td class="commit-tickets">${c.tickets.map((t) => escapeHtml(t)).join(", ")}</td></tr>`
865
+ (c) => `<div class="commit-row">
866
+ <div class="commit-cell hash">${escapeHtml(c.hash.slice(0, 7))}</div>
867
+ <div class="commit-cell date">${escapeHtml(c.date)}</div>
868
+ <div class="commit-cell type"><span class="type-badge type-${escapeHtml(c.type)}">${escapeHtml(c.type)}</span></div>
869
+ <div class="commit-cell subject">${escapeHtml(c.subject)}</div>
870
+ </div>`
820
871
  ).join("");
821
- return `<section class="recent-commits"><h2 class="section-label">Recent Commits</h2><div class="table-scroll"><table><thead><tr><th>Hash</th><th>Date</th><th>Subject</th><th>Type</th><th>Area</th><th>Tickets</th></tr></thead><tbody>${rows}</tbody></table></div></section>`;
872
+ return `<section>
873
+ <h2 class="eyebrow">Recent Commits</h2>
874
+ <div class="commit-scroll">${header}${rows}</div>
875
+ </section>`;
822
876
  }
823
877
 
824
878
  // src/report/templates/sections/narrative.ts
@@ -826,7 +880,7 @@ function renderNarrativeBlock(text) {
826
880
  if (!text) return "";
827
881
  const paragraphs = text.split(/\n\n+/).map((p) => `<p>${escapeHtml(p.trim())}</p>`).join("");
828
882
  return `<section class="narrative-section">
829
- <h2 class="section-label">Summary</h2>
883
+ <h2 class="eyebrow">Summary</h2>
830
884
  <div class="narrative-content">${paragraphs}</div>
831
885
  </section>`;
832
886
  }
@@ -843,28 +897,18 @@ function renderFooter(version, generatedAt) {
843
897
  // src/report/templates/styles.ts
844
898
  var STYLES = `/* \u2500\u2500 Tokens \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
845
899
  :root {
846
- --bg: #080808;
847
- --surface: rgba(255, 255, 255, 0.04);
848
- --surface-raised: rgba(255, 255, 255, 0.07);
849
- --border: rgba(255, 255, 255, 0.08);
850
- --border-subtle: rgba(255, 255, 255, 0.04);
851
- --text-primary: #f0f2f5;
852
- --text-secondary: #a0aaba;
853
- --text-muted: #6b7280;
854
- --accent-silver: #e2e8f0;
855
- --green: #34d399;
856
- --green-muted: rgba(52, 211, 153, 0.12);
857
- --blue: #60a5fa;
858
- --blue-muted: rgba(96, 165, 250, 0.12);
859
- --purple: #a78bfa;
860
- --purple-muted: rgba(167, 139, 250, 0.12);
861
- --yellow: #fbbf24;
862
- --orange: #fb923c;
863
- --red: #f87171;
864
- --pink: #f472b6;
865
- --teal: #2dd4bf;
866
- --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
867
- --font-mono: ui-monospace, "SFMono-Regular", Consolas, "Liberation Mono", monospace;
900
+ --bg: #0D0D0F;
901
+ --surface: #18160F;
902
+ --stone: #F2EDE6;
903
+ --stone-2: #B8A898;
904
+ --stone-3: #7A7060;
905
+ --amber: #C4763A;
906
+ --teal: #7EB8A4;
907
+ --rule: rgba(242,237,230,0.07);
908
+ --rule-light: rgba(242,237,230,0.12);
909
+ --font-serif: Georgia, 'Times New Roman', serif;
910
+ --font-body: system-ui, -apple-system, sans-serif;
911
+ --font-mono: ui-monospace, 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
868
912
  }
869
913
 
870
914
  /* \u2500\u2500 Reset & Base \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
@@ -872,135 +916,294 @@ var STYLES = `/* \u2500\u2500 Tokens \u2500\u2500\u2500\u2500\u2500\u2500\u2500\
872
916
  html { font-size: 16px; }
873
917
  body {
874
918
  font-family: var(--font-body);
919
+ font-weight: 300;
875
920
  background: var(--bg);
876
- color: var(--text-primary);
921
+ background-image:
922
+ radial-gradient(circle, rgba(242,237,230,0.03) 0.5px, transparent 0.5px);
923
+ background-size: 20px 20px;
924
+ background-position: 0 0;
925
+ background-repeat: repeat;
926
+ color: var(--stone);
877
927
  line-height: 1.7;
878
- padding: 44px 32px;
879
- max-width: 1200px;
928
+ padding: 80px 40px;
929
+ max-width: 880px;
880
930
  margin: 0 auto;
881
931
  -webkit-font-smoothing: antialiased;
882
932
  -moz-osx-font-smoothing: grayscale;
883
933
  }
884
- code, .commit-hash, .ticket-id, .type-badge, .metric-value {
934
+ code, .commit-hash, .type-badge, .stats-value, .ticket-id, .reviewer-name, .eyebrow, .bar-label, .bar-count {
885
935
  font-family: var(--font-mono);
886
936
  }
887
937
 
888
938
  /* \u2500\u2500 Typography \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
889
- h1, h2, h3 { text-wrap: balance; font-weight: 600; }
890
- p { max-width: 72ch; }
891
- a { color: var(--blue); }
892
- a:hover { color: var(--text-primary); }
893
-
894
- /* \u2500\u2500 Section pattern \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
895
- section { margin-bottom: 48px; }
896
- .section-label {
897
- font-family: var(--font-mono);
939
+ h2 { font-weight: 400; }
940
+ p { max-width: 72ch; color: var(--stone-2); }
941
+ .narrative-content p { max-width: none; }
942
+ a { color: var(--amber); }
943
+ a:hover { color: var(--stone); }
944
+
945
+ /* \u2500\u2500 Eyebrow pattern \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
946
+ section { margin-bottom: 56px; }
947
+ .eyebrow {
948
+ display: flex;
949
+ align-items: center;
950
+ gap: 16px;
951
+ margin-bottom: 24px;
898
952
  font-size: 0.625rem;
899
953
  font-weight: 500;
900
954
  text-transform: uppercase;
901
- letter-spacing: 0.15em;
902
- color: var(--text-muted);
903
- margin-bottom: 20px;
904
- }
905
-
906
- /* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
907
- .dashboard-header { margin-bottom: 48px; }
908
- .repo-name { font-size: 2.25rem; font-weight: 600; color: var(--text-primary); letter-spacing: -0.025em; line-height: 1.2; }
909
- .repo-period { color: var(--text-secondary); margin-top: 8px; font-size: 0.8125rem; font-family: var(--font-mono); }
910
-
911
- /* \u2500\u2500 Metric cards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
912
- .metric-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 48px; }
913
- .metric-card {
914
- background: var(--surface);
915
- border: 1px solid var(--border);
916
- border-radius: 12px;
917
- padding: 24px 20px;
918
- text-align: center;
919
- transition: background 0.2s ease, border-color 0.2s ease;
955
+ letter-spacing: 0.2em;
956
+ color: var(--stone-3);
957
+ }
958
+ .eyebrow::after {
959
+ content: '';
960
+ flex: 1;
961
+ height: 1px;
962
+ background: var(--rule);
963
+ }
964
+
965
+ /* \u2500\u2500 Hero \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
966
+ .hero { margin-bottom: 64px; }
967
+ .hero-row { display: flex; align-items: flex-end; gap: 12px; }
968
+ .hero-number {
969
+ font-family: var(--font-serif);
970
+ font-size: clamp(72px, 12vw, 120px);
971
+ font-weight: 700;
972
+ color: var(--stone);
973
+ line-height: 1;
974
+ letter-spacing: -0.03em;
975
+ }
976
+ .hero-label {
977
+ font-family: var(--font-mono);
978
+ font-size: 1.25rem;
979
+ font-weight: 400;
980
+ color: var(--stone-3);
981
+ line-height: 1;
982
+ margin-top: 0.35em;
983
+ }
984
+ .hero-repo {
985
+ font-family: var(--font-mono);
986
+ font-size: 0.9375rem;
987
+ font-weight: 500;
988
+ color: var(--amber);
989
+ margin-top: 12px;
990
+ }
991
+ .hero-period {
992
+ font-family: var(--font-mono);
993
+ font-size: 0.75rem;
994
+ color: var(--stone-3);
995
+ margin-top: 4px;
996
+ }
997
+ .hero-period-gen {
998
+ color: #5A5040;
999
+ }
1000
+
1001
+ /* \u2500\u2500 Stats bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1002
+ .stats-bar {
1003
+ display: flex;
1004
+ width: 100%;
1005
+ margin-bottom: 56px;
1006
+ font-size: 1.125rem;
1007
+ color: var(--stone-2);
1008
+ font-weight: 400;
1009
+ }
1010
+ .stats-item {
1011
+ flex: 1;
1012
+ display: flex;
1013
+ flex-direction: column;
1014
+ align-items: center;
1015
+ padding: 20px 16px;
1016
+ gap: 4px;
1017
+ }
1018
+ .stats-item + .stats-item {
1019
+ border-left: 1px solid var(--rule);
1020
+ }
1021
+ .stats-value {
1022
+ font-variant-numeric: tabular-nums;
1023
+ color: var(--stone);
1024
+ font-size: 1.75rem;
1025
+ font-weight: 500;
1026
+ line-height: 1.1;
1027
+ }
1028
+ .stats-label {
1029
+ font-size: 0.6875rem;
1030
+ color: var(--stone-3);
1031
+ text-transform: uppercase;
1032
+ letter-spacing: 0.1em;
1033
+ font-family: var(--font-mono);
920
1034
  }
921
- .metric-card:hover { background: var(--surface-raised); border-color: rgba(255,255,255,0.14); }
922
- .metric-value { display: block; font-size: 2.25rem; font-weight: 400; color: var(--text-primary); line-height: 1; letter-spacing: -0.02em; }
923
- .metric-label { display: block; font-size: 0.625rem; font-family: var(--font-mono); text-transform: uppercase; letter-spacing: 0.2em; color: var(--text-muted); margin-top: 8px; }
924
1035
 
925
1036
  /* \u2500\u2500 Narrative \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
926
- .narrative-content { max-width: 72ch; color: var(--text-primary); font-size: 0.9375rem; line-height: 1.8; padding-right: 40px; }
1037
+ .narrative-section { margin-bottom: 56px; }
1038
+ .narrative-section .eyebrow { margin-bottom: 24px; }
1039
+ .narrative-content { font-size: 0.9375rem; line-height: 1.8; }
927
1040
  .narrative-content p + p { margin-top: 18px; }
928
1041
 
929
1042
  /* \u2500\u2500 Charts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
930
- .chart-full { margin-bottom: 20px; }
931
- .charts-split { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
932
- .chart-wrapper { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 24px; }
933
- .chart-wrapper h3 { font-size: 0.625rem; font-weight: 500; font-family: var(--font-mono); text-transform: uppercase; letter-spacing: 0.15em; color: var(--text-muted); margin-bottom: 20px; }
934
- .chart-container { height: 260px; position: relative; }
935
-
936
- /* \u2500\u2500 Tables \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
937
- .table-scroll { overflow-x: auto; overflow-y: auto; max-height: 600px; border-radius: 12px; border: 1px solid var(--border); }
938
- table { width: 100%; border-collapse: collapse; background: var(--surface); }
939
- thead { position: sticky; top: 0; z-index: 1; }
940
- th {
941
- background: var(--bg);
942
- border-bottom: 1px solid var(--border);
943
- padding: 12px 16px;
944
- text-align: left;
1043
+ .chart-container { height: 220px; position: relative; margin-bottom: 0; }
1044
+
1045
+ /* \u2500\u2500 Type bars \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1046
+ .type-bars, .area-bars { display: flex; flex-direction: column; gap: 12px; }
1047
+ .bar-row { display: flex; align-items: center; gap: 16px; }
1048
+ .bar-label {
1049
+ min-width: 60px;
1050
+ flex-shrink: 0;
1051
+ font-size: 0.6875rem;
945
1052
  font-weight: 500;
1053
+ text-transform: uppercase;
1054
+ letter-spacing: 0.04em;
1055
+ color: var(--stone-3);
1056
+ text-align: right;
1057
+ white-space: nowrap;
1058
+ overflow: hidden;
1059
+ text-overflow: ellipsis;
1060
+ }
1061
+ .bar-track {
1062
+ flex: 1;
1063
+ height: 4px;
1064
+ background: var(--rule-light);
1065
+ border-radius: 2px;
1066
+ overflow: hidden;
1067
+ }
1068
+ .bar-fill {
1069
+ height: 100%;
1070
+ border-radius: 2px;
1071
+ transition: width 0.6s ease;
1072
+ }
1073
+ .bar-count {
1074
+ width: 32px;
1075
+ flex-shrink: 0;
1076
+ font-size: 0.6875rem;
1077
+ color: var(--stone-3);
1078
+ text-align: right;
1079
+ font-variant-numeric: tabular-nums;
1080
+ }
1081
+
1082
+ .bar-type-feat { background: var(--teal); }
1083
+ .bar-type-fix { background: var(--amber); }
1084
+ .bar-type-chore { background: #9A8868; }
1085
+ .bar-type-style { background: #8A7AB0; }
1086
+ .bar-type-refactor { background: #6A9EB8; }
1087
+ .bar-type-docs { background: #609A7A; }
1088
+ .bar-type-test { background: #A07A5A; }
1089
+ .bar-type-perf { background: #8A8860; }
1090
+ .bar-type-ci { background: #4A8A9A; }
1091
+ .bar-type-build { background: #7A8A5A; }
1092
+ .bar-type-revert { background: #A06A7A; }
1093
+ .bar-type-merge { background: #6A7A6A; }
1094
+ .bar-type-other { background: #4A4540; }
1095
+
1096
+ .bar-type-area { background: var(--amber); }
1097
+
1098
+ /* \u2500\u2500 Commit log \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1099
+ .commit-scroll {
1100
+ max-height: 600px;
1101
+ overflow-y: auto;
1102
+ border-top: 1px solid var(--rule);
1103
+ }
1104
+ .commit-scroll::-webkit-scrollbar { width: 6px; }
1105
+ .commit-scroll::-webkit-scrollbar-track { background: transparent; }
1106
+ .commit-scroll::-webkit-scrollbar-thumb { background: #3A3630; border-radius: 3px; }
1107
+ .commit-scroll::-webkit-scrollbar-thumb:hover { background: #5A5040; }
1108
+ .commit-scroll { scrollbar-width: thin; scrollbar-color: #3A3630 transparent; }
1109
+ .commit-grid-header {
1110
+ display: flex;
1111
+ position: sticky;
1112
+ top: 0;
1113
+ z-index: 1;
1114
+ background: var(--bg);
1115
+ border-bottom: 1px solid var(--rule);
1116
+ }
1117
+ .commit-grid-header .commit-cell {
946
1118
  font-size: 0.625rem;
947
- font-family: var(--font-mono);
1119
+ font-weight: 500;
948
1120
  text-transform: uppercase;
949
1121
  letter-spacing: 0.1em;
950
- color: var(--text-muted);
951
- }
952
- td { padding: 10px 16px; border-bottom: 1px solid var(--border-subtle); font-size: 0.8125rem; color: var(--text-primary); }
953
- tr:last-child td { border-bottom: none; }
954
- tr:hover td { background: var(--surface-raised); }
955
-
956
- /* Type badges */
957
- .type-badge { display: inline-block; padding: 2px 10px; border-radius: 8px; font-size: 0.625rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; border: 1px solid transparent; }
958
- .type-feat { background: var(--green-muted); color: var(--green); border-color: rgba(52, 211, 153, 0.2); }
959
- .type-fix { background: rgba(251, 191, 36, 0.12); color: var(--yellow); border-color: rgba(251, 191, 36, 0.2); }
960
- .type-merge { background: rgba(255, 255, 255, 0.06); color: var(--accent-silver); border-color: rgba(255, 255, 255, 0.1); }
961
- .type-other { background: rgba(255, 255, 255, 0.04); color: var(--text-muted); border-color: rgba(255, 255, 255, 0.06); }
962
- .type-chore { background: rgba(255, 255, 255, 0.04); color: var(--text-muted); border-color: rgba(255, 255, 255, 0.06); }
963
- .type-docs { background: var(--blue-muted); color: var(--blue); border-color: rgba(96, 165, 250, 0.2); }
964
- .type-refactor { background: var(--purple-muted); color: var(--purple); border-color: rgba(167, 139, 250, 0.2); }
965
- .type-test { background: rgba(248, 113, 113, 0.12); color: var(--red); border-color: rgba(248, 113, 113, 0.2); }
966
- .type-style { background: rgba(52, 211, 153, 0.12); color: var(--green); border-color: rgba(52, 211, 153, 0.2); }
967
- .type-perf { background: rgba(251, 146, 60, 0.12); color: var(--orange); border-color: rgba(251, 146, 60, 0.2); }
968
- .type-ci { background: rgba(167, 139, 250, 0.12); color: var(--purple); border-color: rgba(167, 139, 250, 0.2); }
969
- .type-build { background: rgba(45, 212, 191, 0.12); color: var(--teal); border-color: rgba(45, 212, 191, 0.2); }
970
- .type-revert { background: rgba(248, 113, 113, 0.12); color: var(--red); border-color: rgba(248, 113, 113, 0.2); }
971
-
972
- /* Commit table cells */
973
- .commit-hash code { color: var(--accent-silver); font-size: inherit; letter-spacing: 0.02em; }
974
- .commit-date { color: var(--text-secondary); font-family: var(--font-mono); font-size: 0.75rem; }
975
- .commit-subject { color: var(--text-primary); }
976
- .commit-area { color: var(--text-secondary); font-family: var(--font-mono); font-size: 0.75rem; }
977
- .commit-tickets { color: var(--text-muted); font-family: var(--font-mono); font-size: 0.75rem; }
978
- .ticket-count { text-align: right; font-variant-numeric: tabular-nums; color: var(--text-secondary); font-family: var(--font-mono); }
979
- .ticket-id { color: var(--accent-silver); font-family: var(--font-mono); font-size: 0.8125rem; letter-spacing: 0.02em; }
1122
+ color: var(--stone-3);
1123
+ padding: 10px 8px;
1124
+ }
1125
+ .commit-row {
1126
+ display: flex;
1127
+ border-bottom: 1px solid var(--rule);
1128
+ }
1129
+ .commit-row:last-child { border-bottom: none; }
1130
+ .commit-row .commit-cell {
1131
+ padding: 10px 8px;
1132
+ font-size: 0.8125rem;
1133
+ overflow: hidden;
1134
+ text-overflow: ellipsis;
1135
+ white-space: nowrap;
1136
+ }
1137
+ .commit-cell.hash { width: 72px; flex-shrink: 0; font-family: var(--font-mono); color: var(--stone-3); font-size: 0.75rem; }
1138
+ .commit-cell.date { width: 96px; flex-shrink: 0; font-family: var(--font-mono); color: var(--stone-3); font-size: 0.75rem; }
1139
+ .commit-cell.type { width: 64px; flex-shrink: 0; }
1140
+ .commit-cell.subject { flex: 1; min-width: 0; color: var(--stone); }
1141
+ .commit-cell .type-badge {
1142
+ display: inline-block;
1143
+ padding: 1px 8px;
1144
+ border-radius: 10px;
1145
+ font-size: 0.625rem;
1146
+ font-weight: 500;
1147
+ text-transform: uppercase;
1148
+ letter-spacing: 0.04em;
1149
+ }
1150
+ .type-feat { background: rgba(126,184,164,0.15); color: var(--teal); }
1151
+ .type-fix { background: rgba(196,118,58,0.15); color: var(--amber); }
1152
+ .type-chore { background: rgba(154,136,104,0.2); color: #9A8868; }
1153
+ .type-style { background: rgba(138,122,176,0.15); color: #8A7AB0; }
1154
+ .type-refactor { background: rgba(106,158,184,0.15); color: #6A9EB8; }
1155
+ .type-docs { background: rgba(96,154,122,0.15); color: #609A7A; }
1156
+ .type-test { background: rgba(160,122,90,0.15); color: #A07A5A; }
1157
+ .type-perf { background: rgba(138,136,96,0.15); color: #8A8860; }
1158
+ .type-ci { background: rgba(74,138,154,0.15); color: #4A8A9A; }
1159
+ .type-build { background: rgba(122,138,90,0.15); color: #7A8A5A; }
1160
+ .type-revert { background: rgba(160,106,122,0.15); color: #A06A7A; }
1161
+ .type-merge { background: rgba(106,122,106,0.15); color: #6A7A6A; }
1162
+ .type-other { background: rgba(74,69,64,0.3); color: #4A4540; }
1163
+
1164
+ /* \u2500\u2500 Side-by-side grid \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1165
+ .side-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; margin-bottom: 56px; }
1166
+ .side-grid > section { margin-bottom: 0; }
1167
+
1168
+ /* \u2500\u2500 Ticket / Reviewer list \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1169
+ .ticket-list, .reviewer-list { list-style: none; }
1170
+ .ticket-list li, .reviewer-list li {
1171
+ display: flex;
1172
+ justify-content: space-between;
1173
+ align-items: center;
1174
+ padding: 8px 0;
1175
+ border-bottom: 1px solid var(--rule);
1176
+ }
1177
+ .ticket-list li:last-child, .reviewer-list li:last-child { border-bottom: none; }
1178
+ .ticket-id, .reviewer-name {
1179
+ font-size: 0.75rem;
1180
+ color: var(--stone);
1181
+ font-weight: 400;
1182
+ }
1183
+ .ticket-count, .reviewer-count {
1184
+ font-size: 0.75rem;
1185
+ color: var(--stone-3);
1186
+ font-variant-numeric: tabular-nums;
1187
+ font-family: var(--font-mono);
1188
+ }
980
1189
 
981
1190
  /* \u2500\u2500 Empty state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
982
1191
  .empty-state {
983
1192
  text-align: center;
984
- padding: 88px 24px 80px;
985
- color: var(--text-secondary);
986
- }
987
- .empty-icon {
988
- display: block;
989
- font-size: 2rem;
990
- line-height: 1;
991
- margin-bottom: 16px;
992
- opacity: 0.25;
993
- font-family: var(--font-mono);
994
- color: var(--text-muted);
995
- letter-spacing: 0.1em;
1193
+ padding: 80px 24px;
1194
+ color: var(--stone-2);
996
1195
  }
997
- .empty-state p { font-size: 1.125rem; margin: 0 auto; color: var(--text-secondary); }
1196
+ .empty-state p { font-size: 1rem; margin: 0 auto; color: var(--stone-2); }
1197
+
1198
+ /* \u2500\u2500 Scroll-in animation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1199
+ section { opacity: 0; transform: translateY(6px); transition: opacity 0.5s ease-out, transform 0.5s ease-out; }
1200
+ section.section-visible { opacity: 1; transform: translateY(0); }
998
1201
 
999
1202
  /* \u2500\u2500 Footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1000
1203
  .dashboard-footer {
1001
- margin-top: 64px;
1204
+ margin-top: 80px;
1002
1205
  padding-top: 20px;
1003
- border-top: 1px solid var(--border);
1206
+ border-top: 1px solid var(--rule);
1004
1207
  display: flex;
1005
1208
  justify-content: flex-end;
1006
1209
  align-items: center;
@@ -1008,15 +1211,15 @@ tr:hover td { background: var(--surface-raised); }
1008
1211
  }
1009
1212
  .dashboard-footer p {
1010
1213
  font-size: 0.6875rem;
1011
- color: var(--text-muted);
1214
+ color: var(--stone-3);
1012
1215
  max-width: none;
1013
1216
  font-family: var(--font-mono);
1014
1217
  }
1015
1218
  .dashboard-footer .footer-badge {
1016
1219
  font-size: 0.625rem;
1017
- color: var(--text-muted);
1018
- border: 1px solid var(--border);
1019
- border-radius: 6px;
1220
+ color: var(--stone-3);
1221
+ border: 1px solid var(--rule);
1222
+ border-radius: 10px;
1020
1223
  padding: 2px 8px;
1021
1224
  font-family: var(--font-mono);
1022
1225
  text-transform: uppercase;
@@ -1026,31 +1229,38 @@ tr:hover td { background: var(--surface-raised); }
1026
1229
  /* \u2500\u2500 Print \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1027
1230
  @media print {
1028
1231
  body { background: #fff; color: #000; padding: 0; max-width: none; -webkit-font-smoothing: auto; -moz-osx-font-smoothing: auto; }
1029
- :root { --surface: #f5f5f5; --surface-raised: #eee; --border: #ccc; --border-subtle: #ddd; --text-primary: #000; --text-secondary: #555; --text-muted: #777; --bg: #fff; --accent-silver: #333; }
1030
- .chart-wrapper, .metric-card, .narrative-content { border-color: #ccc; background: #f5f5f5; }
1031
- th { background: #eee; }
1032
- thead { display: table-header-group; }
1033
- tr:hover td { background: transparent; }
1034
- .dashboard-footer { border-top-color: #ccc; }
1232
+ :root { --stone: #000; --stone-2: #555; --stone-3: #777; --bg: #fff; --rule: #ddd; --rule-light: #ddd; }
1233
+ section { opacity: 1; transform: none; }
1234
+ .stats-item + .stats-item { border-left-color: #ddd; }
1235
+ .dashboard-footer { border-top-color: #ddd; }
1035
1236
  }
1036
1237
 
1037
1238
  /* \u2500\u2500 Responsive \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
1038
- @media (max-width: 960px) {
1039
- .charts-split { grid-template-columns: 1fr; gap: 16px; }
1040
- }
1041
1239
  @media (max-width: 700px) {
1042
- body { padding: 24px 20px; }
1043
- .metric-cards { grid-template-columns: 1fr; gap: 12px; }
1044
- .dashboard-header { margin-bottom: 32px; }
1045
- .repo-name { font-size: 1.75rem; }
1046
- .narrative-content { padding-right: 0; }
1240
+ body { padding: 48px 24px; }
1241
+ .hero { margin-bottom: 48px; }
1242
+ .stats-item { padding: 16px 12px; }
1243
+ .stats-value { font-size: 1.375rem; }
1244
+ .commit-grid-header { display: none; }
1245
+ .commit-cell.hash { width: 56px; }
1246
+ .commit-cell.date { width: 80px; }
1247
+ .commit-cell.type { width: 56px; }
1047
1248
  }
1048
1249
  @media (max-width: 480px) {
1049
- body { padding: 16px 14px; }
1050
- .metric-card { padding: 20px 16px; }
1051
- .chart-container { height: 220px; }
1250
+ body { padding: 32px 16px; }
1251
+ .commit-grid-header { display: none; }
1252
+ .commit-row { flex-wrap: wrap; gap: 0; }
1253
+ .commit-row .commit-cell { padding: 4px 8px; border-bottom: none; }
1254
+ .commit-row .commit-cell.hash { width: auto; order: 1; }
1255
+ .commit-row .commit-cell.date { width: auto; order: 2; }
1256
+ .commit-row .commit-cell.type { width: 100%; order: 3; padding-top: 0; }
1257
+ .commit-row .commit-cell.subject { width: 100%; flex: none; order: 4; padding-top: 0; }
1052
1258
  .dashboard-footer { flex-direction: column; align-items: flex-start; }
1053
1259
  }
1260
+
1261
+ @media (prefers-reduced-motion: reduce) {
1262
+ section { opacity: 1; transform: none; transition: none; }
1263
+ }
1054
1264
  `;
1055
1265
 
1056
1266
  // src/report/dashboard.html.ts
@@ -1062,7 +1272,7 @@ function buildDashboardData(analysis, commits, repoName, narrative) {
1062
1272
  analysis.classification.perCommit.map((c) => [c.hash, c.type])
1063
1273
  );
1064
1274
  const areaLookup = analysis.areas;
1065
- const recentCommits = commits.slice().reverse().slice(0, 200).map((c) => ({
1275
+ const recentCommits = commits.slice(0, 200).map((c) => ({
1066
1276
  hash: c.hash,
1067
1277
  date: c.date,
1068
1278
  subject: c.subject,
@@ -1085,9 +1295,10 @@ function buildDashboardData(analysis, commits, repoName, narrative) {
1085
1295
  typeCounts: analysis.classification.counts,
1086
1296
  areaCounts: areaCountsArr,
1087
1297
  topTickets,
1298
+ reviewers: analysis.reviewers,
1088
1299
  recentCommits,
1089
1300
  narrative,
1090
- version: true ? "0.1.1" : "dev",
1301
+ version: true ? "0.1.4" : "dev",
1091
1302
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
1092
1303
  };
1093
1304
  }
@@ -1098,7 +1309,8 @@ function computePeriod(commits) {
1098
1309
  const end = dates[dates.length - 1].slice(0, 7);
1099
1310
  return { start, end };
1100
1311
  }
1101
- function assembleDashboard(sections, chartJs, chartInitScript) {
1312
+ function assembleDashboard(header, statsBar, narrative, monthlyChart, typeBars, areaBars, sideBySide, recentCommits, footer, chartJs, chartInitScript) {
1313
+ const scrollObserver = `document.addEventListener("DOMContentLoaded",function(){if(window.matchMedia("(prefers-reduced-motion:reduce)").matches)return;var o=new IntersectionObserver(function(e){e.forEach(function(e,i){if(e.isIntersecting){setTimeout(function(){e.target.classList.add("section-visible")},i*80);o.unobserve(e.target)}})},{threshold:0.1});document.querySelectorAll("section").forEach(function(e){o.observe(e)})});`;
1102
1314
  return `<!DOCTYPE html>
1103
1315
  <html lang="en">
1104
1316
  <head>
@@ -1109,28 +1321,43 @@ function assembleDashboard(sections, chartJs, chartInitScript) {
1109
1321
  <script>${chartJs}</script>
1110
1322
  </head>
1111
1323
  <body>
1112
- ${sections.header}
1113
- ${sections.metricCards}
1114
- ${sections.narrative}
1115
- ${sections.charts}
1116
- ${sections.topTickets}
1117
- ${sections.recentCommits}
1118
- ${sections.footer}
1324
+ ${header}
1325
+ ${statsBar}
1326
+ ${narrative}
1327
+ ${monthlyChart}
1328
+ ${typeBars}
1329
+ ${areaBars}
1330
+ ${sideBySide}
1331
+ ${recentCommits}
1332
+ ${footer}
1119
1333
  <script>${chartInitScript}</script>
1334
+ <script>${scrollObserver}</script>
1120
1335
  </body>
1121
1336
  </html>`;
1122
1337
  }
1123
1338
  function buildSections(data, chartJs, chartInitScript) {
1124
- const sections = {
1125
- header: renderHeader(data.repoName, data.period),
1126
- metricCards: renderMetricCards(data.totals),
1127
- charts: renderChartContainers(),
1128
- topTickets: renderTopTickets(data.topTickets),
1129
- recentCommits: renderRecentCommits(data.recentCommits),
1130
- narrative: renderNarrativeBlock(data.narrative),
1131
- footer: renderFooter(data.version ?? "0.0.0-dev", data.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString())
1132
- };
1133
- return assembleDashboard(sections, chartJs, chartInitScript);
1339
+ const generatedAt = data.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
1340
+ const ticketsHtml = renderTickets(data.topTickets);
1341
+ const reviewersHtml = renderReviewers(data.reviewers);
1342
+ let sideBySide = "";
1343
+ if (ticketsHtml && reviewersHtml) {
1344
+ sideBySide = `<div class="side-grid">${ticketsHtml}${reviewersHtml}</div>`;
1345
+ } else {
1346
+ sideBySide = ticketsHtml + reviewersHtml;
1347
+ }
1348
+ return assembleDashboard(
1349
+ renderHeader(data.repoName, data.period, generatedAt, data.totals),
1350
+ renderStatsBar(data.totals),
1351
+ renderNarrativeBlock(data.narrative),
1352
+ renderMonthlyChart(),
1353
+ renderTypeBars(data.typeCounts),
1354
+ renderAreaBars(data.areaCounts),
1355
+ sideBySide,
1356
+ renderRecentCommits(data.recentCommits),
1357
+ renderFooter(data.version ?? "0.0.0-dev", generatedAt),
1358
+ chartJs,
1359
+ chartInitScript
1360
+ );
1134
1361
  }
1135
1362
 
1136
1363
  // src/report/render.ts
@@ -1146,56 +1373,11 @@ function monthlyActivityConfig(timeline) {
1146
1373
  {
1147
1374
  label: "Commits",
1148
1375
  data: timeline.map((b) => b.count),
1149
- backgroundColor: "rgba(226, 232, 240, 0.5)",
1150
- borderColor: "rgba(226, 232, 240, 0.8)",
1151
- borderWidth: 1,
1152
- borderRadius: 3
1153
- }
1154
- ]
1155
- },
1156
- options: {
1157
- responsive: true,
1158
- maintainAspectRatio: false,
1159
- plugins: { legend: { display: false } },
1160
- scales: {
1161
- x: { ticks: { color: "#6b7280" }, grid: { color: "rgba(255,255,255,0.06)" } },
1162
- y: {
1163
- ticks: { color: "#6b7280", stepSize: 1 },
1164
- grid: { color: "rgba(255,255,255,0.06)" }
1165
- }
1166
- }
1167
- }
1168
- };
1169
- }
1170
- function typeBreakdownConfig(counts) {
1171
- const labels = Object.keys(counts);
1172
- const data = Object.values(counts);
1173
- const COLORS = {
1174
- feat: "#34d399",
1175
- fix: "#fbbf24",
1176
- chore: "#6b7280",
1177
- docs: "#60a5fa",
1178
- refactor: "#a78bfa",
1179
- test: "#f87171",
1180
- style: "#34d399",
1181
- perf: "#fb923c",
1182
- ci: "#a78bfa",
1183
- build: "#2dd4bf",
1184
- revert: "#f87171",
1185
- merge: "#e2e8f0",
1186
- other: "#4b5563"
1187
- };
1188
- return {
1189
- type: "doughnut",
1190
- data: {
1191
- labels,
1192
- datasets: [
1193
- {
1194
- data,
1195
- backgroundColor: labels.map(
1196
- (l) => COLORS[l] ?? COLORS.other
1197
- ),
1198
- borderWidth: 0
1376
+ backgroundColor: "#C4763A",
1377
+ borderColor: "#C4763A",
1378
+ borderWidth: 0,
1379
+ borderRadius: 2,
1380
+ hoverBackgroundColor: "#D48A4A"
1199
1381
  }
1200
1382
  ]
1201
1383
  },
@@ -1203,41 +1385,26 @@ function typeBreakdownConfig(counts) {
1203
1385
  responsive: true,
1204
1386
  maintainAspectRatio: false,
1205
1387
  plugins: {
1206
- legend: {
1207
- position: "bottom",
1208
- labels: { color: "#6b7280", padding: 14, font: { family: "ui-monospace, SFMono-Regular, Consolas, monospace", size: 10 } }
1388
+ legend: { display: false },
1389
+ tooltip: {
1390
+ backgroundColor: "#1A1810",
1391
+ titleFont: { size: 11, family: "ui-monospace, SFMono-Regular, Consolas, monospace" },
1392
+ bodyFont: { size: 12, family: "ui-monospace, SFMono-Regular, Consolas, monospace" },
1393
+ padding: 10,
1394
+ cornerRadius: 4,
1395
+ borderColor: "rgba(242,237,230,0.12)",
1396
+ borderWidth: 1
1209
1397
  }
1210
- }
1211
- }
1212
- };
1213
- }
1214
- function areaBreakdownConfig(areas) {
1215
- return {
1216
- type: "bar",
1217
- indexAxis: "y",
1218
- data: {
1219
- labels: areas.map((a) => a.area),
1220
- datasets: [
1221
- {
1222
- label: "Commits",
1223
- data: areas.map((a) => a.count),
1224
- backgroundColor: "rgba(226, 232, 240, 0.5)",
1225
- borderColor: "rgba(226, 232, 240, 0.8)",
1226
- borderWidth: 1,
1227
- borderRadius: 3
1228
- }
1229
- ]
1230
- },
1231
- options: {
1232
- responsive: true,
1233
- maintainAspectRatio: false,
1234
- plugins: { legend: { display: false } },
1398
+ },
1235
1399
  scales: {
1236
1400
  x: {
1237
- ticks: { color: "#6b7280", stepSize: 1 },
1238
- grid: { color: "rgba(255,255,255,0.06)" }
1401
+ ticks: { color: "#7A7060", font: { size: 10, family: "ui-monospace, SFMono-Regular, Consolas, monospace" } },
1402
+ grid: { color: "rgba(242,237,230,0.05)" }
1239
1403
  },
1240
- y: { ticks: { color: "#a0aaba" }, grid: { display: false } }
1404
+ y: {
1405
+ ticks: { color: "#7A7060", stepSize: 1, font: { size: 10, family: "ui-monospace, SFMono-Regular, Consolas, monospace" } },
1406
+ grid: { color: "rgba(242,237,230,0.05)" }
1407
+ }
1241
1408
  }
1242
1409
  }
1243
1410
  };
@@ -1247,21 +1414,11 @@ function areaBreakdownConfig(areas) {
1247
1414
  function buildMonthlyConfig(data) {
1248
1415
  return monthlyActivityConfig(data.timeline);
1249
1416
  }
1250
- function buildTypeConfig(data) {
1251
- return typeBreakdownConfig(data.typeCounts);
1252
- }
1253
- function buildAreaConfig(data) {
1254
- return areaBreakdownConfig(data.areaCounts);
1255
- }
1256
1417
  function buildChartInitScript(data) {
1257
1418
  const monthly = JSON.stringify(buildMonthlyConfig(data));
1258
- const types = JSON.stringify(buildTypeConfig(data));
1259
- const areas = JSON.stringify(buildAreaConfig(data));
1260
1419
  return `
1261
1420
  document.addEventListener("DOMContentLoaded", function () {
1262
1421
  new Chart(document.getElementById("chart-monthly"), ${monthly});
1263
- new Chart(document.getElementById("chart-types"), ${types});
1264
- new Chart(document.getElementById("chart-areas"), ${areas});
1265
1422
  });`;
1266
1423
  }
1267
1424
 
@@ -1546,6 +1703,12 @@ function registerGenerateCommand(program) {
1546
1703
  });
1547
1704
  commits = result.commits;
1548
1705
  }
1706
+ if (opts.author) {
1707
+ const needle = opts.author.toLowerCase();
1708
+ commits = commits.filter(
1709
+ (c) => c.authorName.toLowerCase().includes(needle) || c.authorEmail.toLowerCase().includes(needle)
1710
+ );
1711
+ }
1549
1712
  endPhase(t1, `${commits.length} commits `);
1550
1713
  if (commits.length === 0) {
1551
1714
  console.error("No commits found.");
@@ -1559,6 +1722,7 @@ function registerGenerateCommand(program) {
1559
1722
  typeCounts: {},
1560
1723
  areaCounts: [],
1561
1724
  topTickets: [],
1725
+ reviewers: [],
1562
1726
  recentCommits: []
1563
1727
  },
1564
1728
  chartJs: opts.cdnCharts ? "" : CHART_JS
@@ -1648,7 +1812,7 @@ function getRepoName(repoPath) {
1648
1812
  }
1649
1813
 
1650
1814
  // src/cli.ts
1651
- var VERSION = true ? "0.1.1" : "0.0.0-dev";
1815
+ var VERSION = true ? "0.1.4" : "0.0.0-dev";
1652
1816
  function buildProgram() {
1653
1817
  const program = new Command("commit-insights");
1654
1818
  program.description("Generate a local git contribution dashboard from commit history").version(VERSION);
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  buildProgram
3
- } from "./chunk-IVFHXIJZ.js";
3
+ } from "./chunk-LNNF6KV7.js";
4
4
  import "./chunk-JSBRDJBE.js";
5
5
  export {
6
6
  buildProgram
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "commit-insights",
3
- "version": "0.1.1",
3
+ "version": "0.1.4",
4
4
  "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/hinedy/commit-insights.git"
8
+ },
9
+ "homepage": "https://github.com/hinedy/commit-insights#readme",
5
10
  "description": "Generate a local git contribution dashboard from commit history",
6
11
  "type": "module",
7
12
  "engines": {