repo-dive 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +196 -46
- package/dist/config.d.ts +14 -0
- package/dist/config.js +15 -0
- package/dist/dashboard/assets/index-1G4_D1Zz.css +2 -0
- package/dist/dashboard/assets/index-CRLzUhgz.js +10 -0
- package/dist/dashboard/index.html +2 -2
- package/package.json +1 -1
- package/dist/dashboard/assets/index-BKx1tskf.css +0 -2
- package/dist/dashboard/assets/index-Ds8-JhLY.js +0 -9
package/dist/cli.js
CHANGED
|
@@ -42694,7 +42694,7 @@ var runWith = (command, config) => {
|
|
|
42694
42694
|
};
|
|
42695
42695
|
var package_default = {
|
|
42696
42696
|
name: "repo-dive",
|
|
42697
|
-
version: "0.
|
|
42697
|
+
version: "0.15.0",
|
|
42698
42698
|
description: "Dive into a git repo's history: per-commit snapshots, an indexed metrics catalog and an interactive dashboard",
|
|
42699
42699
|
keywords: [
|
|
42700
42700
|
"git",
|
|
@@ -50060,6 +50060,23 @@ var collectorsCommand = make$5("collectors").pipe(withDescription("List availabl
|
|
|
50060
50060
|
].join("\n"));
|
|
50061
50061
|
})));
|
|
50062
50062
|
//#endregion
|
|
50063
|
+
//#region src/config.ts
|
|
50064
|
+
/**
|
|
50065
|
+
* Stable identifiers of the dashboard's chart sections, in page order — the
|
|
50066
|
+
* keys {@link ChartsConfig.annotations} accepts.
|
|
50067
|
+
*/
|
|
50068
|
+
var chartIds = [
|
|
50069
|
+
"lines-of-code",
|
|
50070
|
+
"commit-calendar",
|
|
50071
|
+
"commits-per-month",
|
|
50072
|
+
"churn-per-month",
|
|
50073
|
+
"direct-dependencies",
|
|
50074
|
+
"dependencies",
|
|
50075
|
+
"loose-ends",
|
|
50076
|
+
"most-suppressed-eslint-rules",
|
|
50077
|
+
"contributors"
|
|
50078
|
+
];
|
|
50079
|
+
//#endregion
|
|
50063
50080
|
//#region src/cli/shared/config.ts
|
|
50064
50081
|
/** "12345+alice@users.noreply.github.com" → "alice"; other emails unchanged. */
|
|
50065
50082
|
var prettifyAuthorEmail = (email) => {
|
|
@@ -50211,6 +50228,18 @@ var parseWeekStartsOn = (value) => {
|
|
|
50211
50228
|
for (const weekStart of weekStarts) if (value === weekStart) return weekStart;
|
|
50212
50229
|
throw configError("`charts.weekStartsOn` must be \"monday\" or \"sunday\".");
|
|
50213
50230
|
};
|
|
50231
|
+
var parseChartAnnotations = (value) => {
|
|
50232
|
+
if (value === void 0) return {};
|
|
50233
|
+
if (!isPlainObject(value)) throw configError("`charts.annotations` must be an object.");
|
|
50234
|
+
const annotations = {};
|
|
50235
|
+
for (const [key, markdown] of Object.entries(value)) {
|
|
50236
|
+
const chartId = chartIds.find((id) => id === key);
|
|
50237
|
+
if (chartId === void 0) throw configError(`\`charts.annotations\` has an unknown chart id ${JSON.stringify(key)}; known ids: ${chartIds.join(", ")}.`);
|
|
50238
|
+
if (typeof markdown !== "string" || markdown.trim() === "") throw configError(`\`charts.annotations.${chartId}\` must be a non-empty string.`);
|
|
50239
|
+
annotations[chartId] = markdown.trim();
|
|
50240
|
+
}
|
|
50241
|
+
return annotations;
|
|
50242
|
+
};
|
|
50214
50243
|
/**
|
|
50215
50244
|
* Resolves `catalog.dir` against the repository root. `gc` deletes whole
|
|
50216
50245
|
* subtrees under the result, so the two placements that would take the
|
|
@@ -50252,6 +50281,7 @@ var resolveConfig = (raw, repoRoot) => {
|
|
|
50252
50281
|
const charts = prop(raw, "charts");
|
|
50253
50282
|
if (charts !== void 0 && !isPlainObject(charts)) throw configError("`charts` must be an object.");
|
|
50254
50283
|
const weekStartsOn = parseWeekStartsOn(charts === void 0 ? void 0 : prop(charts, "weekStartsOn"));
|
|
50284
|
+
const chartAnnotations = parseChartAnnotations(charts === void 0 ? void 0 : prop(charts, "annotations"));
|
|
50255
50285
|
const catalog = prop(raw, "catalog");
|
|
50256
50286
|
if (catalog !== void 0 && !isPlainObject(catalog)) throw configError("`catalog` must be an object.");
|
|
50257
50287
|
const catalogPath = parseCatalogPath(catalog === void 0 ? void 0 : prop(catalog, "dir"), repoRoot);
|
|
@@ -50259,6 +50289,7 @@ var resolveConfig = (raw, repoRoot) => {
|
|
|
50259
50289
|
resolveContributor: (email, name) => bareResolveContributor(aliasMap, email, name),
|
|
50260
50290
|
maxInCharts,
|
|
50261
50291
|
weekStartsOn,
|
|
50292
|
+
chartAnnotations,
|
|
50262
50293
|
catalogPath,
|
|
50263
50294
|
catalogRelativePath: relativeCatalogPath(repoRoot, catalogPath),
|
|
50264
50295
|
checkIgnoreFiles: parseCheckIgnoreFiles(catalog === void 0 ? void 0 : prop(catalog, "checkIgnoreFiles"))
|
|
@@ -50845,8 +50876,8 @@ var haveUnrelatedHistories = (repoRoot, left, right) => runGit([
|
|
|
50845
50876
|
right
|
|
50846
50877
|
], { okExitCodes: [1] }).pipe(map$7((stdout) => stdout.trim() === ""));
|
|
50847
50878
|
/**
|
|
50848
|
-
* The first-parent
|
|
50849
|
-
* founding graft, or
|
|
50879
|
+
* The first-parent chains that continue `chain` backwards in time across a
|
|
50880
|
+
* founding graft, or no chains when there is none.
|
|
50850
50881
|
*
|
|
50851
50882
|
* A repository migration (monorepo assembly, host move, history rewrite)
|
|
50852
50883
|
* leaves a recognizable signature: a fresh root commit followed immediately by
|
|
@@ -50861,53 +50892,107 @@ var haveUnrelatedHistories = (repoRoot, left, right) => runGit([
|
|
|
50861
50892
|
* history that overlaps the chain (e.g. a plugin repository absorbed while
|
|
50862
50893
|
* mainline development continued) does not qualify.
|
|
50863
50894
|
*
|
|
50864
|
-
*
|
|
50865
|
-
* eight)
|
|
50866
|
-
*
|
|
50895
|
+
* Every qualifying absorbed history is returned (effect's monorepo assembly
|
|
50896
|
+
* merged eight): before the migration they were the project's parallel parts,
|
|
50897
|
+
* so composed timelines sum all of them until the assembly replaces them.
|
|
50867
50898
|
*/
|
|
50868
|
-
var
|
|
50899
|
+
var findFoundingGraft = (repoRoot, chain, alreadyClaimed) => gen(function* () {
|
|
50869
50900
|
const root = chain.at(-1);
|
|
50870
|
-
if (root === void 0) return
|
|
50901
|
+
if (root === void 0) return {
|
|
50902
|
+
absorbed: [],
|
|
50903
|
+
assemblyShas: []
|
|
50904
|
+
};
|
|
50871
50905
|
const rootDate = Date.parse(root.committerDate);
|
|
50872
50906
|
const candidateTips = [];
|
|
50907
|
+
const assemblyShas = [root.hash];
|
|
50873
50908
|
for (let index = chain.length - 2; index >= 0; index -= 1) {
|
|
50874
50909
|
const entry = chain[index];
|
|
50875
50910
|
if (entry === void 0 || entry.parentHashes.length < 2) break;
|
|
50876
50911
|
candidateTips.push(...entry.parentHashes.slice(1));
|
|
50912
|
+
assemblyShas.push(entry.hash);
|
|
50877
50913
|
}
|
|
50878
|
-
|
|
50879
|
-
let bestRootDate = Number.POSITIVE_INFINITY;
|
|
50914
|
+
const absorbed = [];
|
|
50880
50915
|
for (const tip of candidateTips) {
|
|
50881
|
-
if (
|
|
50916
|
+
if (alreadyClaimed.has(tip)) continue;
|
|
50882
50917
|
if (!(yield* haveUnrelatedHistories(repoRoot, root.hash, tip))) continue;
|
|
50883
50918
|
const candidate = yield* listFirstParentChain(repoRoot, tip);
|
|
50884
50919
|
const tipDate = Date.parse(candidate.at(0)?.committerDate ?? "");
|
|
50885
50920
|
const candidateRootDate = Date.parse(candidate.at(-1)?.committerDate ?? "");
|
|
50886
50921
|
if (Number.isNaN(tipDate) || Number.isNaN(candidateRootDate) || tipDate >= rootDate) continue;
|
|
50887
|
-
|
|
50888
|
-
|
|
50889
|
-
|
|
50890
|
-
|
|
50922
|
+
absorbed.push(candidate);
|
|
50923
|
+
}
|
|
50924
|
+
return {
|
|
50925
|
+
absorbed,
|
|
50926
|
+
assemblyShas
|
|
50927
|
+
};
|
|
50928
|
+
});
|
|
50929
|
+
/**
|
|
50930
|
+
* The lineages whose tree states describe the project over time — the states
|
|
50931
|
+
* the repository (or, before a migration, its absorbed predecessors) actually
|
|
50932
|
+
* passed through. The first lineage is HEAD's own first-parent chain; each
|
|
50933
|
+
* founding graft on any lineage (see {@link findFoundingGraft}) contributes
|
|
50934
|
+
* the absorbed histories as further lineages, recursively, each ending where
|
|
50935
|
+
* the assembly that absorbed it completes. Ordinary side branches and
|
|
50936
|
+
* histories grafted mid-life stay excluded — their trees were never the
|
|
50937
|
+
* repository's state. See {@link describesTreeState} for what consumes this.
|
|
50938
|
+
*/
|
|
50939
|
+
var listLineages = (repoRoot) => gen(function* () {
|
|
50940
|
+
const lineages = [];
|
|
50941
|
+
/** Every sha assigned to a lineage (or queued): overlap and cycle guard. */
|
|
50942
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
50943
|
+
const initial = yield* listFirstParentChain(repoRoot);
|
|
50944
|
+
const queue = initial.length > 0 ? [{
|
|
50945
|
+
chain: initial,
|
|
50946
|
+
endsAtMs: Number.POSITIVE_INFINITY
|
|
50947
|
+
}] : [];
|
|
50948
|
+
while (queue.length > 0) {
|
|
50949
|
+
const pending = queue.shift();
|
|
50950
|
+
if (pending === void 0) break;
|
|
50951
|
+
const { chain, endsAtMs } = pending;
|
|
50952
|
+
for (const entry of chain) claimed.add(entry.hash);
|
|
50953
|
+
const graft = yield* findFoundingGraft(repoRoot, chain, claimed);
|
|
50954
|
+
const shas = new Set(chain.map((entry) => entry.hash));
|
|
50955
|
+
let absorbedEndsAtMs = endsAtMs;
|
|
50956
|
+
if (graft.absorbed.length > 0 && graft.assemblyShas.length < chain.length) {
|
|
50957
|
+
for (const sha of graft.assemblyShas) shas.delete(sha);
|
|
50958
|
+
const firstPostAssembly = chain.at(-1 - graft.assemblyShas.length);
|
|
50959
|
+
if (firstPostAssembly !== void 0) absorbedEndsAtMs = Date.parse(firstPostAssembly.committerDate);
|
|
50960
|
+
}
|
|
50961
|
+
lineages.push({
|
|
50962
|
+
shas,
|
|
50963
|
+
endsAtMs
|
|
50964
|
+
});
|
|
50965
|
+
for (const absorbedChain of graft.absorbed) queue.push({
|
|
50966
|
+
chain: absorbedChain,
|
|
50967
|
+
endsAtMs: absorbedEndsAtMs
|
|
50968
|
+
});
|
|
50891
50969
|
}
|
|
50892
|
-
return
|
|
50970
|
+
return lineages;
|
|
50893
50971
|
});
|
|
50894
50972
|
/**
|
|
50895
|
-
*
|
|
50896
|
-
*
|
|
50897
|
-
* (see {@link findFoundingGraftChain}): when a migration absorbed the
|
|
50898
|
-
* project's previous history behind a fresh root, the absorbed mainline is the
|
|
50899
|
-
* continuation of the timeline. See {@link describesTreeState} for why
|
|
50900
|
-
* snapshot collectors are restricted to the mainline.
|
|
50973
|
+
* Every sha on any lineage — what `scan` samples tree snapshots from, `gc`
|
|
50974
|
+
* refuses to reclaim and `index` admits into the cube.
|
|
50901
50975
|
*/
|
|
50902
|
-
var listMainlineShas = (repoRoot) =>
|
|
50976
|
+
var listMainlineShas = (repoRoot) => listLineages(repoRoot).pipe(map$7((lineages) => {
|
|
50903
50977
|
const shas = /* @__PURE__ */ new Set();
|
|
50904
|
-
|
|
50905
|
-
|
|
50906
|
-
|
|
50907
|
-
|
|
50978
|
+
for (const lineage of lineages) for (const sha of lineage.shas) shas.add(sha);
|
|
50979
|
+
return shas;
|
|
50980
|
+
}));
|
|
50981
|
+
/**
|
|
50982
|
+
* The commits a tree-state collector should cover: `policy` applied to each
|
|
50983
|
+
* lineage separately, so a monthly policy keeps one snapshot per month of
|
|
50984
|
+
* *each* parallel pre-migration history rather than one per month overall.
|
|
50985
|
+
* Shared by `scan` (planning) and `status` (progress targets), which must
|
|
50986
|
+
* count the same way.
|
|
50987
|
+
*/
|
|
50988
|
+
var sampleTreeCommits = (lineages, commits, policy) => {
|
|
50989
|
+
const shas = /* @__PURE__ */ new Set();
|
|
50990
|
+
for (const lineage of lineages) {
|
|
50991
|
+
const candidates = commits.filter((commit) => lineage.shas.has(commit.hash));
|
|
50992
|
+
for (const commit of sampleCommits(candidates, policy)) shas.add(commit.hash);
|
|
50908
50993
|
}
|
|
50909
50994
|
return shas;
|
|
50910
|
-
}
|
|
50995
|
+
};
|
|
50911
50996
|
var summarizeCommits = (commits) => {
|
|
50912
50997
|
const authorEmails = new Set(commits.map((commit) => commit.authorEmail));
|
|
50913
50998
|
const dates = commits.flatMap((commit) => [commit.authorDate, commit.committerDate]).filter((date) => !Number.isNaN(Date.parse(date))).toSorted((left, right) => Date.parse(left) - Date.parse(right));
|
|
@@ -50987,14 +51072,13 @@ var runScan = ({ repoPath, collectorNames, maxCommits, sample, force = false })
|
|
|
50987
51072
|
const summary = summarizeCommits(commits);
|
|
50988
51073
|
const cacheKeys = new Map(collectors.map((collector) => [collector.name, collectorCacheKey(collector, config)]));
|
|
50989
51074
|
const cacheKeyOf = (collector) => cacheKeys.get(collector.name) ?? collectorCacheKey(collector, config);
|
|
50990
|
-
const
|
|
51075
|
+
const lineages = yield* listLineages(repoRoot);
|
|
50991
51076
|
const plans = collectors.map((collector) => {
|
|
50992
51077
|
const policy = sampleOverride ?? collector.defaultSampling;
|
|
50993
|
-
const candidates = describesTreeState(collector) ? selected.filter((commit) => mainlineShas.has(commit.hash)) : selected;
|
|
50994
51078
|
return {
|
|
50995
51079
|
collector,
|
|
50996
51080
|
policy,
|
|
50997
|
-
shas: new Set(sampleCommits(
|
|
51081
|
+
shas: describesTreeState(collector) ? sampleTreeCommits(lineages, selected, policy) : new Set(sampleCommits(selected, policy).map((commit) => commit.hash))
|
|
50998
51082
|
};
|
|
50999
51083
|
});
|
|
51000
51084
|
yield* log(`Plan: ${plans.map((plan) => `${plan.collector.name} → ${plan.shas.size} commits (${samplingLabel(plan.policy)})`).join(", ")}`);
|
|
@@ -51436,6 +51520,48 @@ var ignoreCommand = make$5("ignore", {
|
|
|
51436
51520
|
dryRun: config.dryRun
|
|
51437
51521
|
})));
|
|
51438
51522
|
//#endregion
|
|
51523
|
+
//#region src/cli/shared/compose-lineage-series.ts
|
|
51524
|
+
/** Adds `source`'s numeric leaves into `target`, recursing through records. */
|
|
51525
|
+
var addValuesInto = (target, source) => {
|
|
51526
|
+
for (const [key, value] of Object.entries(source)) {
|
|
51527
|
+
if (key === "sha" || key === "date") continue;
|
|
51528
|
+
if (typeof value === "number") {
|
|
51529
|
+
const current = target[key];
|
|
51530
|
+
target[key] = (typeof current === "number" ? current : 0) + value;
|
|
51531
|
+
} else if (value !== null && typeof value === "object") {
|
|
51532
|
+
const existing = target[key];
|
|
51533
|
+
const nested = existing !== null && typeof existing === "object" ? existing : {};
|
|
51534
|
+
target[key] = nested;
|
|
51535
|
+
addValuesInto(nested, value);
|
|
51536
|
+
}
|
|
51537
|
+
}
|
|
51538
|
+
};
|
|
51539
|
+
/**
|
|
51540
|
+
* One composed row per snapshot, in input order (which must be chronological
|
|
51541
|
+
* by `date`): the snapshot's own values plus the latest values of every other
|
|
51542
|
+
* lineage still alive at that instant. A single-lineage input — every repo
|
|
51543
|
+
* without a founding graft — composes to exactly its own rows.
|
|
51544
|
+
*/
|
|
51545
|
+
var composeLineageSeries = (snapshots) => {
|
|
51546
|
+
const latest = /* @__PURE__ */ new Map();
|
|
51547
|
+
return snapshots.map((snapshot) => {
|
|
51548
|
+
latest.set(snapshot.lineage, snapshot);
|
|
51549
|
+
const timeMs = Date.parse(snapshot.row.date);
|
|
51550
|
+
const composed = {
|
|
51551
|
+
sha: snapshot.row.sha,
|
|
51552
|
+
date: snapshot.row.date
|
|
51553
|
+
};
|
|
51554
|
+
for (const [lineage, candidate] of latest) {
|
|
51555
|
+
if (candidate !== snapshot && timeMs >= candidate.lineageEndsAtMs) {
|
|
51556
|
+
latest.delete(lineage);
|
|
51557
|
+
continue;
|
|
51558
|
+
}
|
|
51559
|
+
addValuesInto(composed, candidate.row);
|
|
51560
|
+
}
|
|
51561
|
+
return composed;
|
|
51562
|
+
});
|
|
51563
|
+
};
|
|
51564
|
+
//#endregion
|
|
51439
51565
|
//#region src/cli/shared/remote.ts
|
|
51440
51566
|
/**
|
|
51441
51567
|
* Turns whatever `remote.origin.url` holds into a URL a browser can open, or
|
|
@@ -51560,7 +51686,22 @@ var spanOf = (commits) => {
|
|
|
51560
51686
|
last
|
|
51561
51687
|
};
|
|
51562
51688
|
};
|
|
51563
|
-
var buildDashboardData = (repoRoot, commits, config, remoteUrl) => {
|
|
51689
|
+
var buildDashboardData = (repoRoot, commits, config, remoteUrl, lineageOf) => {
|
|
51690
|
+
/**
|
|
51691
|
+
* Rows of one tree-state series: eligible snapshots tagged with their
|
|
51692
|
+
* lineage, summed across the lineages alive at each instant. For a repo
|
|
51693
|
+
* with a single lineage (no founding graft) this is exactly the plain
|
|
51694
|
+
* filter-and-map it replaces.
|
|
51695
|
+
*/
|
|
51696
|
+
const composeSeries = (eligible, toRow) => composeLineageSeries(commits.flatMap((commit) => {
|
|
51697
|
+
if (!eligible(commit)) return [];
|
|
51698
|
+
const lineage = lineageOf(commit.sha);
|
|
51699
|
+
return lineage === void 0 ? [] : [{
|
|
51700
|
+
row: toRow(commit),
|
|
51701
|
+
lineage: lineage.index,
|
|
51702
|
+
lineageEndsAtMs: lineage.endsAtMs
|
|
51703
|
+
}];
|
|
51704
|
+
}));
|
|
51564
51705
|
/** The raw `"Name <email>"` trailers on a commit, in the order git listed them. */
|
|
51565
51706
|
const coAuthorsOf = (commit) => [...commit.factsByCollector.values()].flat().filter((fact) => fact.metric === "commits.coAuthor").map((fact) => fact.categories?.["coAuthor"] ?? "");
|
|
51566
51707
|
/**
|
|
@@ -51585,18 +51726,18 @@ var buildDashboardData = (repoRoot, commits, config, remoteUrl) => {
|
|
|
51585
51726
|
added: sumMetric(commit, "churn.added"),
|
|
51586
51727
|
deleted: sumMetric(commit, "churn.deleted")
|
|
51587
51728
|
}));
|
|
51588
|
-
const languages =
|
|
51729
|
+
const languages = composeSeries((commit) => hasMetric(commit, "languages.lines"), (commit) => ({
|
|
51589
51730
|
sha: commit.sha.slice(0, 10),
|
|
51590
51731
|
date: commit.committedAt,
|
|
51591
51732
|
byLanguage: groupMetric(commit, "languages.lines", "language")
|
|
51592
51733
|
}));
|
|
51593
|
-
const fileTypes =
|
|
51734
|
+
const fileTypes = composeSeries((commit) => hasMetric(commit, "files.count"), (commit) => ({
|
|
51594
51735
|
sha: commit.sha.slice(0, 10),
|
|
51595
51736
|
date: commit.committedAt,
|
|
51596
51737
|
totalFiles: sumMetric(commit, "files.count"),
|
|
51597
51738
|
totalBytes: sumMetric(commit, "files.bytes")
|
|
51598
51739
|
}));
|
|
51599
|
-
const directives =
|
|
51740
|
+
const directives = composeSeries((commit) => hasMetric(commit, "directives.ts"), (commit) => {
|
|
51600
51741
|
const byType = groupMetric(commit, "directives.eslint", "type");
|
|
51601
51742
|
const ts = groupMetric(commit, "directives.ts", "type");
|
|
51602
51743
|
return {
|
|
@@ -51612,7 +51753,7 @@ var buildDashboardData = (repoRoot, commits, config, remoteUrl) => {
|
|
|
51612
51753
|
todos: sumMetric(commit, "todos.count")
|
|
51613
51754
|
};
|
|
51614
51755
|
});
|
|
51615
|
-
const dependencies =
|
|
51756
|
+
const dependencies = composeSeries((commit) => hasMetric(commit, "dependencies.resolved") || hasMetric(commit, "dependencies.manifest") || hasMetric(commit, "dependencies.scanned"), (commit) => {
|
|
51616
51757
|
const byKind = groupMetric(commit, "dependencies.direct", "kind");
|
|
51617
51758
|
return {
|
|
51618
51759
|
sha: commit.sha.slice(0, 10),
|
|
@@ -51658,7 +51799,7 @@ var buildDashboardData = (repoRoot, commits, config, remoteUrl) => {
|
|
|
51658
51799
|
if (resolved.kind !== "human") return kindGroupLabels[resolved.kind];
|
|
51659
51800
|
return nameByCanonicalEmail.get(resolved.canonicalEmail.toLowerCase()) ?? resolved.label;
|
|
51660
51801
|
};
|
|
51661
|
-
const survival =
|
|
51802
|
+
const survival = composeSeries((commit) => hasMetric(commit, "survival.lines"), (commit) => {
|
|
51662
51803
|
const byContributorYear = {};
|
|
51663
51804
|
const byLanguageYear = {};
|
|
51664
51805
|
for (const facts of commit.factsByCollector.values()) for (const fact of facts) {
|
|
@@ -51755,7 +51896,10 @@ var buildDashboardData = (repoRoot, commits, config, remoteUrl) => {
|
|
|
51755
51896
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
51756
51897
|
config: {
|
|
51757
51898
|
contributors: { maxInCharts: config.maxInCharts },
|
|
51758
|
-
charts: {
|
|
51899
|
+
charts: {
|
|
51900
|
+
weekStartsOn: config.weekStartsOn,
|
|
51901
|
+
...Object.keys(config.chartAnnotations).length > 0 ? { annotations: config.chartAnnotations } : {}
|
|
51902
|
+
}
|
|
51759
51903
|
},
|
|
51760
51904
|
repo: {
|
|
51761
51905
|
name: remoteUrl?.split("/").at(-1) ?? path.basename(repoRoot),
|
|
@@ -51824,7 +51968,13 @@ var runIndex = ({ repoPath }) => gen(function* () {
|
|
|
51824
51968
|
const commitsPath = path.join(catalogPath, "commits");
|
|
51825
51969
|
const registry = new Map(builtInCollectors.map((collector) => [collector.name, collector]));
|
|
51826
51970
|
const gitCommits = yield* listCommits(repoRoot);
|
|
51827
|
-
const
|
|
51971
|
+
const lineages = yield* listLineages(repoRoot);
|
|
51972
|
+
/** sha → its lineage's index and contribution window end. */
|
|
51973
|
+
const lineageBySha = /* @__PURE__ */ new Map();
|
|
51974
|
+
for (const [index, lineage] of lineages.entries()) for (const sha of lineage.shas) lineageBySha.set(sha, {
|
|
51975
|
+
index,
|
|
51976
|
+
endsAtMs: lineage.endsAtMs
|
|
51977
|
+
});
|
|
51828
51978
|
const remoteUrl = yield* readRemoteUrl(repoRoot);
|
|
51829
51979
|
const catalogShas = new Set(yield* tryPromise(async () => {
|
|
51830
51980
|
try {
|
|
@@ -51837,7 +51987,7 @@ var runIndex = ({ repoPath }) => gen(function* () {
|
|
|
51837
51987
|
if (orderedCommits.length === 0) return yield* new NoCollectedCommitsError({ commitsPath });
|
|
51838
51988
|
const readOutcomes = yield* forEach$1(orderedCommits, (commit) => tryPromise(async () => {
|
|
51839
51989
|
const commitDir = path.join(commitsPath, commit.hash);
|
|
51840
|
-
const onMainline =
|
|
51990
|
+
const onMainline = lineageBySha.has(commit.hash);
|
|
51841
51991
|
const factsByCollector = /* @__PURE__ */ new Map();
|
|
51842
51992
|
let unknownCollectorDirs = 0;
|
|
51843
51993
|
let offMainlineSnapshots = 0;
|
|
@@ -51875,7 +52025,7 @@ var runIndex = ({ repoPath }) => gen(function* () {
|
|
|
51875
52025
|
const dbPath = path.join(indexDir, "metrics.sqlite");
|
|
51876
52026
|
yield* tryPromise(() => rm(dbPath, { force: true }));
|
|
51877
52027
|
const factCount = yield* try_(() => writeSqlite(dbPath, commitFacts));
|
|
51878
|
-
const dashboardData = buildDashboardData(repoRoot, commitFacts, config, remoteUrl);
|
|
52028
|
+
const dashboardData = buildDashboardData(repoRoot, commitFacts, config, remoteUrl, (sha) => lineageBySha.get(sha));
|
|
51879
52029
|
const dashboardPath = path.join(indexDir, "dashboard.json");
|
|
51880
52030
|
yield* tryPromise(() => writeFile(dashboardPath, JSON.stringify(dashboardData), "utf8"));
|
|
51881
52031
|
yield* log([
|
|
@@ -51883,7 +52033,7 @@ var runIndex = ({ repoPath }) => gen(function* () {
|
|
|
51883
52033
|
`Cube: ${dbPath}`,
|
|
51884
52034
|
`Dashboard data: ${dashboardPath}`,
|
|
51885
52035
|
...unknownCollectorDirs > 0 ? [`Skipped ${unknownCollectorDirs} outputs from unknown collectors (see \`gc --stale\`).`] : [],
|
|
51886
|
-
...offMainlineSnapshots > 0 ? [`Skipped ${offMainlineSnapshots} tree snapshots taken off
|
|
52036
|
+
...offMainlineSnapshots > 0 ? [`Skipped ${offMainlineSnapshots} tree snapshots taken off the lineages.`] : []
|
|
51887
52037
|
].join("\n"));
|
|
51888
52038
|
yield* warnAboutIgnoreFiles({
|
|
51889
52039
|
repoRoot,
|
|
@@ -56854,7 +57004,7 @@ var exists = (filePath) => promise(() => access(filePath).then(() => true, () =>
|
|
|
56854
57004
|
var runStatus = ({ repoPath }) => gen(function* () {
|
|
56855
57005
|
const repoRoot = yield* resolveRepoRoot(repoPath);
|
|
56856
57006
|
const commits = yield* listCommits(repoRoot);
|
|
56857
|
-
const
|
|
57007
|
+
const lineages = yield* listLineages(repoRoot);
|
|
56858
57008
|
const config = yield* loadConfig(repoRoot);
|
|
56859
57009
|
const catalogPath = config.catalogPath;
|
|
56860
57010
|
if (!(yield* exists(path.join(catalogPath, "catalog.json")))) {
|
|
@@ -56876,10 +57026,10 @@ var runStatus = ({ repoPath }) => gen(function* () {
|
|
|
56876
57026
|
`Catalog: ${catalogPath}`
|
|
56877
57027
|
];
|
|
56878
57028
|
for (const collector of builtInCollectors) {
|
|
56879
|
-
const
|
|
57029
|
+
const targetShas = describesTreeState(collector) ? sampleTreeCommits(lineages, commits, collector.defaultSampling) : new Set(sampleCommits(commits, collector.defaultSampling).map((commit) => commit.hash));
|
|
56880
57030
|
const cacheKey = collectorCacheKey(collector, config);
|
|
56881
|
-
const collected = (yield* forEach$1(
|
|
56882
|
-
lines.push(collector.defaultSampling === "all" ? ` ${collector.name}: ${collected}/${
|
|
57031
|
+
const collected = (yield* forEach$1([...targetShas], (sha) => isCollected(catalog, sha, collector, cacheKey), { concurrency: 16 })).filter(Boolean).length;
|
|
57032
|
+
lines.push(collector.defaultSampling === "all" ? ` ${collector.name}: ${collected}/${targetShas.size} commits collected` : ` ${collector.name}: ${collected}/${targetShas.size} commits collected (${samplingLabel(collector.defaultSampling)} sample of ${commits.length})`);
|
|
56883
57033
|
}
|
|
56884
57034
|
yield* log(lines.join("\n"));
|
|
56885
57035
|
yield* warnAboutIgnoreFiles({
|
package/dist/config.d.ts
CHANGED
|
@@ -71,12 +71,26 @@ export type ContributorsConfig = {
|
|
|
71
71
|
};
|
|
72
72
|
/** First day of the week in calendar-shaped charts. */
|
|
73
73
|
export type WeekStart = "monday" | "sunday";
|
|
74
|
+
/**
|
|
75
|
+
* Stable identifiers of the dashboard's chart sections, in page order — the
|
|
76
|
+
* keys {@link ChartsConfig.annotations} accepts.
|
|
77
|
+
*/
|
|
78
|
+
export declare const chartIds: readonly ["lines-of-code", "commit-calendar", "commits-per-month", "churn-per-month", "direct-dependencies", "dependencies", "loose-ends", "most-suppressed-eslint-rules", "contributors"];
|
|
79
|
+
export type ChartId = (typeof chartIds)[number];
|
|
74
80
|
export type ChartsConfig = {
|
|
75
81
|
/**
|
|
76
82
|
* Which day calendar-shaped dashboard charts (e.g. the commit calendar)
|
|
77
83
|
* start the week on. Defaults to `"monday"`.
|
|
78
84
|
*/
|
|
79
85
|
readonly weekStartsOn?: WeekStart;
|
|
86
|
+
/**
|
|
87
|
+
* Per-chart notes rendered in a callout between the chart's heading and the
|
|
88
|
+
* chart itself — for explaining oddities the data alone cannot, like a
|
|
89
|
+
* history migration that makes a timeline sparse. Keyed by {@link ChartId};
|
|
90
|
+
* values are Markdown limited to paragraphs, `-` lists, `**bold**`,
|
|
91
|
+
* `_italic_`, `` `code` `` and `[links](https://…)`.
|
|
92
|
+
*/
|
|
93
|
+
readonly annotations?: Partial<Record<ChartId, string>>;
|
|
80
94
|
};
|
|
81
95
|
export type CatalogConfig = {
|
|
82
96
|
/**
|
package/dist/config.js
CHANGED
|
@@ -31,6 +31,21 @@
|
|
|
31
31
|
* });
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
|
+
/**
|
|
35
|
+
* Stable identifiers of the dashboard's chart sections, in page order — the
|
|
36
|
+
* keys {@link ChartsConfig.annotations} accepts.
|
|
37
|
+
*/
|
|
38
|
+
export const chartIds = [
|
|
39
|
+
"lines-of-code",
|
|
40
|
+
"commit-calendar",
|
|
41
|
+
"commits-per-month",
|
|
42
|
+
"churn-per-month",
|
|
43
|
+
"direct-dependencies",
|
|
44
|
+
"dependencies",
|
|
45
|
+
"loose-ends",
|
|
46
|
+
"most-suppressed-eslint-rules",
|
|
47
|
+
"contributors",
|
|
48
|
+
];
|
|
34
49
|
/**
|
|
35
50
|
* Identity helper that gives `repo-dive.config.ts` full type-checking and
|
|
36
51
|
* editor IntelliSense. It returns its argument unchanged.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--container-xs:20rem;--container-xl:36rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-wide:.025em;--radius-xs:.125rem;--radius-sm:.25rem;--radius-md:.375rem;--animate-spin:spin 1s linear infinite;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--animate-late-fade-in:late-fade-in .15s ease-out .5s both}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.collapse{visibility:collapse}.invisible{visibility:hidden}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:0}.inset-y-0{inset-block:0}.top-0{top:0}.top-2{top:calc(var(--spacing) * 2)}.top-7{top:calc(var(--spacing) * 7)}.right-0{right:0}.right-2{right:calc(var(--spacing) * 2)}.bottom-0{bottom:0}.left-0{left:0}.isolate{isolation:isolate}.z-10{z-index:10}.z-50{z-index:50}.col-start-1{grid-column-start:1}.row-start-1{grid-row-start:1}.mx-1\.5{margin-inline:calc(var(--spacing) * 1.5)}.mx-2{margin-inline:calc(var(--spacing) * 2)}.mx-auto{margin-inline:auto}.my-0\.5{margin-block:calc(var(--spacing) * .5)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mr-1{margin-right:var(--spacing)}.mb-1{margin-bottom:var(--spacing)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.mb-10{margin-bottom:calc(var(--spacing) * 10)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.inline-grid{display:inline-grid}.table{display:table}.size-2{width:calc(var(--spacing) * 2);height:calc(var(--spacing) * 2)}.size-2\.5{width:calc(var(--spacing) * 2.5);height:calc(var(--spacing) * 2.5)}.size-3\.5{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-5{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.size-full{width:100%;height:100%}.h-\(--scroll-area-thumb-height\){height:var(--scroll-area-thumb-height)}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-full{height:100%}.max-h-\(--available-height\){max-height:var(--available-height)}.max-h-64{max-height:calc(var(--spacing) * 64)}.w-\(--scroll-area-thumb-width\){width:var(--scroll-area-thumb-width)}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-12{width:calc(var(--spacing) * 12)}.w-20{width:calc(var(--spacing) * 20)}.w-36{width:calc(var(--spacing) * 36)}.w-56{width:calc(var(--spacing) * 56)}.w-80{width:calc(var(--spacing) * 80)}.w-fit{width:fit-content}.w-full{width:100%}.w-max{width:max-content}.max-w-5xl{max-width:var(--container-5xl)}.max-w-full{max-width:100%}.max-w-prose{max-width:65ch}.max-w-xl{max-width:var(--container-xl)}.max-w-xs{max-width:var(--container-xs)}.min-w-\(--anchor-width\){min-width:var(--anchor-width)}.min-w-8{min-width:calc(var(--spacing) * 8)}.min-w-9{min-width:calc(var(--spacing) * 9)}.min-w-10{min-width:calc(var(--spacing) * 10)}.flex-1{flex:1}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.origin-\(--transform-origin\){transform-origin:var(--transform-origin)}.animate-late-fade-in{animation:var(--animate-late-fade-in)}.cursor-default{cursor:default}.cursor-help{cursor:help}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.list-disc{list-style-type:disc}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-center{align-items:center}.items-end{align-items:flex-end}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(var(--spacing) * var(--tw-space-y-reverse));margin-block-end:calc(var(--spacing) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-y-1{row-gap:var(--spacing)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:2147483647px}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xs{border-radius:var(--radius-xs)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-\(--grid-line\),.border-border,.border-input{border-color:var(--grid-line)}.border-t-transparent{border-top-color:#0000}.border-l-transparent{border-left-color:#0000}.bg-\(--surface-1\){background-color:var(--surface-1)}.bg-\(--surface-2\){background-color:var(--surface-2)}.bg-background\/85{background-color:var(--surface-1)}@supports (color:color-mix(in lab, red, red)){.bg-background\/85{background-color:color-mix(in oklab, var(--surface-1) 85%, transparent)}}.bg-border{background-color:var(--grid-line)}.bg-muted{background-color:var(--surface-2)}.bg-popover{background-color:var(--surface-1)}.bg-transparent{background-color:#0000}.fill-muted{fill:var(--surface-2)}.stroke-border{stroke:var(--grid-line)}.p-2{padding:calc(var(--spacing) * 2)}.p-4{padding:calc(var(--spacing) * 4)}.p-px{padding:1px}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-16{padding-block:calc(var(--spacing) * 16)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pb-1{padding-bottom:var(--spacing)}.pb-2\.5{padding-bottom:calc(var(--spacing) * 2.5)}.pl-1{padding-left:var(--spacing)}.pl-2{padding-left:calc(var(--spacing) * 2)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-4{padding-left:calc(var(--spacing) * 4)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.65rem\]{font-size:.65rem}.text-\[0\.85em\]{font-size:.85em}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.text-balance{text-wrap:balance}.whitespace-nowrap{white-space:nowrap}.text-\(--text-muted\){color:var(--text-muted)}.text-\(--text-secondary\){color:var(--text-secondary)}.text-foreground{color:var(--text-primary)}.text-muted-foreground{color:var(--text-muted)}.text-popover-foreground{color:var(--text-primary)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.decoration-\(--text-muted\){-webkit-text-decoration-color:var(--text-muted);-webkit-text-decoration-color:var(--text-muted);-webkit-text-decoration-color:var(--text-muted);text-decoration-color:var(--text-muted)}.decoration-dotted{text-decoration-style:dotted}.underline-offset-2{text-underline-offset:2px}.underline-offset-4{text-underline-offset:4px}.opacity-90{opacity:.9}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline-hidden{--tw-outline-style:none;outline-style:none}@media (forced-colors:active){.outline-hidden{outline-offset:2px;outline:2px solid #0000}}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[color\,box-shadow\]{transition-property:color,box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[opacity\,transform\]{transition-property:opacity,transform;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-100{--tw-duration:.1s;transition-duration:.1s}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.not-data-pressed\:text-\(--text-muted\):not([data-pressed]){color:var(--text-muted)}@media (hover:hover){.group-hover\:text-\(--series-1\):is(:where(.group):hover *){color:var(--series-1)}}.first\:mt-0:first-child{margin-top:0}@media (hover:hover){.hover\:text-\(--series-1\):hover{color:var(--series-1)}.hover\:text-\(--text-primary\):hover{color:var(--text-primary)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:z-10:focus,.focus-visible\:z-10:focus-visible{z-index:10}.focus-visible\:border-ring:focus-visible{border-color:var(--series-1)}.focus-visible\:ring-\[3px\]:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--series-1)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab, var(--series-1) 50%, transparent)}}.focus-visible\:outline-1:focus-visible{outline-style:var(--tw-outline-style);outline-width:1px}@media (hover:hover){.enabled\:hover\:bg-accent:enabled:hover,.enabled\:hover\:bg-muted:enabled:hover{background-color:var(--surface-2)}.enabled\:hover\:text-accent-foreground:enabled:hover{color:var(--text-primary)}.enabled\:hover\:text-muted-foreground:enabled:hover{color:var(--text-muted)}.not-data-pressed\:enabled\:hover\:bg-transparent:not([data-pressed]):enabled:hover{background-color:#0000}.not-data-pressed\:enabled\:hover\:text-\(--text-secondary\):not([data-pressed]):enabled:hover{color:var(--text-secondary)}}.disabled\:pointer-events-auto:disabled{pointer-events:auto}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.data-disabled\:pointer-events-none[data-disabled]{pointer-events:none}.data-disabled\:opacity-50[data-disabled]{opacity:.5}.data-highlighted\:bg-accent[data-highlighted]{background-color:var(--surface-2)}.data-highlighted\:text-accent-foreground[data-highlighted]{color:var(--text-primary)}.data-pressed\:bg-accent[data-pressed]{background-color:var(--surface-2)}.data-pressed\:text-accent-foreground[data-pressed]{color:var(--text-primary)}.data-\[ending-style\]\:scale-95[data-ending-style]{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.data-\[ending-style\]\:opacity-0[data-ending-style]{opacity:0}.data-\[side\=bottom\]\:-top-\[4px\][data-side=bottom]{top:-4px}.data-\[side\=bottom\]\:rotate-180[data-side=bottom]{rotate:180deg}.data-\[side\=left\]\:-right-\[6\.5px\][data-side=left]{right:-6.5px}.data-\[side\=left\]\:-rotate-90[data-side=left]{rotate:-90deg}.data-\[side\=right\]\:-left-\[6\.5px\][data-side=right]{left:-6.5px}.data-\[side\=right\]\:rotate-90[data-side=right]{rotate:90deg}.data-\[side\=top\]\:-bottom-\[4px\][data-side=top]{bottom:-4px}.data-\[size\=default\]\:h-9[data-size=default]{height:calc(var(--spacing) * 9)}.data-\[size\=sm\]\:h-8[data-size=sm]{height:calc(var(--spacing) * 8)}.data-\[size\=xs\]\:h-7[data-size=xs]{height:calc(var(--spacing) * 7)}.data-\[size\=xs\]\:px-2[data-size=xs]{padding-inline:calc(var(--spacing) * 2)}.data-\[size\=xs\]\:text-xs[data-size=xs]{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}:is(.\*\:data-\[slot\=select-value\]\:line-clamp-1>*)[data-slot=select-value]{-webkit-line-clamp:1;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}:is(.\*\:data-\[slot\=select-value\]\:flex>*)[data-slot=select-value]{display:flex}:is(.\*\:data-\[slot\=select-value\]\:items-center>*)[data-slot=select-value]{align-items:center}:is(.\*\:data-\[slot\=select-value\]\:gap-2>*)[data-slot=select-value]{gap:calc(var(--spacing) * 2)}.data-\[starting-style\]\:scale-95[data-starting-style]{--tw-scale-x:95%;--tw-scale-y:95%;--tw-scale-z:95%;scale:var(--tw-scale-x) var(--tw-scale-y)}.data-\[starting-style\]\:opacity-0[data-starting-style]{opacity:0}.data-\[variant\=outline\]\:gap-0[data-variant=outline]{gap:0}.data-\[variant\=outline\]\:rounded-md[data-variant=outline]{border-radius:var(--radius-md)}.data-\[variant\=outline\]\:rounded-none[data-variant=outline]{border-radius:0}.data-\[variant\=outline\]\:border-l-0[data-variant=outline]{border-left-style:var(--tw-border-style);border-left-width:0}.data-\[variant\=outline\]\:first\:rounded-l-md[data-variant=outline]:first-child{border-top-left-radius:var(--radius-md);border-bottom-left-radius:var(--radius-md)}.data-\[variant\=outline\]\:first\:border-l[data-variant=outline]:first-child{border-left-style:var(--tw-border-style);border-left-width:1px}.data-\[variant\=outline\]\:last\:rounded-r-md[data-variant=outline]:last-child{border-top-right-radius:var(--radius-md);border-bottom-right-radius:var(--radius-md)}@media (prefers-reduced-motion:no-preference){.motion-safe\:animate-pulse{animation:var(--animate-pulse)}.motion-safe\:animate-spin{animation:var(--animate-spin)}}@media (width>=40rem){.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (width>=64rem){.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}.\[\&_svg\:not\(\[class\*\=\'size-\'\]\)\]\:size-4 svg:not([class*=size-]){width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.\[\&_svg\:not\(\[class\*\=\'text-\'\]\)\]\:text-muted-foreground svg:not([class*=text-]){color:var(--text-muted)}}:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--surface-1:#fcfcfb;--surface-2:#f4f4f2;--text-primary:#0b0b0b;--text-secondary:#52514e;--text-muted:#86847e;--grid-line:#e7e6e2;--series-1:#2a78d6;--series-2:#1baf7a;--series-3:#eda100;--series-4:#008300;--series-5:#4a3aa7;--series-6:#e34948;--series-7:#e87ba4;--series-8:#eb6834;--series-9:#8569e8;--series-10:#958400;--series-11:#a92172;--series-12:#925001;--series-13:#00b8bf;--series-14:#59bc42;--series-15:#df71d7;--series-16:#8339b0;--series-17:#5b6c00;--series-18:#006a9e;--series-19:#b51b34;--series-20:#3d54cb;--diverge-pos:#2a78d6;--diverge-neg:#e34948;--kind-human:var(--series-1);--kind-bot:var(--series-3);--kind-ai:#93278f;--seq-100:#cde2fb;--seq-200:#9ec5f4;--seq-300:#6da7ec;--seq-400:#3987e5;--seq-500:#256abf;--seq-600:#184f95;--seq-700:#0d366b}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--surface-1:#1a1a19;--surface-2:#242423;--text-primary:#fff;--text-secondary:#c3c2b7;--text-muted:#8b897f;--grid-line:#33332f;--series-1:#3987e5;--series-2:#199e70;--series-3:#c98500;--series-4:#008300;--series-5:#9085e9;--series-6:#e66767;--series-7:#d55181;--series-8:#d95926;--series-9:#8164e3;--series-10:#907f03;--series-11:#b02978;--series-12:#9b5400;--series-13:#03a5ab;--series-14:#47aa2d;--series-15:#cd5fc5;--series-16:#893fb6;--series-17:#607201;--series-18:#0070a6;--series-19:#bc2539;--series-20:#425ad1;--diverge-pos:#3987e5;--diverge-neg:#e66767;--kind-ai:#b02fa8}}html{overflow-y:scroll}body{background:var(--surface-1);color:var(--text-primary)}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{50%{opacity:.5}}@keyframes late-fade-in{0%{opacity:0}to{opacity:1}}
|