hillclimb 0.8.8 → 0.8.9

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/main.js +139 -68
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -14711,6 +14711,7 @@ function createGitError(args, err, timeoutMs = GIT_COMMAND_TIMEOUT_MS) {
14711
14711
  const message = isTimeout ? `${command} timed out after ${timeoutMs}ms` : failure.code === "ENOBUFS" ? `${command} output exceeded the ${formatSize(failure.maxBufferBytes ?? EXEC_OPTS.maxBuffer)} limit` : formatGitFailure(command, failure, err);
14712
14712
  const wrapped = new Error(message);
14713
14713
  wrapped.isGitTimeout = isTimeout;
14714
+ wrapped.gitExitStatus = failure.status;
14714
14715
  const stderr = outputToString(failure.stderr);
14715
14716
  wrapped.gitStderr = stderr ? truncateOutput(stderr) : void 0;
14716
14717
  wrapped.isGitMissingObject = !isTimeout && failure.code !== "ENOBUFS" && matchesMissingObject(stderr);
@@ -14752,24 +14753,50 @@ function isGitRepo(dir) {
14752
14753
  }
14753
14754
  }
14754
14755
  function captureWorkingCommitSha(repoRoot) {
14755
- try {
14756
- const sha = timeCaptureStage(
14757
- repoRoot,
14758
- "stash-create",
14759
- () => git(repoRoot, ["stash", "create"])
14760
- );
14761
- if (sha) return sha;
14762
- } catch (err) {
14763
- if (isGitTimeoutError(err)) throw err;
14764
- }
14765
- try {
14766
- return git(repoRoot, ["rev-parse", "HEAD"]);
14767
- } catch (err) {
14768
- if (isGitTimeoutError(err)) throw err;
14769
- const tree = git(repoRoot, ["write-tree"]);
14770
- return git(repoRoot, ["commit-tree", tree, "-m", "empty baseline"]);
14756
+ for (let attempt = 1; ; attempt++) {
14757
+ let sha;
14758
+ try {
14759
+ sha = timeCaptureStage(
14760
+ repoRoot,
14761
+ "stash-create",
14762
+ () => (
14763
+ // Keep the two expected stderr classifications independent of locale.
14764
+ gitWithEnv(repoRoot, ["stash", "create"], {
14765
+ ...process.env,
14766
+ LC_ALL: "C"
14767
+ })
14768
+ )
14769
+ );
14770
+ } catch (err) {
14771
+ const failure = err;
14772
+ const stderr = failure.gitStderr ?? "";
14773
+ if (failure.gitExitStatus === 1 && stderr.includes("You do not have the initial commit yet") && hasUnbornHead(repoRoot)) {
14774
+ appendLog(
14775
+ "info",
14776
+ `git-traces: stash create unavailable (repo=${repoRoot}, reason=unborn-head); capturing index tree`
14777
+ );
14778
+ const tree = git(repoRoot, ["write-tree"]);
14779
+ return git(repoRoot, ["commit-tree", tree, "-m", "empty baseline"]);
14780
+ }
14781
+ if (failure.gitExitStatus !== 1 || !/Unable to create [^\n]*index\.lock[^\n]*: File exists/.test(stderr) || attempt >= 5)
14782
+ throw err;
14783
+ appendLog(
14784
+ "warn",
14785
+ `git-traces: retrying stash create after index lock contention (repo=${repoRoot}, attempt=${attempt}, maxAttempts=5): ${failure.message}`
14786
+ );
14787
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 250);
14788
+ continue;
14789
+ }
14790
+ return sha || git(repoRoot, ["rev-parse", "--verify", "HEAD"]);
14771
14791
  }
14772
14792
  }
14793
+ function hasUnbornHead(repoRoot) {
14794
+ const ref = git(repoRoot, ["symbolic-ref", "--quiet", "HEAD"]);
14795
+ const args = ["show-ref", "--verify", "--quiet", ref];
14796
+ const result = spawnSync("git", args, { cwd: repoRoot, ...EXEC_OPTS });
14797
+ if (result.error) throw createGitError(args, result.error);
14798
+ return result.status === 1;
14799
+ }
14773
14800
  function captureBaselineSha(repoRoot) {
14774
14801
  return captureWorkingCommitSha(repoRoot);
14775
14802
  }
@@ -15040,39 +15067,53 @@ function filterOmittedFilesFromTree(repoRoot, treeSha, options = {}) {
15040
15067
  }
15041
15068
  function buildUntrackedTree(repoRoot, options = {}) {
15042
15069
  const limits = options.limits ?? DEFAULT_SNAPSHOT_LIMITS;
15043
- const list = git(repoRoot, [
15044
- "ls-files",
15045
- "--others",
15046
- "--exclude-standard",
15047
- "-z"
15048
- ]);
15049
- if (!list) return null;
15070
+ const list = timeCaptureStage(
15071
+ repoRoot,
15072
+ "untracked-list",
15073
+ () => git(repoRoot, ["ls-files", "--others", "--exclude-standard", "-z"])
15074
+ );
15050
15075
  const kept = [];
15051
- for (const relPath of list.split("\0")) {
15052
- if (!relPath) continue;
15053
- try {
15054
- const absPath = path14.join(repoRoot, relPath);
15055
- const stat = fs14.lstatSync(absPath);
15056
- const reason = classifyOmission(
15057
- relPath,
15058
- stat.size,
15059
- () => readWorkingFileHead(absPath),
15060
- limits,
15061
- false
15062
- );
15063
- if (reason) {
15064
- options.omittedFiles?.push({
15065
- path: relPath,
15066
- sizeBytes: stat.size,
15067
- tracked: false,
15068
- reason
15069
- });
15070
- continue;
15076
+ let candidates = 0;
15077
+ let keptBytes = 0;
15078
+ let omitted = 0;
15079
+ let omittedBytes = 0;
15080
+ let skipped = 0;
15081
+ timeCaptureStage(repoRoot, "untracked-filter", () => {
15082
+ for (const relPath of list.split("\0")) {
15083
+ if (!relPath) continue;
15084
+ candidates++;
15085
+ try {
15086
+ const absPath = path14.join(repoRoot, relPath);
15087
+ const stat = fs14.lstatSync(absPath);
15088
+ const reason = classifyOmission(
15089
+ relPath,
15090
+ stat.size,
15091
+ () => readWorkingFileHead(absPath),
15092
+ limits,
15093
+ false
15094
+ );
15095
+ if (reason) {
15096
+ omitted++;
15097
+ omittedBytes += stat.size;
15098
+ options.omittedFiles?.push({
15099
+ path: relPath,
15100
+ sizeBytes: stat.size,
15101
+ tracked: false,
15102
+ reason
15103
+ });
15104
+ continue;
15105
+ }
15106
+ kept.push(relPath);
15107
+ keptBytes += stat.size;
15108
+ } catch {
15109
+ skipped++;
15071
15110
  }
15072
- kept.push(relPath);
15073
- } catch {
15074
15111
  }
15075
- }
15112
+ });
15113
+ appendLog(
15114
+ "info",
15115
+ `git-traces: untracked inventory (repo=${repoRoot}, candidates=${candidates}, kept=${kept.length}, keptBytes=${keptBytes}, omitted=${omitted}, omittedBytes=${omittedBytes}, skipped=${skipped})`
15116
+ );
15076
15117
  if (kept.length === 0) return null;
15077
15118
  const tmpIndex = path14.join(
15078
15119
  os7.tmpdir(),
@@ -15080,11 +15121,19 @@ function buildUntrackedTree(repoRoot, options = {}) {
15080
15121
  );
15081
15122
  const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
15082
15123
  try {
15083
- gitBuffer(repoRoot, ["update-index", "--add", "-z", "--stdin"], {
15084
- input: `${kept.join("\0")}\0`,
15085
- env
15086
- });
15087
- return gitWithEnv(repoRoot, ["write-tree"], env);
15124
+ timeCaptureStage(
15125
+ repoRoot,
15126
+ "untracked-hash-and-index",
15127
+ () => gitBuffer(repoRoot, ["update-index", "--add", "-z", "--stdin"], {
15128
+ input: `${kept.join("\0")}\0`,
15129
+ env
15130
+ })
15131
+ );
15132
+ return timeCaptureStage(
15133
+ repoRoot,
15134
+ "untracked-write-tree",
15135
+ () => gitWithEnv(repoRoot, ["write-tree"], env)
15136
+ );
15088
15137
  } finally {
15089
15138
  try {
15090
15139
  fs14.unlinkSync(tmpIndex);
@@ -15139,26 +15188,48 @@ function mergeSnapshotTrees(repoRoot, filteredTrackedTree, untrackedTree) {
15139
15188
  );
15140
15189
  const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
15141
15190
  try {
15142
- gitWithEnv(repoRoot, ["read-tree", filteredTrackedTree], env);
15143
- const untrackedList = gitBuffer(
15191
+ timeCaptureStage(
15144
15192
  repoRoot,
15145
- ["ls-tree", "-r", untrackedTree],
15146
- {
15147
- env
15148
- }
15149
- ).toString("utf-8");
15150
- const indexInfo = untrackedList.split("\n").filter(Boolean).map((line) => {
15151
- const [meta, filePath] = line.split(" ");
15152
- const [mode, , sha] = meta.split(/\s+/);
15153
- return `${mode} ${sha} ${filePath}`;
15154
- }).join("\n");
15193
+ "assembly-read-tree",
15194
+ () => gitWithEnv(repoRoot, ["read-tree", filteredTrackedTree], env)
15195
+ );
15196
+ const untrackedList = timeCaptureStage(
15197
+ repoRoot,
15198
+ "assembly-list-untracked",
15199
+ () => gitBuffer(repoRoot, ["ls-tree", "-r", untrackedTree], { env }).toString(
15200
+ "utf-8"
15201
+ )
15202
+ );
15203
+ let overlayEntries = 0;
15204
+ const indexInfo = timeCaptureStage(
15205
+ repoRoot,
15206
+ "assembly-prepare-index",
15207
+ () => untrackedList.split("\n").filter(Boolean).map((line) => {
15208
+ const [meta, filePath] = line.split(" ");
15209
+ const [mode, , sha] = meta.split(/\s+/);
15210
+ overlayEntries++;
15211
+ return `${mode} ${sha} ${filePath}`;
15212
+ }).join("\n")
15213
+ );
15214
+ appendLog(
15215
+ "info",
15216
+ `git-traces: assembly inventory (repo=${repoRoot}, untrackedEntries=${overlayEntries})`
15217
+ );
15155
15218
  if (indexInfo) {
15156
- gitBuffer(repoRoot, ["update-index", "--index-info"], {
15157
- input: indexInfo,
15158
- env
15159
- });
15219
+ timeCaptureStage(
15220
+ repoRoot,
15221
+ "assembly-update-index",
15222
+ () => gitBuffer(repoRoot, ["update-index", "--index-info"], {
15223
+ input: indexInfo,
15224
+ env
15225
+ })
15226
+ );
15160
15227
  }
15161
- return gitWithEnv(repoRoot, ["write-tree"], env);
15228
+ return timeCaptureStage(
15229
+ repoRoot,
15230
+ "assembly-write-tree",
15231
+ () => gitWithEnv(repoRoot, ["write-tree"], env)
15232
+ );
15162
15233
  } finally {
15163
15234
  try {
15164
15235
  fs14.unlinkSync(tmpIndex);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hillclimb",
3
- "version": "0.8.8",
3
+ "version": "0.8.9",
4
4
  "description": "Extract and export AI coding tool logs grouped by repo",
5
5
  "license": "MIT",
6
6
  "type": "module",