hillclimb 0.5.1 → 0.5.3

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 (3) hide show
  1. package/README.md +13 -24
  2. package/dist/cli.js +155 -52
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,43 +1,32 @@
1
1
  # hillclimb
2
2
 
3
- Extract AI coding tool sessions (Claude Code, Cursor, Codex, opencode, GitHub Copilot Chat) and upload them to a Hillclimb project.
3
+ Capture AI coding sessions and upload agent traces, ATIF traces, git traces, and
4
+ debug logs to Hillclimb.
4
5
 
5
- ## Quickstart
6
+ Supported tools: Claude Code, Cursor, Codex, opencode, and GitHub Copilot Chat.
6
7
 
7
- ```sh
8
- npx hillclimb
9
- ```
8
+ ## Quickstart
10
9
 
11
- Run in any git repo. Sessions auto-upload when they end.
10
+ Run once from a git repo:
12
11
 
13
12
  ```sh
14
- npx hillclimb status
13
+ npx hillclimb
15
14
  ```
16
15
 
17
- Shows login and hook status for the current repo.
18
-
19
- ## Hooks
20
-
21
- `hillclimb` detects which tools you use (`~/.claude`, `~/.cursor`, `~/.codex`, `~/.local/share/opencode`, VS Code GitHub Copilot Chat storage) and installs upload + git-trace hooks for each. You might want to gitignore the respective directories for the tools you use in your repo.
22
-
23
- To manually export historical logs instead of configuring auto-upload, run:
24
-
25
- ```sh
26
- npx hillclimb export
27
- ```
16
+ Hillclimb signs you in, connects the repo to a project, installs local hooks for
17
+ detected tools, and uploads future sessions automatically.
28
18
 
29
19
  ## Logs
30
20
 
31
- Hillclimb writes one log file per day to `~/.hillclimb/logs/YYYY-MM-DD.log`. Timestamps and the daily filename are in Pacific Time, so logs line up with Hillclimb's internal clock regardless of your timezone.
32
-
33
- Each hook firing tags every line it writes (parent + worker) with a short correlation ID, e.g. `[a1b2c3]`. To follow one flow across interleaved hook runs:
21
+ Daily logs are written to:
34
22
 
35
23
  ```sh
36
- grep '\[a1b2c3\]' ~/.hillclimb/logs/$(TZ=America/Los_Angeles date +%F).log
24
+ ~/.hillclimb/logs/YYYY-MM-DD.log
37
25
  ```
38
26
 
39
- HTTP calls log method, path, status, and latency. Auth codes and presigned-URL signatures are deliberately omitted.
27
+ If a hook reports `npx: command not found`, install Node/npm or make sure `npx`
28
+ is available to the agent hook shell.
40
29
 
41
30
  ## License
42
31
 
43
- MIT see [LICENSE](LICENSE).
32
+ MIT - see [LICENSE](LICENSE).
package/dist/cli.js CHANGED
@@ -2319,6 +2319,7 @@ import crypto3 from "crypto";
2319
2319
  import fs11 from "fs";
2320
2320
  import os6 from "os";
2321
2321
  import path15 from "path";
2322
+ import readline from "readline";
2322
2323
 
2323
2324
  // src/debug-logs.ts
2324
2325
  import crypto from "crypto";
@@ -13384,8 +13385,8 @@ var DEFAULT_STATE_DIR = path14.join(
13384
13385
  ".hillclimb",
13385
13386
  "agent-uploads"
13386
13387
  );
13387
- var LOCK_RETRIES2 = 120;
13388
- var LOCK_RETRY_DELAY_MS2 = 500;
13388
+ var DEFAULT_LOCK_WAIT_MS = 5 * 60 * 1e3;
13389
+ var DEFAULT_LOCK_RETRY_DELAY_MS = 500;
13389
13390
  var DEFAULT_STATE_TTL_MS = 24 * 60 * 60 * 1e3;
13390
13391
  function stateDir2() {
13391
13392
  return process.env.HILLCLIMB_UPLOAD_STATE_DIR ?? DEFAULT_STATE_DIR;
@@ -13402,6 +13403,23 @@ function stateTtlMs() {
13402
13403
  DEFAULT_STATE_TTL_MS
13403
13404
  );
13404
13405
  }
13406
+ function lockRetryDelayMs() {
13407
+ return Math.max(
13408
+ 1,
13409
+ readPositiveEnvMs(
13410
+ "HILLCLIMB_UPLOAD_LOCK_RETRY_DELAY_MS",
13411
+ DEFAULT_LOCK_RETRY_DELAY_MS
13412
+ )
13413
+ );
13414
+ }
13415
+ function lockRetries() {
13416
+ return Math.max(
13417
+ 1,
13418
+ Math.ceil(
13419
+ readPositiveEnvMs("HILLCLIMB_UPLOAD_LOCK_WAIT_MS", DEFAULT_LOCK_WAIT_MS) / lockRetryDelayMs()
13420
+ )
13421
+ );
13422
+ }
13405
13423
  function stateFileFor(repoRoot, tool, sessionId) {
13406
13424
  const hash = crypto2.createHash("sha256").update(`${path14.resolve(repoRoot)}\0${tool}\0${sessionId}`).digest("hex").slice(0, 16);
13407
13425
  return path14.join(stateDir2(), `${hash}.json`);
@@ -13437,7 +13455,7 @@ async function deleteUploadState(repoRoot, tool, sessionId) {
13437
13455
  } catch {
13438
13456
  }
13439
13457
  }
13440
- async function acquireLock2(repoRoot, tool, sessionId, retries = LOCK_RETRIES2, delayMs = LOCK_RETRY_DELAY_MS2) {
13458
+ async function acquireLock2(repoRoot, tool, sessionId, retries = lockRetries(), delayMs = lockRetryDelayMs()) {
13441
13459
  const lockPath = lockFileFor(repoRoot, tool, sessionId);
13442
13460
  await fs10.promises.mkdir(stateDir2(), { recursive: true, mode: 448 });
13443
13461
  for (let i = 0; i < retries; i++) {
@@ -13457,10 +13475,13 @@ async function acquireLock2(repoRoot, tool, sessionId, retries = LOCK_RETRIES2,
13457
13475
  await new Promise((r) => setTimeout(r, delayMs));
13458
13476
  continue;
13459
13477
  }
13478
+ if (err.code === "EEXIST") break;
13460
13479
  throw err;
13461
13480
  }
13462
13481
  }
13463
- throw new Error(`Failed to acquire upload lock after ${retries} retries`);
13482
+ throw new Error(
13483
+ `Failed to acquire upload lock for ${tool} session ${sessionId} after ${retries} retries (${delayMs}ms delay, lock=${lockPath})`
13484
+ );
13464
13485
  }
13465
13486
  async function releaseLock2(repoRoot, tool, sessionId) {
13466
13487
  try {
@@ -13588,6 +13609,54 @@ function summarizePayload(payload) {
13588
13609
  cursor_version_present: !!payload.cursor_version
13589
13610
  });
13590
13611
  }
13612
+ async function codexSessionIdFromFile(filePath) {
13613
+ const stream = fs11.createReadStream(filePath, { encoding: "utf-8" });
13614
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
13615
+ try {
13616
+ for await (const line of rl) {
13617
+ if (!line.trim()) continue;
13618
+ try {
13619
+ const obj = JSON.parse(line);
13620
+ if (obj.type === "session_meta") return obj.payload?.id ?? null;
13621
+ } catch {
13622
+ return null;
13623
+ }
13624
+ return null;
13625
+ }
13626
+ } finally {
13627
+ rl.close();
13628
+ stream.destroy();
13629
+ }
13630
+ return null;
13631
+ }
13632
+ async function findCodexTranscriptPath(sessionId) {
13633
+ const sessionsDir = process.env.HILLCLIMB_CODEX_SESSIONS_DIR ?? path15.join(os6.homedir(), ".codex", "sessions");
13634
+ const candidates = [];
13635
+ async function walk(dir) {
13636
+ let entries;
13637
+ try {
13638
+ entries = await fs11.promises.readdir(dir, { withFileTypes: true });
13639
+ } catch {
13640
+ return;
13641
+ }
13642
+ for (const entry of entries) {
13643
+ const full = path15.join(dir, entry.name);
13644
+ if (entry.isDirectory()) {
13645
+ await walk(full);
13646
+ } else if (entry.isFile() && entry.name.endsWith(".jsonl") && entry.name.includes(sessionId)) {
13647
+ candidates.push(full);
13648
+ }
13649
+ }
13650
+ }
13651
+ await walk(sessionsDir);
13652
+ candidates.sort();
13653
+ for (const filePath of candidates) {
13654
+ if (await codexSessionIdFromFile(filePath) === sessionId) {
13655
+ return filePath;
13656
+ }
13657
+ }
13658
+ return void 0;
13659
+ }
13591
13660
  async function selfHealHook(repoRoot, tool) {
13592
13661
  if (process.env.HILLCLIMB_SKIP_HOOK_SELF_HEAL === "1") return;
13593
13662
  try {
@@ -13621,17 +13690,31 @@ function resolveCursorTranscriptPath(payload) {
13621
13690
  `${id}.jsonl`
13622
13691
  );
13623
13692
  }
13693
+ async function resolveTranscriptPath(payload, sourceTool, sessionId) {
13694
+ if (payload.transcript_path) return payload.transcript_path;
13695
+ if (sourceTool === "cursor") return resolveCursorTranscriptPath(payload);
13696
+ if (sourceTool === "codex") {
13697
+ const resolved = await findCodexTranscriptPath(sessionId);
13698
+ if (resolved) {
13699
+ appendLog(
13700
+ "info",
13701
+ `[${sessionId}] resolved missing Codex transcript_path via ~/.codex/sessions: ${resolved}`
13702
+ );
13703
+ }
13704
+ return resolved;
13705
+ }
13706
+ return void 0;
13707
+ }
13624
13708
  async function runUploadInner(payload) {
13625
13709
  const sessionId = resolveHookSessionId(payload);
13626
- const transcriptPath = payload.transcript_path ?? resolveCursorTranscriptPath(payload);
13627
13710
  const cwd = resolveHookCwd(payload);
13628
13711
  const eventKind = classifyHookEvent(payload.hook_event_name);
13629
- if (!sessionId || !transcriptPath || !cwd) {
13712
+ if (!sessionId || !cwd) {
13630
13713
  appendLog(
13631
13714
  "warn",
13632
- `Skipping upload: missing required hook fields (session_id=${!!sessionId}, transcript_path=${!!transcriptPath}, cwd=${!!cwd})`
13715
+ `Skipping upload: missing required hook fields (session_id=${!!sessionId}, transcript_path=${!!payload.transcript_path}, cwd=${!!cwd})`
13633
13716
  );
13634
- return;
13717
+ return false;
13635
13718
  }
13636
13719
  const match = await findProjectForCwd(cwd);
13637
13720
  if (!match) {
@@ -13639,13 +13722,13 @@ async function runUploadInner(payload) {
13639
13722
  "warn",
13640
13723
  `Skipping session ${sessionId}: no hillclimb config for cwd ${cwd}. Run \`npx hillclimb\` in the repo.`
13641
13724
  );
13642
- return;
13725
+ return false;
13643
13726
  }
13644
13727
  const { repoRoot, config } = match;
13645
13728
  const sourceTool = await resolveSourceTool(payload, repoRoot);
13646
13729
  if (!sourceTool) {
13647
13730
  payload.tool = "unknown";
13648
- return;
13731
+ return false;
13649
13732
  }
13650
13733
  payload.tool = sourceTool;
13651
13734
  appendLog(
@@ -13653,6 +13736,18 @@ async function runUploadInner(payload) {
13653
13736
  `[${sessionId}] payload parsed (tool=${sourceTool}, cwd=${cwd}, event=${eventKind ?? "?"})`
13654
13737
  );
13655
13738
  await selfHealHook(repoRoot, sourceTool);
13739
+ const transcriptPath = await resolveTranscriptPath(
13740
+ payload,
13741
+ sourceTool,
13742
+ sessionId
13743
+ );
13744
+ if (!transcriptPath) {
13745
+ appendLog(
13746
+ "warn",
13747
+ `Skipping upload: missing required hook fields (session_id=true, transcript_path=false, cwd=true)`
13748
+ );
13749
+ return false;
13750
+ }
13656
13751
  const transcriptResolved = path15.resolve(transcriptPath);
13657
13752
  try {
13658
13753
  const stat = await fs11.promises.stat(transcriptResolved);
@@ -13661,23 +13756,23 @@ async function runUploadInner(payload) {
13661
13756
  "warn",
13662
13757
  `Skipping session ${sessionId}: transcript_path is not a file: ${transcriptResolved}`
13663
13758
  );
13664
- return;
13759
+ return false;
13665
13760
  }
13666
13761
  } catch (err) {
13667
13762
  appendLog(
13668
13763
  "warn",
13669
13764
  `Skipping session ${sessionId}: transcript_path not readable (${transcriptResolved}): ${err instanceof Error ? err.message : String(err)}`
13670
13765
  );
13671
- return;
13766
+ return false;
13672
13767
  }
13673
13768
  if (!await hasAssistantMessage(transcriptResolved)) {
13674
13769
  appendLog(
13675
13770
  "info",
13676
13771
  `Skipping session ${sessionId}: transcript contains no assistant messages (nothing to upload).`
13677
13772
  );
13678
- return;
13773
+ return false;
13679
13774
  }
13680
- await uploadSession({
13775
+ return await uploadSession({
13681
13776
  sessionId,
13682
13777
  transcriptPath: transcriptResolved,
13683
13778
  repoRoot,
@@ -13689,7 +13784,7 @@ async function runUploadInner(payload) {
13689
13784
  async function uploadSession(args) {
13690
13785
  const { sessionId, transcriptPath, repoRoot, config, sourceTool, eventKind } = args;
13691
13786
  const isSessionEnd = eventKind === "sessionEnd";
13692
- await withUploadLock(repoRoot, sourceTool, sessionId, async () => {
13787
+ return await withUploadLock(repoRoot, sourceTool, sessionId, async () => {
13693
13788
  const prior = await readUploadState(repoRoot, sourceTool, sessionId);
13694
13789
  let transcriptSize;
13695
13790
  let transcriptSha256;
@@ -13706,7 +13801,7 @@ async function uploadSession(args) {
13706
13801
  if (isSessionEnd) {
13707
13802
  await deleteUploadState(repoRoot, sourceTool, sessionId);
13708
13803
  }
13709
- return;
13804
+ return true;
13710
13805
  }
13711
13806
  const now = /* @__PURE__ */ new Date();
13712
13807
  const sourceFile = {
@@ -13736,7 +13831,7 @@ async function uploadSession(args) {
13736
13831
  "error",
13737
13832
  `Session ${sessionId} upload skipped: no saved login for ${config.apiBaseUrl}. Run \`npx hillclimb login\`.`
13738
13833
  );
13739
- return;
13834
+ return false;
13740
13835
  }
13741
13836
  const client = new PlatformClient(
13742
13837
  config.apiBaseUrl,
@@ -13803,13 +13898,13 @@ Uploaded: ${now.toISOString()}`;
13803
13898
  "error",
13804
13899
  `Session ${sessionId} upload failed: authentication expired. Re-run \`npx hillclimb\` in ${repoRoot}.`
13805
13900
  );
13806
- return;
13901
+ return false;
13807
13902
  }
13808
13903
  appendLog(
13809
13904
  "error",
13810
13905
  `Session ${sessionId} upload failed: ${err instanceof Error ? err.message : String(err)}`
13811
13906
  );
13812
- return;
13907
+ return false;
13813
13908
  }
13814
13909
  const next = {
13815
13910
  schemaVersion: CURRENT_SCHEMA_VERSION2,
@@ -13837,6 +13932,7 @@ Uploaded: ${now.toISOString()}`;
13837
13932
  `[${sessionId}] session complete \u2014 contribution ${contributionId}, ${seq} file(s); local state cleared`
13838
13933
  );
13839
13934
  }
13935
+ return true;
13840
13936
  });
13841
13937
  }
13842
13938
  var WORKER_ENV_FLAG = "HILLCLIMB_UPLOAD_WORKER";
@@ -13940,19 +14036,22 @@ async function runUploadWorker() {
13940
14036
  const toolOverride = process.env[TOOL_ENV_FLAG];
13941
14037
  if (toolOverride) payload.tool = toolOverride;
13942
14038
  appendLog("info", `worker: payload summary: ${summarizePayload(payload)}`);
14039
+ let completed = false;
13943
14040
  try {
13944
- await runUploadInner(payload);
14041
+ completed = await runUploadInner(payload);
13945
14042
  } catch (err) {
13946
14043
  appendLog(
13947
14044
  "error",
13948
14045
  `worker: unexpected error: ${err instanceof Error ? err.stack ?? err.message : String(err)}`
13949
14046
  );
13950
14047
  } finally {
13951
- await recordDebugLogCompletion({
13952
- kind: "agent",
13953
- tool: fallbackSourceTool(payload),
13954
- payload
13955
- });
14048
+ if (completed) {
14049
+ await recordDebugLogCompletion({
14050
+ kind: "agent",
14051
+ tool: fallbackSourceTool(payload),
14052
+ payload
14053
+ });
14054
+ }
13956
14055
  try {
13957
14056
  await sweepStaleUploadStates();
13958
14057
  } catch {
@@ -13981,7 +14080,11 @@ var EXEC_OPTS = {
13981
14080
  };
13982
14081
  var MAX_ERROR_OUTPUT_CHARS = 2e3;
13983
14082
  var MAX_SNAPSHOT_FILE_BYTES = 10 * 1024 * 1024;
14083
+ var EXCLUDED_SNAPSHOT_EXTENSIONS = /* @__PURE__ */ new Set([".pdf"]);
13984
14084
  var EMPTY_TREE_SHA = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
14085
+ function hasExcludedSnapshotExtension(filePath) {
14086
+ return EXCLUDED_SNAPSHOT_EXTENSIONS.has(path16.extname(filePath).toLowerCase());
14087
+ }
13985
14088
  function quoteGitArg(arg) {
13986
14089
  if (/^[A-Za-z0-9_./:=@%+,-]+$/.test(arg)) return arg;
13987
14090
  return JSON.stringify(arg);
@@ -14085,10 +14188,8 @@ function recordOmittedSnapshotFile(omittedFiles, file, options = {}) {
14085
14188
  omittedFiles?.push(file);
14086
14189
  if (options.log === false) return;
14087
14190
  const action = file.tracked ? "omitting tracked" : "skipping untracked";
14088
- appendLog(
14089
- "warn",
14090
- `git-traces: ${action} ${file.path} (${file.sizeBytes} bytes > ${MAX_SNAPSHOT_FILE_BYTES} limit)`
14091
- );
14191
+ const reason = file.reason === "file-over-limit" ? `${file.sizeBytes} bytes > ${MAX_SNAPSHOT_FILE_BYTES} limit` : "excluded extension";
14192
+ appendLog("warn", `git-traces: ${action} ${file.path} (${reason})`);
14092
14193
  }
14093
14194
  function parseLsTreeLongZ(output) {
14094
14195
  const entries = [];
@@ -14106,24 +14207,25 @@ function parseLsTreeLongZ(output) {
14106
14207
  }
14107
14208
  return entries;
14108
14209
  }
14109
- function listOversizedTreeFiles(repoRoot, treeSha, options = {}) {
14210
+ function listOmittedTreeFiles(repoRoot, treeSha, options = {}) {
14110
14211
  const output = gitBuffer(repoRoot, ["ls-tree", "-r", "-l", "-z", treeSha]);
14111
- const oversized = [];
14212
+ const omitted = [];
14112
14213
  for (const entry of parseLsTreeLongZ(output)) {
14113
- if (entry.sizeBytes <= MAX_SNAPSHOT_FILE_BYTES) continue;
14214
+ const reason = hasExcludedSnapshotExtension(entry.path) ? "excluded-extension" : entry.sizeBytes > MAX_SNAPSHOT_FILE_BYTES ? "file-over-limit" : null;
14215
+ if (!reason) continue;
14114
14216
  const file = {
14115
14217
  path: entry.path,
14116
14218
  sizeBytes: entry.sizeBytes,
14117
14219
  tracked: true,
14118
- reason: "file-over-limit",
14220
+ reason,
14119
14221
  gitObjectSha: entry.sha
14120
14222
  };
14121
- oversized.push(file);
14223
+ omitted.push(file);
14122
14224
  recordOmittedSnapshotFile(options.omittedFiles, file, {
14123
14225
  log: options.log
14124
14226
  });
14125
14227
  }
14126
- return oversized;
14228
+ return omitted;
14127
14229
  }
14128
14230
  function removePathsFromIndex(repoRoot, env, paths) {
14129
14231
  if (paths.length === 0) return;
@@ -14132,9 +14234,9 @@ function removePathsFromIndex(repoRoot, env, paths) {
14132
14234
  env
14133
14235
  });
14134
14236
  }
14135
- function filterOversizedFilesFromTree(repoRoot, treeSha, options = {}) {
14136
- const oversizedFiles = listOversizedTreeFiles(repoRoot, treeSha, options);
14137
- if (oversizedFiles.length === 0) return treeSha;
14237
+ function filterOmittedFilesFromTree(repoRoot, treeSha, options = {}) {
14238
+ const omittedFiles = listOmittedTreeFiles(repoRoot, treeSha, options);
14239
+ if (omittedFiles.length === 0) return treeSha;
14138
14240
  const tmpIndex = path16.join(
14139
14241
  os7.tmpdir(),
14140
14242
  `hillclimb-filter-${Date.now()}-${process.pid}`
@@ -14145,7 +14247,7 @@ function filterOversizedFilesFromTree(repoRoot, treeSha, options = {}) {
14145
14247
  removePathsFromIndex(
14146
14248
  repoRoot,
14147
14249
  env,
14148
- oversizedFiles.map((file) => file.path)
14250
+ omittedFiles.map((file) => file.path)
14149
14251
  );
14150
14252
  return gitWithEnv(repoRoot, ["write-tree"], env);
14151
14253
  } finally {
@@ -14168,12 +14270,13 @@ function buildUntrackedTree(repoRoot, omittedFiles) {
14168
14270
  if (!relPath) continue;
14169
14271
  try {
14170
14272
  const stat = fs12.lstatSync(path16.join(repoRoot, relPath));
14171
- if (stat.size > MAX_SNAPSHOT_FILE_BYTES) {
14273
+ const reason = hasExcludedSnapshotExtension(relPath) ? "excluded-extension" : stat.size > MAX_SNAPSHOT_FILE_BYTES ? "file-over-limit" : null;
14274
+ if (reason) {
14172
14275
  recordOmittedSnapshotFile(omittedFiles, {
14173
14276
  path: relPath,
14174
14277
  sizeBytes: stat.size,
14175
14278
  tracked: false,
14176
- reason: "file-over-limit"
14279
+ reason
14177
14280
  });
14178
14281
  continue;
14179
14282
  }
@@ -14202,7 +14305,7 @@ function buildUntrackedTree(repoRoot, omittedFiles) {
14202
14305
  }
14203
14306
  function buildSnapshotTree(repoRoot, stashSha, options = {}) {
14204
14307
  const trackedTree = git(repoRoot, ["rev-parse", `${stashSha}^{tree}`]);
14205
- const filteredTrackedTree = filterOversizedFilesFromTree(
14308
+ const filteredTrackedTree = filterOmittedFilesFromTree(
14206
14309
  repoRoot,
14207
14310
  trackedTree,
14208
14311
  {
@@ -14274,14 +14377,14 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
14274
14377
  }
14275
14378
  function createTreeDiffPatchGz(repoRoot, fromTreeSha, toTreeSha) {
14276
14379
  if (fromTreeSha === toTreeSha) return null;
14277
- const filteredFromTreeSha = filterOversizedFilesFromTree(
14380
+ const filteredFromTreeSha = filterOmittedFilesFromTree(
14278
14381
  repoRoot,
14279
14382
  fromTreeSha,
14280
14383
  {
14281
14384
  log: false
14282
14385
  }
14283
14386
  );
14284
- const filteredToTreeSha = filterOversizedFilesFromTree(repoRoot, toTreeSha, {
14387
+ const filteredToTreeSha = filterOmittedFilesFromTree(repoRoot, toTreeSha, {
14285
14388
  log: false
14286
14389
  });
14287
14390
  if (filteredFromTreeSha === filteredToTreeSha) return null;
@@ -14527,8 +14630,8 @@ import os8 from "os";
14527
14630
  import path17 from "path";
14528
14631
  var CURRENT_SCHEMA_VERSION3 = 3;
14529
14632
  var DEFAULT_STATE_DIR2 = path17.join(os8.homedir(), ".hillclimb", "git-traces");
14530
- var LOCK_RETRIES3 = 120;
14531
- var LOCK_RETRY_DELAY_MS3 = 500;
14633
+ var LOCK_RETRIES2 = 120;
14634
+ var LOCK_RETRY_DELAY_MS2 = 500;
14532
14635
  function stateDir3() {
14533
14636
  return process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? DEFAULT_STATE_DIR2;
14534
14637
  }
@@ -14656,7 +14759,7 @@ async function deleteSessionState(repoRoot, tool, sessionId) {
14656
14759
  }
14657
14760
  await deleteStateFile(stateFileForRepo(repoRoot, tool));
14658
14761
  }
14659
- async function acquireLock3(repoRoot, tool, retries = LOCK_RETRIES3, delayMs = LOCK_RETRY_DELAY_MS3) {
14762
+ async function acquireLock3(repoRoot, tool, retries = LOCK_RETRIES2, delayMs = LOCK_RETRY_DELAY_MS2) {
14660
14763
  const lockPath = lockFileForRepo(repoRoot, tool);
14661
14764
  await fs13.promises.mkdir(stateDir3(), { recursive: true, mode: 448 });
14662
14765
  for (let i = 0; i < retries; i++) {
@@ -16058,7 +16161,7 @@ async function confirmExport(group, output) {
16058
16161
  import fs16 from "fs";
16059
16162
  import os10 from "os";
16060
16163
  import path21 from "path";
16061
- import readline from "readline";
16164
+ import readline2 from "readline";
16062
16165
  var SKIP_DIRS = /* @__PURE__ */ new Set(["tool-results"]);
16063
16166
  async function resolveRepoPath(projectDir) {
16064
16167
  const indexPath = path21.join(projectDir, "sessions-index.json");
@@ -16096,7 +16199,7 @@ async function resolveRepoPath(projectDir) {
16096
16199
  }
16097
16200
  async function extractCwdFromJsonl(filePath) {
16098
16201
  const stream = fs16.createReadStream(filePath, { encoding: "utf-8" });
16099
- const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
16202
+ const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
16100
16203
  try {
16101
16204
  for await (const line of rl) {
16102
16205
  if (!line.trim()) continue;
@@ -16177,10 +16280,10 @@ var ClaudeSource = class {
16177
16280
  import fs17 from "fs";
16178
16281
  import os11 from "os";
16179
16282
  import path22 from "path";
16180
- import readline2 from "readline";
16283
+ import readline3 from "readline";
16181
16284
  async function parseSessionMeta(filePath) {
16182
16285
  const stream = fs17.createReadStream(filePath, { encoding: "utf-8" });
16183
- const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
16286
+ const rl = readline3.createInterface({ input: stream, crlfDelay: Infinity });
16184
16287
  try {
16185
16288
  for await (const line of rl) {
16186
16289
  if (!line.trim()) continue;
@@ -16228,7 +16331,7 @@ async function loadHistory(historyPath) {
16228
16331
  return map;
16229
16332
  }
16230
16333
  const stream = fs17.createReadStream(historyPath, { encoding: "utf-8" });
16231
- const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
16334
+ const rl = readline3.createInterface({ input: stream, crlfDelay: Infinity });
16232
16335
  try {
16233
16336
  for await (const line of rl) {
16234
16337
  if (!line.trim()) continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hillclimb",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Extract and export AI coding tool logs grouped by repo",
5
5
  "license": "MIT",
6
6
  "type": "module",