hillclimb 0.4.0 → 0.4.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.
Files changed (2) hide show
  1. package/dist/cli.js +102 -31
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -13515,7 +13515,7 @@ var EXEC_OPTS = {
13515
13515
  maxBuffer: 50 * 1024 * 1024
13516
13516
  };
13517
13517
  var MAX_ERROR_OUTPUT_CHARS = 2e3;
13518
- var MAX_UNTRACKED_FILE_BYTES = 10 * 1024 * 1024;
13518
+ var MAX_SNAPSHOT_FILE_BYTES = 10 * 1024 * 1024;
13519
13519
  var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
13520
13520
  function quoteGitArg(arg) {
13521
13521
  if (/^[A-Za-z0-9_./:=@%+,-]+$/.test(arg)) return arg;
@@ -13619,7 +13619,55 @@ function captureSnapshotSha(repoRoot) {
13619
13619
  }
13620
13620
  return git(repoRoot, ["rev-parse", "HEAD"]);
13621
13621
  }
13622
- function buildUntrackedTree(repoRoot) {
13622
+ function recordOmittedSnapshotFile(omittedFiles, file) {
13623
+ omittedFiles?.push(file);
13624
+ const action = file.tracked ? "omitting tracked" : "skipping untracked";
13625
+ appendLog(
13626
+ "warn",
13627
+ `git-traces: ${action} ${file.path} (${file.sizeBytes} bytes > ${MAX_SNAPSHOT_FILE_BYTES} limit)`
13628
+ );
13629
+ }
13630
+ function parseLsTreeLongZ(output) {
13631
+ const entries = [];
13632
+ for (const record of output.toString("utf-8").split("\0")) {
13633
+ if (!record) continue;
13634
+ const tab = record.indexOf(" ");
13635
+ if (tab === -1) continue;
13636
+ const meta = record.slice(0, tab);
13637
+ const filePath = record.slice(tab + 1);
13638
+ const [mode, type, sha, sizeRaw] = meta.trim().split(/\s+/);
13639
+ if (type !== "blob" || !mode || !sha || !sizeRaw) continue;
13640
+ const sizeBytes = Number.parseInt(sizeRaw, 10);
13641
+ if (!Number.isFinite(sizeBytes)) continue;
13642
+ entries.push({ mode, sha, sizeBytes, path: filePath });
13643
+ }
13644
+ return entries;
13645
+ }
13646
+ function listOversizedTreeFiles(repoRoot, treeSha, omittedFiles) {
13647
+ const output = gitBuffer(repoRoot, ["ls-tree", "-r", "-l", "-z", treeSha]);
13648
+ const oversized = [];
13649
+ for (const entry of parseLsTreeLongZ(output)) {
13650
+ if (entry.sizeBytes <= MAX_SNAPSHOT_FILE_BYTES) continue;
13651
+ const file = {
13652
+ path: entry.path,
13653
+ sizeBytes: entry.sizeBytes,
13654
+ tracked: true,
13655
+ reason: "file-over-limit",
13656
+ gitObjectSha: entry.sha
13657
+ };
13658
+ oversized.push(file);
13659
+ recordOmittedSnapshotFile(omittedFiles, file);
13660
+ }
13661
+ return oversized;
13662
+ }
13663
+ function removePathsFromIndex(repoRoot, env, paths) {
13664
+ if (paths.length === 0) return;
13665
+ gitBuffer(repoRoot, ["update-index", "--force-remove", "-z", "--stdin"], {
13666
+ input: `${paths.join("\0")}\0`,
13667
+ env
13668
+ });
13669
+ }
13670
+ function buildUntrackedTree(repoRoot, omittedFiles) {
13623
13671
  const list = git(repoRoot, [
13624
13672
  "ls-files",
13625
13673
  "--others",
@@ -13632,11 +13680,13 @@ function buildUntrackedTree(repoRoot) {
13632
13680
  if (!relPath) continue;
13633
13681
  try {
13634
13682
  const stat = fs11.lstatSync(path14.join(repoRoot, relPath));
13635
- if (stat.size > MAX_UNTRACKED_FILE_BYTES) {
13636
- appendLog(
13637
- "warn",
13638
- `git-traces: skipping untracked ${relPath} (${stat.size} bytes > ${MAX_UNTRACKED_FILE_BYTES} limit)`
13639
- );
13683
+ if (stat.size > MAX_SNAPSHOT_FILE_BYTES) {
13684
+ recordOmittedSnapshotFile(omittedFiles, {
13685
+ path: relPath,
13686
+ sizeBytes: stat.size,
13687
+ tracked: false,
13688
+ reason: "file-over-limit"
13689
+ });
13640
13690
  continue;
13641
13691
  }
13642
13692
  kept.push(relPath);
@@ -13662,10 +13712,15 @@ function buildUntrackedTree(repoRoot) {
13662
13712
  }
13663
13713
  }
13664
13714
  }
13665
- function buildSnapshotTree(repoRoot, stashSha) {
13715
+ function buildSnapshotTree(repoRoot, stashSha, options = {}) {
13666
13716
  const trackedTree = git(repoRoot, ["rev-parse", `${stashSha}^{tree}`]);
13667
- const untrackedTree = buildUntrackedTree(repoRoot);
13668
- if (!untrackedTree || untrackedTree === EMPTY_TREE_SHA) {
13717
+ const oversizedTracked = listOversizedTreeFiles(
13718
+ repoRoot,
13719
+ trackedTree,
13720
+ options.omittedFiles
13721
+ );
13722
+ const untrackedTree = buildUntrackedTree(repoRoot, options.omittedFiles);
13723
+ if (oversizedTracked.length === 0 && (!untrackedTree || untrackedTree === EMPTY_TREE_SHA)) {
13669
13724
  return trackedTree;
13670
13725
  }
13671
13726
  const tmpIndex = path14.join(
@@ -13675,23 +13730,30 @@ function buildSnapshotTree(repoRoot, stashSha) {
13675
13730
  const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
13676
13731
  try {
13677
13732
  gitWithEnv(repoRoot, ["read-tree", trackedTree], env);
13678
- const untrackedList = gitBuffer(
13733
+ removePathsFromIndex(
13679
13734
  repoRoot,
13680
- ["ls-tree", "-r", untrackedTree],
13681
- {
13682
- env
13735
+ env,
13736
+ oversizedTracked.map((file) => file.path)
13737
+ );
13738
+ if (untrackedTree && untrackedTree !== EMPTY_TREE_SHA) {
13739
+ const untrackedList = gitBuffer(
13740
+ repoRoot,
13741
+ ["ls-tree", "-r", untrackedTree],
13742
+ {
13743
+ env
13744
+ }
13745
+ ).toString("utf-8");
13746
+ const indexInfo = untrackedList.split("\n").filter(Boolean).map((line) => {
13747
+ const [meta, filePath] = line.split(" ");
13748
+ const [mode, , sha] = meta.split(/\s+/);
13749
+ return `${mode} ${sha} ${filePath}`;
13750
+ }).join("\n");
13751
+ if (indexInfo) {
13752
+ gitBuffer(repoRoot, ["update-index", "--index-info"], {
13753
+ input: indexInfo,
13754
+ env
13755
+ });
13683
13756
  }
13684
- ).toString("utf-8");
13685
- const indexInfo = untrackedList.split("\n").filter(Boolean).map((line) => {
13686
- const [meta, filePath] = line.split(" ");
13687
- const [mode, , sha] = meta.split(/\s+/);
13688
- return `${mode} ${sha} ${filePath}`;
13689
- }).join("\n");
13690
- if (indexInfo) {
13691
- gitBuffer(repoRoot, ["update-index", "--index-info"], {
13692
- input: indexInfo,
13693
- env
13694
- });
13695
13757
  }
13696
13758
  return gitWithEnv(repoRoot, ["write-tree"], env);
13697
13759
  } finally {
@@ -13778,7 +13840,7 @@ function parseDirtyFilesFromStatus(status) {
13778
13840
  return pathPart;
13779
13841
  }).filter(Boolean);
13780
13842
  }
13781
- function buildBaselineMetadata(repoRoot, sessionId, tool, baselineSha, cliVersion, epoch, prevHeadSha, transitionKind, startedAt) {
13843
+ function buildBaselineMetadata(repoRoot, sessionId, tool, baselineSha, cliVersion, epoch, prevHeadSha, transitionKind, startedAt, omittedFiles = []) {
13782
13844
  const headSha = safeGit(repoRoot, ["rev-parse", "HEAD"]) ?? "unknown";
13783
13845
  const branch = safeGit(repoRoot, ["rev-parse", "--abbrev-ref", "HEAD"]) ?? null;
13784
13846
  const remoteUrl = safeGit(repoRoot, ["config", "--get", "remote.origin.url"]) ?? null;
@@ -13802,7 +13864,8 @@ function buildBaselineMetadata(repoRoot, sessionId, tool, baselineSha, cliVersio
13802
13864
  branch,
13803
13865
  remoteUrl,
13804
13866
  isDirty: dirtyFiles.length > 0,
13805
- dirtyFiles
13867
+ dirtyFiles,
13868
+ ...omittedFiles.length > 0 ? { omittedFiles } : {}
13806
13869
  },
13807
13870
  author: { name: authorName, email: authorEmail },
13808
13871
  hostname: os6.hostname(),
@@ -14246,6 +14309,10 @@ function freezeEpochBaseline(params) {
14246
14309
  sessionId,
14247
14310
  epoch
14248
14311
  );
14312
+ const omittedFiles = [];
14313
+ const baselineTreeSha = buildSnapshotTree(repoRoot, baselineSha, {
14314
+ omittedFiles
14315
+ });
14249
14316
  const baselineMetadata = buildBaselineMetadata(
14250
14317
  repoRoot,
14251
14318
  sessionId,
@@ -14255,9 +14322,9 @@ function freezeEpochBaseline(params) {
14255
14322
  epoch,
14256
14323
  prevHeadSha,
14257
14324
  transitionKind,
14258
- startedAt
14325
+ startedAt,
14326
+ omittedFiles
14259
14327
  );
14260
- const baselineTreeSha = buildSnapshotTree(repoRoot, baselineSha);
14261
14328
  pinFrozenBaselineTree(repoRoot, sessionId, epoch, baselineTreeSha);
14262
14329
  return { baselineSha, baselineTreeSha, baselineMetadata, headSha };
14263
14330
  } catch (err) {
@@ -14303,6 +14370,10 @@ function buildEpochBaselineArtifacts(params) {
14303
14370
  } = params;
14304
14371
  const prefix = epochPrefix(epoch);
14305
14372
  try {
14373
+ const omittedFiles = [];
14374
+ const baselineTreeSha = buildSnapshotTree(repoRoot, baselineSha, {
14375
+ omittedFiles
14376
+ });
14306
14377
  const metadata = buildBaselineMetadata(
14307
14378
  repoRoot,
14308
14379
  sessionId,
@@ -14312,9 +14383,9 @@ function buildEpochBaselineArtifacts(params) {
14312
14383
  epoch,
14313
14384
  prevHeadSha,
14314
14385
  transitionKind,
14315
- startedAt
14386
+ startedAt,
14387
+ omittedFiles
14316
14388
  );
14317
- const baselineTreeSha = buildSnapshotTree(repoRoot, baselineSha);
14318
14389
  return buildFrozenEpochBaselineArtifacts({
14319
14390
  repoRoot,
14320
14391
  sessionId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hillclimb",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Extract and export AI coding tool logs grouped by repo",
5
5
  "license": "MIT",
6
6
  "type": "module",