repo-dive 0.13.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 +268 -31
- 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-C5cY7tuF.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",
|
|
@@ -42956,10 +42956,11 @@ var languageOfExtension = (extension) => languageByExtension[extension] ?? exten
|
|
|
42956
42956
|
/**
|
|
42957
42957
|
* Whether a collector describes the *state of the tree* at a commit rather than
|
|
42958
42958
|
* facts about the commit itself. Such snapshots are only meaningful on the
|
|
42959
|
-
* first-parent chain
|
|
42960
|
-
*
|
|
42961
|
-
*
|
|
42962
|
-
*
|
|
42959
|
+
* mainline (the first-parent chain, extended backwards across founding
|
|
42960
|
+
* grafts — see `listMainlineShas`): a commit that lives on a side branch — or
|
|
42961
|
+
* that arrived with a foreign history absorbed mid-life by an
|
|
42962
|
+
* unrelated-histories merge — carries a tree that was never the repository's
|
|
42963
|
+
* state, so charting it puts a cliff into every timeline.
|
|
42963
42964
|
*/
|
|
42964
42965
|
var describesTreeState = (collector) => collector.strategy !== "log";
|
|
42965
42966
|
/** File extension used as a category key, e.g. ".ts"; files without one map to "(none)". */
|
|
@@ -50059,6 +50060,23 @@ var collectorsCommand = make$5("collectors").pipe(withDescription("List availabl
|
|
|
50059
50060
|
].join("\n"));
|
|
50060
50061
|
})));
|
|
50061
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
|
|
50062
50080
|
//#region src/cli/shared/config.ts
|
|
50063
50081
|
/** "12345+alice@users.noreply.github.com" → "alice"; other emails unchanged. */
|
|
50064
50082
|
var prettifyAuthorEmail = (email) => {
|
|
@@ -50210,6 +50228,18 @@ var parseWeekStartsOn = (value) => {
|
|
|
50210
50228
|
for (const weekStart of weekStarts) if (value === weekStart) return weekStart;
|
|
50211
50229
|
throw configError("`charts.weekStartsOn` must be \"monday\" or \"sunday\".");
|
|
50212
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
|
+
};
|
|
50213
50243
|
/**
|
|
50214
50244
|
* Resolves `catalog.dir` against the repository root. `gc` deletes whole
|
|
50215
50245
|
* subtrees under the result, so the two placements that would take the
|
|
@@ -50251,6 +50281,7 @@ var resolveConfig = (raw, repoRoot) => {
|
|
|
50251
50281
|
const charts = prop(raw, "charts");
|
|
50252
50282
|
if (charts !== void 0 && !isPlainObject(charts)) throw configError("`charts` must be an object.");
|
|
50253
50283
|
const weekStartsOn = parseWeekStartsOn(charts === void 0 ? void 0 : prop(charts, "weekStartsOn"));
|
|
50284
|
+
const chartAnnotations = parseChartAnnotations(charts === void 0 ? void 0 : prop(charts, "annotations"));
|
|
50254
50285
|
const catalog = prop(raw, "catalog");
|
|
50255
50286
|
if (catalog !== void 0 && !isPlainObject(catalog)) throw configError("`catalog` must be an object.");
|
|
50256
50287
|
const catalogPath = parseCatalogPath(catalog === void 0 ? void 0 : prop(catalog, "dir"), repoRoot);
|
|
@@ -50258,6 +50289,7 @@ var resolveConfig = (raw, repoRoot) => {
|
|
|
50258
50289
|
resolveContributor: (email, name) => bareResolveContributor(aliasMap, email, name),
|
|
50259
50290
|
maxInCharts,
|
|
50260
50291
|
weekStartsOn,
|
|
50292
|
+
chartAnnotations,
|
|
50261
50293
|
catalogPath,
|
|
50262
50294
|
catalogRelativePath: relativeCatalogPath(repoRoot, catalogPath),
|
|
50263
50295
|
checkIgnoreFiles: parseCheckIgnoreFiles(catalog === void 0 ? void 0 : prop(catalog, "checkIgnoreFiles"))
|
|
@@ -50809,18 +50841,158 @@ var listCommits = (repoRoot) => runGit([
|
|
|
50809
50841
|
"log",
|
|
50810
50842
|
`--format=${gitLogFormat}`
|
|
50811
50843
|
]).pipe(succeedIfNoCommitsYet, map$7(parseGitLog));
|
|
50844
|
+
var chainLogFormat = [
|
|
50845
|
+
"%H",
|
|
50846
|
+
"%P",
|
|
50847
|
+
"%cI"
|
|
50848
|
+
].join("%x1f");
|
|
50812
50849
|
/**
|
|
50813
|
-
*
|
|
50814
|
-
* passed
|
|
50815
|
-
*
|
|
50850
|
+
* The first-parent chain of `tip` (HEAD when omitted), newest first. HEAD is
|
|
50851
|
+
* left implicit rather than passed: an explicit rev makes git fail an empty
|
|
50852
|
+
* repository with "unknown revision" instead of the "does not have any commits
|
|
50853
|
+
* yet" that {@link succeedIfNoCommitsYet} recovers from.
|
|
50816
50854
|
*/
|
|
50817
|
-
var
|
|
50855
|
+
var listFirstParentChain = (repoRoot, tip) => runGit([
|
|
50818
50856
|
"-C",
|
|
50819
50857
|
repoRoot,
|
|
50820
50858
|
"log",
|
|
50821
50859
|
"--first-parent",
|
|
50822
|
-
|
|
50823
|
-
|
|
50860
|
+
`--format=${chainLogFormat}`,
|
|
50861
|
+
...tip === void 0 ? [] : [tip]
|
|
50862
|
+
]).pipe(succeedIfNoCommitsYet, map$7((stdout) => stdout.split("\n").filter(Boolean).map((line) => {
|
|
50863
|
+
const [hash = "", parents = "", committerDate = ""] = line.split(fieldSeparator);
|
|
50864
|
+
return {
|
|
50865
|
+
hash,
|
|
50866
|
+
parentHashes: parents.split(" ").filter(Boolean),
|
|
50867
|
+
committerDate
|
|
50868
|
+
};
|
|
50869
|
+
})));
|
|
50870
|
+
/** No common ancestor means the two commits come from unrelated histories. */
|
|
50871
|
+
var haveUnrelatedHistories = (repoRoot, left, right) => runGit([
|
|
50872
|
+
"-C",
|
|
50873
|
+
repoRoot,
|
|
50874
|
+
"merge-base",
|
|
50875
|
+
left,
|
|
50876
|
+
right
|
|
50877
|
+
], { okExitCodes: [1] }).pipe(map$7((stdout) => stdout.trim() === ""));
|
|
50878
|
+
/**
|
|
50879
|
+
* The first-parent chains that continue `chain` backwards in time across a
|
|
50880
|
+
* founding graft, or no chains when there is none.
|
|
50881
|
+
*
|
|
50882
|
+
* A repository migration (monorepo assembly, host move, history rewrite)
|
|
50883
|
+
* leaves a recognizable signature: a fresh root commit followed immediately by
|
|
50884
|
+
* merges that absorb the project's previous histories. Both conditions below
|
|
50885
|
+
* are required, so ordinary absorptions stay excluded:
|
|
50886
|
+
*
|
|
50887
|
+
* - the merge sits in the founding window — the unbroken run of merges
|
|
50888
|
+
* directly above the root, before the first ordinary commit. A foreign
|
|
50889
|
+
* history vendored later in the repository's life does not qualify.
|
|
50890
|
+
* - the absorbed history ends before the root begins, so it occupies the
|
|
50891
|
+
* stretch of timeline where the current chain has nothing to say. A side
|
|
50892
|
+
* history that overlaps the chain (e.g. a plugin repository absorbed while
|
|
50893
|
+
* mainline development continued) does not qualify.
|
|
50894
|
+
*
|
|
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.
|
|
50898
|
+
*/
|
|
50899
|
+
var findFoundingGraft = (repoRoot, chain, alreadyClaimed) => gen(function* () {
|
|
50900
|
+
const root = chain.at(-1);
|
|
50901
|
+
if (root === void 0) return {
|
|
50902
|
+
absorbed: [],
|
|
50903
|
+
assemblyShas: []
|
|
50904
|
+
};
|
|
50905
|
+
const rootDate = Date.parse(root.committerDate);
|
|
50906
|
+
const candidateTips = [];
|
|
50907
|
+
const assemblyShas = [root.hash];
|
|
50908
|
+
for (let index = chain.length - 2; index >= 0; index -= 1) {
|
|
50909
|
+
const entry = chain[index];
|
|
50910
|
+
if (entry === void 0 || entry.parentHashes.length < 2) break;
|
|
50911
|
+
candidateTips.push(...entry.parentHashes.slice(1));
|
|
50912
|
+
assemblyShas.push(entry.hash);
|
|
50913
|
+
}
|
|
50914
|
+
const absorbed = [];
|
|
50915
|
+
for (const tip of candidateTips) {
|
|
50916
|
+
if (alreadyClaimed.has(tip)) continue;
|
|
50917
|
+
if (!(yield* haveUnrelatedHistories(repoRoot, root.hash, tip))) continue;
|
|
50918
|
+
const candidate = yield* listFirstParentChain(repoRoot, tip);
|
|
50919
|
+
const tipDate = Date.parse(candidate.at(0)?.committerDate ?? "");
|
|
50920
|
+
const candidateRootDate = Date.parse(candidate.at(-1)?.committerDate ?? "");
|
|
50921
|
+
if (Number.isNaN(tipDate) || Number.isNaN(candidateRootDate) || tipDate >= rootDate) continue;
|
|
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
|
+
});
|
|
50969
|
+
}
|
|
50970
|
+
return lineages;
|
|
50971
|
+
});
|
|
50972
|
+
/**
|
|
50973
|
+
* Every sha on any lineage — what `scan` samples tree snapshots from, `gc`
|
|
50974
|
+
* refuses to reclaim and `index` admits into the cube.
|
|
50975
|
+
*/
|
|
50976
|
+
var listMainlineShas = (repoRoot) => listLineages(repoRoot).pipe(map$7((lineages) => {
|
|
50977
|
+
const shas = /* @__PURE__ */ new Set();
|
|
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);
|
|
50993
|
+
}
|
|
50994
|
+
return shas;
|
|
50995
|
+
};
|
|
50824
50996
|
var summarizeCommits = (commits) => {
|
|
50825
50997
|
const authorEmails = new Set(commits.map((commit) => commit.authorEmail));
|
|
50826
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));
|
|
@@ -50900,14 +51072,13 @@ var runScan = ({ repoPath, collectorNames, maxCommits, sample, force = false })
|
|
|
50900
51072
|
const summary = summarizeCommits(commits);
|
|
50901
51073
|
const cacheKeys = new Map(collectors.map((collector) => [collector.name, collectorCacheKey(collector, config)]));
|
|
50902
51074
|
const cacheKeyOf = (collector) => cacheKeys.get(collector.name) ?? collectorCacheKey(collector, config);
|
|
50903
|
-
const
|
|
51075
|
+
const lineages = yield* listLineages(repoRoot);
|
|
50904
51076
|
const plans = collectors.map((collector) => {
|
|
50905
51077
|
const policy = sampleOverride ?? collector.defaultSampling;
|
|
50906
|
-
const candidates = describesTreeState(collector) ? selected.filter((commit) => firstParentShas.has(commit.hash)) : selected;
|
|
50907
51078
|
return {
|
|
50908
51079
|
collector,
|
|
50909
51080
|
policy,
|
|
50910
|
-
shas: new Set(sampleCommits(
|
|
51081
|
+
shas: describesTreeState(collector) ? sampleTreeCommits(lineages, selected, policy) : new Set(sampleCommits(selected, policy).map((commit) => commit.hash))
|
|
50911
51082
|
};
|
|
50912
51083
|
});
|
|
50913
51084
|
yield* log(`Plan: ${plans.map((plan) => `${plan.collector.name} → ${plan.shas.size} commits (${samplingLabel(plan.policy)})`).join(", ")}`);
|
|
@@ -51101,7 +51272,7 @@ var buildPlan = (repoRoot) => gen(function* () {
|
|
|
51101
51272
|
"rev-list",
|
|
51102
51273
|
"HEAD"
|
|
51103
51274
|
])).split("\n").filter(Boolean));
|
|
51104
|
-
const
|
|
51275
|
+
const mainlineShas = yield* listMainlineShas(repoRoot);
|
|
51105
51276
|
const currentCacheKeys = new Map(builtInCollectors.map((collector) => [collector.name, collectorCacheKey(collector, config)]));
|
|
51106
51277
|
const snapshotCollectorNames = new Set(builtInCollectors.filter((collector) => describesTreeState(collector)).map((collector) => collector.name));
|
|
51107
51278
|
const unreachableShas = [];
|
|
@@ -51116,7 +51287,7 @@ var buildPlan = (repoRoot) => gen(function* () {
|
|
|
51116
51287
|
const collectorNames = yield* readdirIfExists(path.join(commitsPath, sha));
|
|
51117
51288
|
for (const collectorName of collectorNames) {
|
|
51118
51289
|
countsByCollector.set(collectorName, (countsByCollector.get(collectorName) ?? 0) + 1);
|
|
51119
|
-
if (!
|
|
51290
|
+
if (!mainlineShas.has(sha) && snapshotCollectorNames.has(collectorName)) offMainlineOutputs.push({
|
|
51120
51291
|
sha,
|
|
51121
51292
|
collectorName
|
|
51122
51293
|
});
|
|
@@ -51349,6 +51520,48 @@ var ignoreCommand = make$5("ignore", {
|
|
|
51349
51520
|
dryRun: config.dryRun
|
|
51350
51521
|
})));
|
|
51351
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
|
|
51352
51565
|
//#region src/cli/shared/remote.ts
|
|
51353
51566
|
/**
|
|
51354
51567
|
* Turns whatever `remote.origin.url` holds into a URL a browser can open, or
|
|
@@ -51473,7 +51686,22 @@ var spanOf = (commits) => {
|
|
|
51473
51686
|
last
|
|
51474
51687
|
};
|
|
51475
51688
|
};
|
|
51476
|
-
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
|
+
}));
|
|
51477
51705
|
/** The raw `"Name <email>"` trailers on a commit, in the order git listed them. */
|
|
51478
51706
|
const coAuthorsOf = (commit) => [...commit.factsByCollector.values()].flat().filter((fact) => fact.metric === "commits.coAuthor").map((fact) => fact.categories?.["coAuthor"] ?? "");
|
|
51479
51707
|
/**
|
|
@@ -51498,18 +51726,18 @@ var buildDashboardData = (repoRoot, commits, config, remoteUrl) => {
|
|
|
51498
51726
|
added: sumMetric(commit, "churn.added"),
|
|
51499
51727
|
deleted: sumMetric(commit, "churn.deleted")
|
|
51500
51728
|
}));
|
|
51501
|
-
const languages =
|
|
51729
|
+
const languages = composeSeries((commit) => hasMetric(commit, "languages.lines"), (commit) => ({
|
|
51502
51730
|
sha: commit.sha.slice(0, 10),
|
|
51503
51731
|
date: commit.committedAt,
|
|
51504
51732
|
byLanguage: groupMetric(commit, "languages.lines", "language")
|
|
51505
51733
|
}));
|
|
51506
|
-
const fileTypes =
|
|
51734
|
+
const fileTypes = composeSeries((commit) => hasMetric(commit, "files.count"), (commit) => ({
|
|
51507
51735
|
sha: commit.sha.slice(0, 10),
|
|
51508
51736
|
date: commit.committedAt,
|
|
51509
51737
|
totalFiles: sumMetric(commit, "files.count"),
|
|
51510
51738
|
totalBytes: sumMetric(commit, "files.bytes")
|
|
51511
51739
|
}));
|
|
51512
|
-
const directives =
|
|
51740
|
+
const directives = composeSeries((commit) => hasMetric(commit, "directives.ts"), (commit) => {
|
|
51513
51741
|
const byType = groupMetric(commit, "directives.eslint", "type");
|
|
51514
51742
|
const ts = groupMetric(commit, "directives.ts", "type");
|
|
51515
51743
|
return {
|
|
@@ -51525,7 +51753,7 @@ var buildDashboardData = (repoRoot, commits, config, remoteUrl) => {
|
|
|
51525
51753
|
todos: sumMetric(commit, "todos.count")
|
|
51526
51754
|
};
|
|
51527
51755
|
});
|
|
51528
|
-
const dependencies =
|
|
51756
|
+
const dependencies = composeSeries((commit) => hasMetric(commit, "dependencies.resolved") || hasMetric(commit, "dependencies.manifest") || hasMetric(commit, "dependencies.scanned"), (commit) => {
|
|
51529
51757
|
const byKind = groupMetric(commit, "dependencies.direct", "kind");
|
|
51530
51758
|
return {
|
|
51531
51759
|
sha: commit.sha.slice(0, 10),
|
|
@@ -51571,7 +51799,7 @@ var buildDashboardData = (repoRoot, commits, config, remoteUrl) => {
|
|
|
51571
51799
|
if (resolved.kind !== "human") return kindGroupLabels[resolved.kind];
|
|
51572
51800
|
return nameByCanonicalEmail.get(resolved.canonicalEmail.toLowerCase()) ?? resolved.label;
|
|
51573
51801
|
};
|
|
51574
|
-
const survival =
|
|
51802
|
+
const survival = composeSeries((commit) => hasMetric(commit, "survival.lines"), (commit) => {
|
|
51575
51803
|
const byContributorYear = {};
|
|
51576
51804
|
const byLanguageYear = {};
|
|
51577
51805
|
for (const facts of commit.factsByCollector.values()) for (const fact of facts) {
|
|
@@ -51668,7 +51896,10 @@ var buildDashboardData = (repoRoot, commits, config, remoteUrl) => {
|
|
|
51668
51896
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
51669
51897
|
config: {
|
|
51670
51898
|
contributors: { maxInCharts: config.maxInCharts },
|
|
51671
|
-
charts: {
|
|
51899
|
+
charts: {
|
|
51900
|
+
weekStartsOn: config.weekStartsOn,
|
|
51901
|
+
...Object.keys(config.chartAnnotations).length > 0 ? { annotations: config.chartAnnotations } : {}
|
|
51902
|
+
}
|
|
51672
51903
|
},
|
|
51673
51904
|
repo: {
|
|
51674
51905
|
name: remoteUrl?.split("/").at(-1) ?? path.basename(repoRoot),
|
|
@@ -51737,7 +51968,13 @@ var runIndex = ({ repoPath }) => gen(function* () {
|
|
|
51737
51968
|
const commitsPath = path.join(catalogPath, "commits");
|
|
51738
51969
|
const registry = new Map(builtInCollectors.map((collector) => [collector.name, collector]));
|
|
51739
51970
|
const gitCommits = yield* listCommits(repoRoot);
|
|
51740
|
-
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
|
+
});
|
|
51741
51978
|
const remoteUrl = yield* readRemoteUrl(repoRoot);
|
|
51742
51979
|
const catalogShas = new Set(yield* tryPromise(async () => {
|
|
51743
51980
|
try {
|
|
@@ -51750,7 +51987,7 @@ var runIndex = ({ repoPath }) => gen(function* () {
|
|
|
51750
51987
|
if (orderedCommits.length === 0) return yield* new NoCollectedCommitsError({ commitsPath });
|
|
51751
51988
|
const readOutcomes = yield* forEach$1(orderedCommits, (commit) => tryPromise(async () => {
|
|
51752
51989
|
const commitDir = path.join(commitsPath, commit.hash);
|
|
51753
|
-
const onMainline =
|
|
51990
|
+
const onMainline = lineageBySha.has(commit.hash);
|
|
51754
51991
|
const factsByCollector = /* @__PURE__ */ new Map();
|
|
51755
51992
|
let unknownCollectorDirs = 0;
|
|
51756
51993
|
let offMainlineSnapshots = 0;
|
|
@@ -51788,7 +52025,7 @@ var runIndex = ({ repoPath }) => gen(function* () {
|
|
|
51788
52025
|
const dbPath = path.join(indexDir, "metrics.sqlite");
|
|
51789
52026
|
yield* tryPromise(() => rm(dbPath, { force: true }));
|
|
51790
52027
|
const factCount = yield* try_(() => writeSqlite(dbPath, commitFacts));
|
|
51791
|
-
const dashboardData = buildDashboardData(repoRoot, commitFacts, config, remoteUrl);
|
|
52028
|
+
const dashboardData = buildDashboardData(repoRoot, commitFacts, config, remoteUrl, (sha) => lineageBySha.get(sha));
|
|
51792
52029
|
const dashboardPath = path.join(indexDir, "dashboard.json");
|
|
51793
52030
|
yield* tryPromise(() => writeFile(dashboardPath, JSON.stringify(dashboardData), "utf8"));
|
|
51794
52031
|
yield* log([
|
|
@@ -51796,7 +52033,7 @@ var runIndex = ({ repoPath }) => gen(function* () {
|
|
|
51796
52033
|
`Cube: ${dbPath}`,
|
|
51797
52034
|
`Dashboard data: ${dashboardPath}`,
|
|
51798
52035
|
...unknownCollectorDirs > 0 ? [`Skipped ${unknownCollectorDirs} outputs from unknown collectors (see \`gc --stale\`).`] : [],
|
|
51799
|
-
...offMainlineSnapshots > 0 ? [`Skipped ${offMainlineSnapshots} tree snapshots taken off
|
|
52036
|
+
...offMainlineSnapshots > 0 ? [`Skipped ${offMainlineSnapshots} tree snapshots taken off the lineages.`] : []
|
|
51800
52037
|
].join("\n"));
|
|
51801
52038
|
yield* warnAboutIgnoreFiles({
|
|
51802
52039
|
repoRoot,
|
|
@@ -56767,7 +57004,7 @@ var exists = (filePath) => promise(() => access(filePath).then(() => true, () =>
|
|
|
56767
57004
|
var runStatus = ({ repoPath }) => gen(function* () {
|
|
56768
57005
|
const repoRoot = yield* resolveRepoRoot(repoPath);
|
|
56769
57006
|
const commits = yield* listCommits(repoRoot);
|
|
56770
|
-
const
|
|
57007
|
+
const lineages = yield* listLineages(repoRoot);
|
|
56771
57008
|
const config = yield* loadConfig(repoRoot);
|
|
56772
57009
|
const catalogPath = config.catalogPath;
|
|
56773
57010
|
if (!(yield* exists(path.join(catalogPath, "catalog.json")))) {
|
|
@@ -56789,10 +57026,10 @@ var runStatus = ({ repoPath }) => gen(function* () {
|
|
|
56789
57026
|
`Catalog: ${catalogPath}`
|
|
56790
57027
|
];
|
|
56791
57028
|
for (const collector of builtInCollectors) {
|
|
56792
|
-
const
|
|
57029
|
+
const targetShas = describesTreeState(collector) ? sampleTreeCommits(lineages, commits, collector.defaultSampling) : new Set(sampleCommits(commits, collector.defaultSampling).map((commit) => commit.hash));
|
|
56793
57030
|
const cacheKey = collectorCacheKey(collector, config);
|
|
56794
|
-
const collected = (yield* forEach$1(
|
|
56795
|
-
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})`);
|
|
56796
57033
|
}
|
|
56797
57034
|
yield* log(lines.join("\n"));
|
|
56798
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}}
|