repo-dive 0.16.0 → 0.16.1

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 CHANGED
@@ -43882,7 +43882,7 @@ var runWith = (command, config) => {
43882
43882
  };
43883
43883
  var package_default = {
43884
43884
  name: "repo-dive",
43885
- version: "0.16.0",
43885
+ version: "0.16.1",
43886
43886
  description: "Dive into a git repo's history: per-commit snapshots, an indexed metrics catalog and an interactive dashboard",
43887
43887
  keywords: [
43888
43888
  "git",
@@ -43954,13 +43954,13 @@ var package_default = {
43954
43954
  prettier: "@kachkaev/prettier-config",
43955
43955
  devDependencies: {
43956
43956
  "@babel/core": "8.0.1",
43957
- "@base-ui/react": "1.7.0",
43958
- "@changesets/changelog-github": "1.0.0",
43959
- "@changesets/cli": "3.0.1",
43957
+ "@base-ui/react": "1.8.0",
43958
+ "@changesets/changelog-github": "1.0.1",
43959
+ "@changesets/cli": "3.0.2",
43960
43960
  "@changesets/config": "4.0.0",
43961
43961
  "@effect/platform-node": "4.0.0-rc.112",
43962
43962
  "@effect/vitest": "4.0.0-rc.112",
43963
- "@kachkaev/eslint-config-base": "1.2.0",
43963
+ "@kachkaev/eslint-config-base": "2.0.0",
43964
43964
  "@kachkaev/markdownlint-config": "1.0.1",
43965
43965
  "@kachkaev/prettier-config": "2.2.1",
43966
43966
  "@rolldown/plugin-babel": "0.2.3",
@@ -43981,14 +43981,14 @@ var package_default = {
43981
43981
  "@vitejs/plugin-react": "6.1.1",
43982
43982
  "babel-plugin-react-compiler": "1.0.0",
43983
43983
  "class-variance-authority": "0.7.1",
43984
- "cspell": "10.2.1",
43984
+ "cspell": "10.2.2",
43985
43985
  "effect": "4.0.0-rc.112",
43986
- "eslint": "9.39.5",
43986
+ "eslint": "10.10.0",
43987
43987
  "eslint-plugin-react-hooks": "7.1.1",
43988
43988
  "husky": "9.1.7",
43989
43989
  "knip": "6.34.0",
43990
- "lint-staged": "17.4.1",
43991
- "lucide-react": "1.40.0",
43990
+ "lint-staged": "17.5.0",
43991
+ "lucide-react": "1.41.0",
43992
43992
  "markdownlint-cli": "0.49.1",
43993
43993
  "npm-run-all2": "9.0.3",
43994
43994
  "prettier": "3.9.6",
@@ -44001,7 +44001,7 @@ var package_default = {
44001
44001
  "vitest": "5.0.0",
44002
44002
  "yaml": "2.9.0"
44003
44003
  },
44004
- packageManager: "pnpm@12.3.3",
44004
+ packageManager: "pnpm@12.3.4",
44005
44005
  engines: { "node": ">=22.15.0" },
44006
44006
  devEngines: { "runtime": {
44007
44007
  "name": "node",
@@ -44206,7 +44206,7 @@ var parseNumstat = (stdout) => {
44206
44206
  let binaryFiles = 0;
44207
44207
  const byExtension = {};
44208
44208
  for (const line of stdout.split("\n")) {
44209
- const [addedRaw, deletedRaw, filePath] = line.split(" ");
44209
+ const [addedRaw, deletedRaw, filePath] = line.split(" ", 3);
44210
44210
  if (!filePath || addedRaw === void 0 || deletedRaw === void 0) continue;
44211
44211
  filesChanged += 1;
44212
44212
  const extension = extensionOf(filePath);
@@ -50214,7 +50214,7 @@ var parsePnpmLockfile = (content) => {
50214
50214
  }
50215
50215
  if (!isRecord$3(root)) continue;
50216
50216
  const importerEntries = isRecord$3(root["importers"]) ? Object.values(root["importers"]).filter(isRecord$3) : [];
50217
- const projectImporters = importerEntries.filter((importer) => standardGroups.some((group) => group in importer));
50217
+ const projectImporters = importerEntries.filter((importer) => standardGroups.some((group) => Object.hasOwn(importer, group)));
50218
50218
  if (importerEntries.length > 0 && projectImporters.length === 0 && importerEntries.some((importer) => "packageManagerDependencies" in importer || "configDependencies" in importer)) continue;
50219
50219
  sawLockfile = true;
50220
50220
  const version = root["lockfileVersion"];
@@ -50434,10 +50434,10 @@ var pruneBlobCacheNamespaces = (catalogPath, namespaces) => {
50434
50434
  const db = openForMaintenance(catalogPath);
50435
50435
  if (!db) return 0;
50436
50436
  try {
50437
- const deleteNamespace = db.prepare("DELETE FROM blob_results WHERE collector = ? AND cache_key = ?");
50437
+ const namespaceDeletion = db.prepare("DELETE FROM blob_results WHERE collector = ? AND cache_key = ?");
50438
50438
  db.exec("BEGIN");
50439
50439
  try {
50440
- for (const { collector, cacheKey } of namespaces) deleteNamespace.run(collector, cacheKey);
50440
+ for (const { collector, cacheKey } of namespaces) namespaceDeletion.run(collector, cacheKey);
50441
50441
  db.exec("COMMIT");
50442
50442
  } catch (error) {
50443
50443
  db.exec("ROLLBACK");
@@ -50472,7 +50472,7 @@ var listBlobs = (repoRoot, sha, include) => runGit([
50472
50472
  const tabIndex = line.indexOf(" ");
50473
50473
  if (tabIndex === -1) continue;
50474
50474
  const filePath = line.slice(tabIndex + 1);
50475
- const [, type = "", blobSha = ""] = line.slice(0, tabIndex).split(" ");
50475
+ const [, type = "", blobSha = ""] = line.slice(0, tabIndex).split(" ", 3);
50476
50476
  if (type === "blob" && blobSha && include(filePath)) blobs.push({
50477
50477
  blobSha,
50478
50478
  filePath
@@ -50498,10 +50498,10 @@ var fetchBlobContents$1 = (repoRoot, blobShas) => blobShas.length === 0 ? succee
50498
50498
  while (headerEnd < bytes.length && bytes[headerEnd] !== newline$1) headerEnd += 1;
50499
50499
  const header = decoder$1.decode(bytes.subarray(offset, headerEnd));
50500
50500
  offset = headerEnd + 1;
50501
- const [sha = "", type = "", sizeRaw = ""] = header.split(" ");
50501
+ const [sha = "", type = "", sizeRaw = ""] = header.split(" ", 3);
50502
50502
  if (!sha || type === "missing") continue;
50503
50503
  const size = Number(sizeRaw);
50504
- if (!Number.isInteger(size)) continue;
50504
+ if (!Number.isSafeInteger(size)) continue;
50505
50505
  if (type === "blob" && size <= maxBlobBytes$1) contents.set(sha, decoder$1.decode(bytes.subarray(offset, offset + size)));
50506
50506
  offset += size + 1;
50507
50507
  }
@@ -50684,7 +50684,7 @@ var listSourceBlobs = (repoRoot, sha) => runGit([
50684
50684
  const tabIndex = line.indexOf(" ");
50685
50685
  if (tabIndex === -1) continue;
50686
50686
  const filePath = line.slice(tabIndex + 1);
50687
- const [, type = "", blobSha = ""] = line.slice(0, tabIndex).split(" ");
50687
+ const [, type = "", blobSha = ""] = line.slice(0, tabIndex).split(" ", 3);
50688
50688
  if (type === "blob" && blobSha && isScannableSourceFile(filePath)) blobs.push({
50689
50689
  blobSha,
50690
50690
  filePath
@@ -50708,10 +50708,10 @@ var fetchBlobContents = (repoRoot, blobShas) => blobShas.length === 0 ? succeed$
50708
50708
  while (headerEnd < bytes.length && bytes[headerEnd] !== newline) headerEnd += 1;
50709
50709
  const header = decoder.decode(bytes.subarray(offset, headerEnd));
50710
50710
  offset = headerEnd + 1;
50711
- const [sha = "", type = "", sizeRaw = ""] = header.split(" ");
50711
+ const [sha = "", type = "", sizeRaw = ""] = header.split(" ", 3);
50712
50712
  if (!sha || type === "missing") continue;
50713
50713
  const size = Number(sizeRaw);
50714
- if (!Number.isInteger(size)) continue;
50714
+ if (!Number.isSafeInteger(size)) continue;
50715
50715
  if (type === "blob" && size <= maxBlobBytes) contents.set(sha, decoder.decode(bytes.subarray(offset, offset + size)));
50716
50716
  offset += size + 1;
50717
50717
  }
@@ -50820,7 +50820,7 @@ var scanFileForDirectives = (content) => {
50820
50820
  output.eslintBlocks.count += 1;
50821
50821
  addRules(output.eslintBlocks.byRule, parseRules(line, "eslint-disable"));
50822
50822
  openBlock = { line: index };
50823
- } else if (line.includes("eslint-enable") && openBlock) {
50823
+ } else if (openBlock && line.includes("eslint-enable")) {
50824
50824
  output.eslintBlocks.closedCount += 1;
50825
50825
  output.eslintBlocks.coveredLines += Math.max(0, index - openBlock.line - 1);
50826
50826
  openBlock = void 0;
@@ -50997,7 +50997,7 @@ var fileSurvivalCollector = {
50997
50997
  for (const line of fileList.split("\n")) {
50998
50998
  const tabIndex = line.indexOf(" ");
50999
50999
  if (tabIndex === -1) continue;
51000
- const [, type] = line.slice(0, tabIndex).split(/\s+/);
51000
+ const [, type] = line.slice(0, tabIndex).split(/\s+/, 2);
51001
51001
  const filePath = line.slice(tabIndex + 1);
51002
51002
  if (type === "blob" && isScannableSourceFile(filePath)) files.push(filePath);
51003
51003
  }
@@ -51022,7 +51022,7 @@ var fileSurvivalCollector = {
51022
51022
  counts.set(key, (counts.get(key) ?? 0) + 1);
51023
51023
  }
51024
51024
  return {
51025
- rows: [...counts.entries()].map(([key, fileCount]) => {
51025
+ rows: [...counts].map(([key, fileCount]) => {
51026
51026
  const [extension = "", authorEmail = "", cohortMonth = ""] = key.split(fieldSeparator$1);
51027
51027
  return {
51028
51028
  extension,
@@ -51035,8 +51035,7 @@ var fileSurvivalCollector = {
51035
51035
  };
51036
51036
  }),
51037
51037
  normalize: (raw) => {
51038
- const facts = [];
51039
- for (const row of arrayAt(raw, "rows")) facts.push({
51038
+ return Array.from(arrayAt(raw, "rows"), (row) => ({
51040
51039
  metric: "survival.files",
51041
51040
  value: numberAt(row, "files"),
51042
51041
  categories: {
@@ -51044,8 +51043,7 @@ var fileSurvivalCollector = {
51044
51043
  author: stringAt(row, "authorEmail"),
51045
51044
  cohort: stringAt(row, "cohortMonth")
51046
51045
  }
51047
- });
51048
- return facts;
51046
+ }));
51049
51047
  }
51050
51048
  };
51051
51049
  //#endregion
@@ -51059,7 +51057,7 @@ var parseLsTree = (stdout) => {
51059
51057
  const tabIndex = line.indexOf(" ");
51060
51058
  if (tabIndex === -1) continue;
51061
51059
  const filePath = line.slice(tabIndex + 1);
51062
- const [, type, , sizeRaw] = line.slice(0, tabIndex).split(/\s+/);
51060
+ const [, type, , sizeRaw] = line.slice(0, tabIndex).split(/\s+/, 4);
51063
51061
  if (type !== "blob") continue;
51064
51062
  const bytes = sizeRaw === "-" ? 0 : Number(sizeRaw);
51065
51063
  totalFiles += 1;
@@ -51232,13 +51230,13 @@ var survivalCollector = {
51232
51230
  const counts = /* @__PURE__ */ new Map();
51233
51231
  let totalLines = 0;
51234
51232
  for (const { extension, attributions } of attributionsPerFile) for (const attribution of attributions) {
51235
- const key = `${extension}\u001F${attribution.authorEmail}\u001F${attribution.cohortMonth}`;
51233
+ const key = `${extension}\u{1F}${attribution.authorEmail}\u{1F}${attribution.cohortMonth}`;
51236
51234
  counts.set(key, (counts.get(key) ?? 0) + 1);
51237
51235
  totalLines += 1;
51238
51236
  }
51239
51237
  return {
51240
- rows: [...counts.entries()].map(([key, lines]) => {
51241
- const [extension = "", authorEmail = "", cohortMonth = ""] = key.split("");
51238
+ rows: [...counts].map(([key, lines]) => {
51239
+ const [extension = "", authorEmail = "", cohortMonth = ""] = key.split("", 3);
51242
51240
  return {
51243
51241
  extension,
51244
51242
  authorEmail,
@@ -51251,8 +51249,7 @@ var survivalCollector = {
51251
51249
  };
51252
51250
  }),
51253
51251
  normalize: (raw) => {
51254
- const facts = [];
51255
- for (const row of arrayAt(raw, "rows")) facts.push({
51252
+ return Array.from(arrayAt(raw, "rows"), (row) => ({
51256
51253
  metric: "survival.lines",
51257
51254
  value: numberAt(row, "lines"),
51258
51255
  categories: {
@@ -51260,8 +51257,7 @@ var survivalCollector = {
51260
51257
  author: stringAt(row, "authorEmail"),
51261
51258
  cohort: stringAt(row, "cohortMonth")
51262
51259
  }
51263
- });
51264
- return facts;
51260
+ }));
51265
51261
  }
51266
51262
  };
51267
51263
  //#endregion
@@ -51276,7 +51272,8 @@ var markerPatterns = [
51276
51272
  var scanFileForTodos = (content) => {
51277
51273
  const byMarker = {};
51278
51274
  let total = 0;
51279
- for (const line of content.split("\n")) for (const [marker, pattern] of markerPatterns) if (pattern.test(line)) {
51275
+ for (const line of content.split("\n")) for (const [marker, pattern] of markerPatterns) {
51276
+ if (!pattern.test(line)) continue;
51280
51277
  byMarker[marker] = (byMarker[marker] ?? 0) + 1;
51281
51278
  total += 1;
51282
51279
  }
@@ -51624,7 +51621,7 @@ var buildAliasMap = (aliases) => {
51624
51621
  };
51625
51622
  var parseMaxInCharts = (value) => {
51626
51623
  if (value === void 0) return 10;
51627
- if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 100) throw configError("`contributors.maxInCharts` must be an integer between 1 and 100.");
51624
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > 100) throw configError("`contributors.maxInCharts` must be an integer between 1 and 100.");
51628
51625
  return value;
51629
51626
  };
51630
51627
  var weekStarts = ["monday", "sunday"];
@@ -52005,7 +52002,7 @@ var redundancyReason = ({ name, setup }) => {
52005
52002
  * keeps its patterns in commented groups.
52006
52003
  */
52007
52004
  var isBlank = (line) => line.trim() === "";
52008
- var isComment = (line) => line.trim().startsWith("#");
52005
+ var isComment = (line) => line.trimStart().startsWith("#");
52009
52006
  var isPattern = (line) => !isBlank(line) && !isComment(line);
52010
52007
  /**
52011
52008
  * What decides where a pattern sits in an alphabetically ordered file: the path
@@ -52388,7 +52385,7 @@ var listLineages = (repoRoot) => gen(function* () {
52388
52385
  const initial = yield* listFirstParentChain(repoRoot);
52389
52386
  const queue = initial.length > 0 ? [{
52390
52387
  chain: initial,
52391
- endsAtMs: Number.POSITIVE_INFINITY
52388
+ endsAtMs: Infinity
52392
52389
  }] : [];
52393
52390
  while (queue.length > 0) {
52394
52391
  const pending = queue.shift();
@@ -52549,7 +52546,7 @@ var runScan = ({ repoPath, collectorNames, maxCommits, sample, force = false })
52549
52546
  return succeed$4(/* @__PURE__ */ new Map());
52550
52547
  }), timed);
52551
52548
  const durationMs = Math.max(1, Math.round(toMillis(batchDuration) / Math.max(1, outputs.size)));
52552
- const written = new Set(yield* forEach$1([...outputs.entries()], ([sha, output]) => writeCollectorOutput({
52549
+ const written = new Set(yield* forEach$1(outputs.entries().toArray(), ([sha, output]) => writeCollectorOutput({
52553
52550
  catalog,
52554
52551
  sha,
52555
52552
  collector,
@@ -52831,7 +52828,7 @@ var runGc = ({ repoPath, unreachable, offMainline, stale, collectorNames, dryRun
52831
52828
  value: { kind: "stale" }
52832
52829
  });
52833
52830
  }
52834
- for (const [name, count] of [...plan.countsByCollector.entries()].toSorted(([left], [right]) => left.localeCompare(right))) choices.push({
52831
+ for (const [name, count] of [...plan.countsByCollector].toSorted(([left], [right]) => left.localeCompare(right))) choices.push({
52835
52832
  title: `All ${count} outputs of collector "${name}"`,
52836
52833
  value: {
52837
52834
  kind: "collector",
@@ -52881,7 +52878,7 @@ var runGc = ({ repoPath, unreachable, offMainline, stale, collectorNames, dryRun
52881
52878
  for (const sha of shas) targets.add(path.join(plan.commitsPath, sha, name));
52882
52879
  reportLines.push(`${count} outputs of collector "${name}"`);
52883
52880
  }
52884
- if (targets.size === 0 && !pruneCacheNamespaces) {
52881
+ if (!pruneCacheNamespaces && targets.size === 0) {
52885
52882
  yield* log("Nothing to garbage-collect.");
52886
52883
  return;
52887
52884
  }
@@ -53092,13 +53089,14 @@ var sumMetric = (commit, metric, filter) => {
53092
53089
  };
53093
53090
  var groupMetric = (commit, metric, categoryKey) => {
53094
53091
  const grouped = {};
53095
- for (const facts of commit.factsByCollector.values()) for (const fact of facts) if (fact.metric === metric) {
53092
+ for (const facts of commit.factsByCollector.values()) for (const fact of facts) {
53093
+ if (fact.metric !== metric) continue;
53096
53094
  const key = fact.categories?.[categoryKey] ?? "(unknown)";
53097
53095
  grouped[key] = (grouped[key] ?? 0) + fact.value;
53098
53096
  }
53099
53097
  return grouped;
53100
53098
  };
53101
- var hasMetric = (commit, metric) => [...commit.factsByCollector.values()].some((facts) => facts.some((fact) => fact.metric === metric));
53099
+ var hasMetric = (commit, metric) => commit.factsByCollector.values().some((facts) => facts.some((fact) => fact.metric === metric));
53102
53100
  /** Re-keys a numeric record, summing values whose new keys collide. */
53103
53101
  var sumByKey = (record, keyOf) => {
53104
53102
  const merged = {};
@@ -53148,7 +53146,7 @@ var buildDashboardData = (repoRoot, commits, config, remoteUrl, lineageOf) => {
53148
53146
  }];
53149
53147
  }));
53150
53148
  /** The raw `"Name <email>"` trailers on a commit, in the order git listed them. */
53151
- const coAuthorsOf = (commit) => [...commit.factsByCollector.values()].flat().filter((fact) => fact.metric === "commits.coAuthor").map((fact) => fact.categories?.["coAuthor"] ?? "");
53149
+ const coAuthorsOf = (commit) => commit.factsByCollector.values().toArray().flat().filter((fact) => fact.metric === "commits.coAuthor").map((fact) => fact.categories?.["coAuthor"] ?? "");
53152
53150
  /**
53153
53151
  * A co-author resolved the same way an author is. An identity with no email
53154
53152
  * (a bare `Co-authored-by: Some Name`) keys off its name instead, so those
@@ -53290,7 +53288,7 @@ var buildDashboardData = (repoRoot, commits, config, remoteUrl, lineageOf) => {
53290
53288
  const bucketFor = (resolved, observedName) => {
53291
53289
  const name = nameOf(resolved, observedName);
53292
53290
  const email = resolved.canonicalEmail.toLowerCase();
53293
- const key = resolved.kind === "human" ? email : `${name.toLowerCase()}\u001F${email}`;
53291
+ const key = resolved.kind === "human" ? email : `${name.toLowerCase()}\u{1F}${email}`;
53294
53292
  const bucket = contributorMap.get(key) ?? {
53295
53293
  email: resolved.canonicalEmail,
53296
53294
  name,
@@ -53337,7 +53335,7 @@ var buildDashboardData = (repoRoot, commits, config, remoteUrl, lineageOf) => {
53337
53335
  bot: 0,
53338
53336
  ai: 0
53339
53337
  };
53340
- const contributors = [...contributorMap.values()].toSorted((left, right) => involvementOf(right) - involvementOf(left)).filter((bucket) => {
53338
+ const contributors = contributorMap.values().toArray().toSorted((left, right) => involvementOf(right) - involvementOf(left)).filter((bucket) => {
53341
53339
  if (keptPerKind[bucket.kind] >= perKindCap) return false;
53342
53340
  keptPerKind[bucket.kind] += 1;
53343
53341
  return true;
@@ -53352,7 +53350,7 @@ var buildDashboardData = (repoRoot, commits, config, remoteUrl, lineageOf) => {
53352
53350
  contributors: { maxInCharts: config.maxInCharts },
53353
53351
  charts: {
53354
53352
  weekStartsOn: config.weekStartsOn,
53355
- ...Object.keys(config.chartAnnotations).length > 0 ? { annotations: config.chartAnnotations } : {}
53353
+ ...Object.keys(config.chartAnnotations).length > 0 && { annotations: config.chartAnnotations }
53356
53354
  }
53357
53355
  },
53358
53356
  repo: {
@@ -60226,12 +60224,12 @@ var runReport = ({ repoPath, outPath, open }) => gen(function* () {
60226
60224
  const [indexHtml, dataJson] = await Promise.all([readFile(path.join(assetsDir, "index.html"), "utf8"), readFile(dataPath, "utf8")]);
60227
60225
  const dataTag = `<script>window.__REPO_DIVE_DATA__ = ${dataJson.replaceAll("</", String.raw`<\/`)};<\/script>`;
60228
60226
  let result = indexHtml;
60229
- const styleMatches = [...result.matchAll(/<link rel="stylesheet"[^>]*href="([^"]+)"[^>]*>/g)];
60227
+ const styleMatches = result.matchAll(/<link rel="stylesheet"[^>]*href="([^"]+)"[^>]*>/g).toArray();
60230
60228
  for (const match of styleMatches) {
60231
60229
  const css = await readFile(path.join(assetsDir, match[1] ?? ""), "utf8");
60232
60230
  result = result.replace(match[0], () => `<style>${css}</style>`);
60233
60231
  }
60234
- const scriptMatches = [...result.matchAll(/<script type="module"[^>]*src="([^"]+)"[^>]*><\/script>/g)];
60232
+ const scriptMatches = result.matchAll(/<script type="module"[^>]*src="([^"]+)"[^>]*><\/script>/g).toArray();
60235
60233
  for (const match of scriptMatches) {
60236
60234
  const safeJs = (await readFile(path.join(assetsDir, match[1] ?? ""), "utf8")).replaceAll("<\/script", String.raw`<\/script`);
60237
60235
  result = result.replace(match[0], () => `${dataTag}<script type="module">${safeJs}<\/script>`);
@@ -60271,7 +60269,14 @@ var scanCommand = make$6("scan", {
60271
60269
  })));
60272
60270
  //#endregion
60273
60271
  //#region src/cli/status-command/run-status.ts
60274
- var exists = (filePath) => promise(() => access(filePath).then(() => true, () => false));
60272
+ var exists = (filePath) => promise(async () => {
60273
+ try {
60274
+ await access(filePath);
60275
+ return true;
60276
+ } catch {
60277
+ return false;
60278
+ }
60279
+ });
60275
60280
  var runStatus = ({ repoPath }) => gen(function* () {
60276
60281
  const repoRoot = yield* resolveRepoRoot(repoPath);
60277
60282
  const commits = yield* listCommits(repoRoot);