hillclimb 0.1.8 → 0.1.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/cli.js +260 -175
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import fs16 from "fs";
5
- import path19 from "path";
4
+ import fs17 from "fs";
5
+ import path20 from "path";
6
6
  import * as p6 from "@clack/prompts";
7
7
 
8
8
  // src/commands/init.ts
@@ -125,16 +125,65 @@ function detectRepoRoot() {
125
125
  }
126
126
  }
127
127
 
128
- // src/identity.ts
128
+ // src/gitignore.ts
129
129
  import fs2 from "fs";
130
130
  import path3 from "path";
131
- var IDENTITY_PATH = path3.join(configDir(), "identity.json");
131
+ function toGitignorePattern(repoRoot, file) {
132
+ const rel = path3.relative(repoRoot, file);
133
+ if (!rel || rel.startsWith("..") || path3.isAbsolute(rel)) return null;
134
+ return `/${rel.split(path3.sep).join("/")}`;
135
+ }
136
+ function normalizePattern(pattern) {
137
+ return pattern.trim().replace(/\\/g, "/").replace(/^\/+/, "");
138
+ }
139
+ async function ensureGitignored(repoRoot, files) {
140
+ const gitignorePath = path3.join(repoRoot, ".gitignore");
141
+ const patterns = [
142
+ ...new Set(
143
+ files.map((file) => toGitignorePattern(repoRoot, file)).filter((pattern) => pattern !== null)
144
+ )
145
+ ];
146
+ let content = "";
147
+ let created = false;
148
+ try {
149
+ content = await fs2.promises.readFile(gitignorePath, "utf-8");
150
+ } catch (err) {
151
+ if (err.code !== "ENOENT") throw err;
152
+ created = true;
153
+ }
154
+ const existing = new Set(
155
+ content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map(normalizePattern)
156
+ );
157
+ const added = patterns.filter(
158
+ (pattern) => !existing.has(normalizePattern(pattern))
159
+ );
160
+ if (added.length === 0) {
161
+ return { path: gitignorePath, added: [], created: false, changed: false };
162
+ }
163
+ let next = content;
164
+ if (next && !next.endsWith("\n")) next += "\n";
165
+ if (next) next += "\n";
166
+ next += ["# Hillclimb hook files", ...added].join("\n");
167
+ next += "\n";
168
+ await fs2.promises.writeFile(gitignorePath, next);
169
+ return {
170
+ path: gitignorePath,
171
+ added,
172
+ created,
173
+ changed: true
174
+ };
175
+ }
176
+
177
+ // src/identity.ts
178
+ import fs3 from "fs";
179
+ import path4 from "path";
180
+ var IDENTITY_PATH = path4.join(configDir(), "identity.json");
132
181
  function normalizeUrl(apiBaseUrl) {
133
182
  return apiBaseUrl.replace(/\/$/, "");
134
183
  }
135
184
  async function loadAllIdentities() {
136
185
  try {
137
- const raw = await fs2.promises.readFile(IDENTITY_PATH, "utf-8");
186
+ const raw = await fs3.promises.readFile(IDENTITY_PATH, "utf-8");
138
187
  const parsed = JSON.parse(raw);
139
188
  if (!parsed.identities || typeof parsed.identities !== "object") {
140
189
  return { identities: {} };
@@ -149,12 +198,12 @@ async function loadIdentity(apiBaseUrl) {
149
198
  return file.identities[normalizeUrl(apiBaseUrl)] ?? null;
150
199
  }
151
200
  async function writeIdentityFile(file) {
152
- await fs2.promises.mkdir(configDir(), { recursive: true, mode: 448 });
201
+ await fs3.promises.mkdir(configDir(), { recursive: true, mode: 448 });
153
202
  const tmp = `${IDENTITY_PATH}.tmp`;
154
- await fs2.promises.writeFile(tmp, JSON.stringify(file, null, 2), {
203
+ await fs3.promises.writeFile(tmp, JSON.stringify(file, null, 2), {
155
204
  mode: 384
156
205
  });
157
- await fs2.promises.rename(tmp, IDENTITY_PATH);
206
+ await fs3.promises.rename(tmp, IDENTITY_PATH);
158
207
  }
159
208
  async function saveIdentity(identity) {
160
209
  const file = await loadAllIdentities();
@@ -192,8 +241,8 @@ function formatError(err) {
192
241
  }
193
242
 
194
243
  // src/platform/log.ts
195
- import fs3 from "fs";
196
- import path4 from "path";
244
+ import fs4 from "fs";
245
+ import path5 from "path";
197
246
  var PT_TIME_ZONE = "America/Los_Angeles";
198
247
  function pacificParts(date) {
199
248
  const parts = new Intl.DateTimeFormat("en-US", {
@@ -244,10 +293,10 @@ function pacificDateString(date) {
244
293
  return `${p7.year}-${p7.month}-${p7.day}`;
245
294
  }
246
295
  function logsDir() {
247
- return path4.join(configDir(), "logs");
296
+ return path5.join(configDir(), "logs");
248
297
  }
249
298
  function todayLogPath() {
250
- return path4.join(logsDir(), `${pacificDateString(/* @__PURE__ */ new Date())}.log`);
299
+ return path5.join(logsDir(), `${pacificDateString(/* @__PURE__ */ new Date())}.log`);
251
300
  }
252
301
  var logPrefix = "";
253
302
  function setLogPrefix(prefix) {
@@ -258,8 +307,8 @@ function appendLog(level, message) {
258
307
  const line = `[${pacificTimestamp(/* @__PURE__ */ new Date())}] [${level}]${tag} ${message}
259
308
  `;
260
309
  try {
261
- fs3.mkdirSync(logsDir(), { recursive: true, mode: 448 });
262
- fs3.appendFileSync(todayLogPath(), line);
310
+ fs4.mkdirSync(logsDir(), { recursive: true, mode: 448 });
311
+ fs4.appendFileSync(todayLogPath(), line);
263
312
  } catch {
264
313
  process.stderr.write(`hillclimb:${tag} ${level}: ${message}
265
314
  `);
@@ -547,9 +596,9 @@ var PlatformClient = class {
547
596
  };
548
597
 
549
598
  // src/platform/hooks.ts
550
- import fs4 from "fs";
599
+ import fs5 from "fs";
551
600
  import os2 from "os";
552
- import path5 from "path";
601
+ import path6 from "path";
553
602
  var HOOK_CMD = (sub) => `npx hillclimb@latest ${sub}`;
554
603
  var GIT_TRACES_CMD = (tool) => `npx hillclimb@latest git-traces --tool=${tool}`;
555
604
  var CLAUDE_SESSIONEND_UPLOAD_TIMEOUT_SECONDS = 30;
@@ -569,7 +618,7 @@ var TOOLS = [
569
618
  { eventName: "Stop", command: GIT_TRACES_CMD("claude") },
570
619
  { eventName: "SessionEnd", command: GIT_TRACES_CMD("claude") }
571
620
  ],
572
- detect: () => isDir(path5.join(os2.homedir(), ".claude"))
621
+ detect: () => isDir(path6.join(os2.homedir(), ".claude"))
573
622
  },
574
623
  {
575
624
  tool: "cursor",
@@ -582,7 +631,7 @@ var TOOLS = [
582
631
  { eventName: "stop", command: GIT_TRACES_CMD("cursor") },
583
632
  { eventName: "sessionEnd", command: GIT_TRACES_CMD("cursor") }
584
633
  ],
585
- detect: () => isDir(path5.join(os2.homedir(), ".cursor"))
634
+ detect: () => isDir(path6.join(os2.homedir(), ".cursor"))
586
635
  },
587
636
  {
588
637
  // opencode has no settings-file hook system (the `experimental.hook` block
@@ -597,7 +646,7 @@ var TOOLS = [
597
646
  format: "opencode",
598
647
  events: [],
599
648
  // Events are wired inside the plugin itself; see OPENCODE_PLUGIN_CONTENT.
600
- detect: () => isDir(path5.join(os2.homedir(), ".local", "share", "opencode")) || isDir(path5.join(os2.homedir(), ".config", "opencode"))
649
+ detect: () => isDir(path6.join(os2.homedir(), ".local", "share", "opencode")) || isDir(path6.join(os2.homedir(), ".config", "opencode"))
601
650
  },
602
651
  {
603
652
  // VS Code Copilot Chat. Same "no SessionEnd" constraint as Codex (the
@@ -637,23 +686,23 @@ var TOOLS = [
637
686
  { eventName: "SessionStart", command: GIT_TRACES_CMD("codex") },
638
687
  { eventName: "Stop", command: GIT_TRACES_CMD("codex") }
639
688
  ],
640
- detect: () => isDir(path5.join(os2.homedir(), ".codex"))
689
+ detect: () => isDir(path6.join(os2.homedir(), ".codex"))
641
690
  }
642
691
  ];
643
692
  function isDir(p7) {
644
693
  try {
645
- return fs4.statSync(p7).isDirectory();
694
+ return fs5.statSync(p7).isDirectory();
646
695
  } catch {
647
696
  return false;
648
697
  }
649
698
  }
650
699
  function copilotChatDetect() {
651
700
  const home = os2.homedir();
652
- const suffix = path5.join("User", "globalStorage", "github.copilot-chat");
701
+ const suffix = path6.join("User", "globalStorage", "github.copilot-chat");
653
702
  const candidates = [
654
- path5.join(home, "Library", "Application Support", "Code", suffix),
655
- path5.join(home, ".config", "Code", suffix),
656
- process.env.APPDATA ? path5.join(process.env.APPDATA, "Code", suffix) : null
703
+ path6.join(home, "Library", "Application Support", "Code", suffix),
704
+ path6.join(home, ".config", "Code", suffix),
705
+ process.env.APPDATA ? path6.join(process.env.APPDATA, "Code", suffix) : null
657
706
  ].filter((p7) => p7 !== null);
658
707
  return candidates.some(isDir);
659
708
  }
@@ -662,11 +711,11 @@ function ensureHooks(settings) {
662
711
  return settings.hooks;
663
712
  }
664
713
  function settingsPath(repoRoot, def) {
665
- return path5.join(repoRoot, def.settingsFile);
714
+ return path6.join(repoRoot, def.settingsFile);
666
715
  }
667
716
  async function readJson(file) {
668
717
  try {
669
- const raw = await fs4.promises.readFile(file, "utf-8");
718
+ const raw = await fs5.promises.readFile(file, "utf-8");
670
719
  const parsed = JSON.parse(raw);
671
720
  if (parsed && typeof parsed === "object") return parsed;
672
721
  return {};
@@ -676,8 +725,8 @@ async function readJson(file) {
676
725
  }
677
726
  }
678
727
  async function writeJson(file, obj) {
679
- await fs4.promises.mkdir(path5.dirname(file), { recursive: true });
680
- await fs4.promises.writeFile(file, `${JSON.stringify(obj, null, 2)}
728
+ await fs5.promises.mkdir(path6.dirname(file), { recursive: true });
729
+ await fs5.promises.writeFile(file, `${JSON.stringify(obj, null, 2)}
681
730
  `);
682
731
  }
683
732
  function claudeHookPresent(matchers, command, options = {}) {
@@ -951,19 +1000,19 @@ export default HillclimbPlugin;
951
1000
  `;
952
1001
  async function opencodeInstall(file) {
953
1002
  try {
954
- const existing = await fs4.promises.readFile(file, "utf-8");
1003
+ const existing = await fs5.promises.readFile(file, "utf-8");
955
1004
  if (existing === OPENCODE_PLUGIN_CONTENT) {
956
1005
  return { installed: 0, alreadyPresent: 1 };
957
1006
  }
958
1007
  } catch {
959
1008
  }
960
- await fs4.promises.mkdir(path5.dirname(file), { recursive: true });
961
- await fs4.promises.writeFile(file, OPENCODE_PLUGIN_CONTENT);
1009
+ await fs5.promises.mkdir(path6.dirname(file), { recursive: true });
1010
+ await fs5.promises.writeFile(file, OPENCODE_PLUGIN_CONTENT);
962
1011
  return { installed: 1, alreadyPresent: 0 };
963
1012
  }
964
1013
  async function opencodeCheck(file) {
965
1014
  try {
966
- const content = await fs4.promises.readFile(file, "utf-8");
1015
+ const content = await fs5.promises.readFile(file, "utf-8");
967
1016
  return content.includes(OPENCODE_PLUGIN_MARKER);
968
1017
  } catch {
969
1018
  return false;
@@ -1097,17 +1146,17 @@ async function checkAllHooks(repoRoot) {
1097
1146
  }
1098
1147
  return results;
1099
1148
  }
1100
- var CODEX_CONFIG_PATH = path5.join(os2.homedir(), ".codex", "config.toml");
1149
+ var CODEX_CONFIG_PATH = path6.join(os2.homedir(), ".codex", "config.toml");
1101
1150
  async function ensureCodexHooksEnabled() {
1102
1151
  let content;
1103
1152
  try {
1104
- content = await fs4.promises.readFile(CODEX_CONFIG_PATH, "utf-8");
1153
+ content = await fs5.promises.readFile(CODEX_CONFIG_PATH, "utf-8");
1105
1154
  } catch (err) {
1106
1155
  if (err.code === "ENOENT") {
1107
- await fs4.promises.mkdir(path5.dirname(CODEX_CONFIG_PATH), {
1156
+ await fs5.promises.mkdir(path6.dirname(CODEX_CONFIG_PATH), {
1108
1157
  recursive: true
1109
1158
  });
1110
- await fs4.promises.writeFile(
1159
+ await fs5.promises.writeFile(
1111
1160
  CODEX_CONFIG_PATH,
1112
1161
  "[features]\nhooks = true\n"
1113
1162
  );
@@ -1148,7 +1197,7 @@ hooks = true
1148
1197
  `;
1149
1198
  }
1150
1199
  if (content === original) return false;
1151
- await fs4.promises.writeFile(CODEX_CONFIG_PATH, content);
1200
+ await fs5.promises.writeFile(CODEX_CONFIG_PATH, content);
1152
1201
  return true;
1153
1202
  }
1154
1203
  var CLAUDE_DEF = TOOLS[0];
@@ -1571,6 +1620,29 @@ async function runInit(args = []) {
1571
1620
  p3.log.success(`Installed ${r.label} hook`);
1572
1621
  }
1573
1622
  }
1623
+ try {
1624
+ const ignored = await ensureGitignored(
1625
+ repoRoot,
1626
+ hookResults.map((r) => r.settingsFile)
1627
+ );
1628
+ if (ignored.changed) {
1629
+ appendLog(
1630
+ "info",
1631
+ `init: gitignored hook files (${ignored.added.join(",")})`
1632
+ );
1633
+ p3.log.success(
1634
+ `${ignored.created ? "Created" : "Updated"} .gitignore for hook files`
1635
+ );
1636
+ }
1637
+ } catch (err) {
1638
+ appendLog(
1639
+ "warn",
1640
+ `init: could not update .gitignore for hook files: ${formatError(err)}`
1641
+ );
1642
+ p3.log.warn(
1643
+ `Could not update .gitignore for hook files: ${err instanceof Error ? err.message : String(err)}`
1644
+ );
1645
+ }
1574
1646
  if (hookResults.some((r) => r.tool === "codex")) {
1575
1647
  try {
1576
1648
  const changed = await ensureCodexHooksEnabled();
@@ -1821,7 +1893,7 @@ async function runLogout(args = []) {
1821
1893
  }
1822
1894
 
1823
1895
  // src/commands/status.ts
1824
- import path6 from "path";
1896
+ import path7 from "path";
1825
1897
  async function runStatus(args = []) {
1826
1898
  const debug = args.includes("--debug");
1827
1899
  header("status");
@@ -1839,7 +1911,7 @@ async function runStatus(args = []) {
1839
1911
  return;
1840
1912
  }
1841
1913
  const { repoRoot, config } = match;
1842
- const repoName = repo?.name ?? path6.basename(repoRoot);
1914
+ const repoName = repo?.name ?? path7.basename(repoRoot);
1843
1915
  if (IS_TTY) {
1844
1916
  process.stdout.write(` ${dim("Checking login...")}`);
1845
1917
  }
@@ -1933,7 +2005,7 @@ async function runStatus(args = []) {
1933
2005
  try {
1934
2006
  const hookStatuses = await checkAllHooks(repoRoot);
1935
2007
  for (const h of hookStatuses) {
1936
- const rel = path6.relative(repoRoot, h.settingsFile) || h.settingsFile;
2008
+ const rel = path7.relative(repoRoot, h.settingsFile) || h.settingsFile;
1937
2009
  debugRow(
1938
2010
  `${h.label} hook:`,
1939
2011
  `${rel} ${dim(`(${h.installed ? "installed" : "missing"})`)}`
@@ -1950,20 +2022,20 @@ async function runStatus(args = []) {
1950
2022
 
1951
2023
  // src/commands/upload.ts
1952
2024
  import { spawn as spawn2 } from "child_process";
1953
- import fs8 from "fs";
2025
+ import fs9 from "fs";
1954
2026
  import os5 from "os";
1955
- import path11 from "path";
2027
+ import path12 from "path";
1956
2028
 
1957
2029
  // src/middleware/pattern-redact.ts
1958
2030
  import os3 from "os";
1959
2031
  import { Worker } from "worker_threads";
1960
2032
 
1961
2033
  // src/middleware/file-utils.ts
1962
- import fs5 from "fs";
2034
+ import fs6 from "fs";
1963
2035
  async function checkBinary(filePath) {
1964
2036
  let handle = null;
1965
2037
  try {
1966
- handle = await fs5.promises.open(filePath, "r");
2038
+ handle = await fs6.promises.open(filePath, "r");
1967
2039
  const buf = Buffer.alloc(8192);
1968
2040
  const { bytesRead } = await handle.read(buf, 0, 8192, 0);
1969
2041
  for (let i = 0; i < bytesRead; i++) {
@@ -1988,7 +2060,7 @@ async function readFileContent(file) {
1988
2060
  return { kind: "binary" };
1989
2061
  }
1990
2062
  try {
1991
- const content = await fs5.promises.readFile(file.absolutePath, "utf-8");
2063
+ const content = await fs6.promises.readFile(file.absolutePath, "utf-8");
1992
2064
  return { kind: "text", content };
1993
2065
  } catch {
1994
2066
  return { kind: "error" };
@@ -10440,8 +10512,8 @@ var RedactMiddleware = class {
10440
10512
  var middleware = [];
10441
10513
 
10442
10514
  // src/middleware/secrets.ts
10443
- import fs6 from "fs";
10444
- import path7 from "path";
10515
+ import fs7 from "fs";
10516
+ import path8 from "path";
10445
10517
  var KNOWN_NON_SECRETS = /* @__PURE__ */ new Set([
10446
10518
  "true",
10447
10519
  "false",
@@ -10521,7 +10593,7 @@ async function parseEnvFile(filePath) {
10521
10593
  const values = [];
10522
10594
  let content;
10523
10595
  try {
10524
- content = await fs6.promises.readFile(filePath, "utf-8");
10596
+ content = await fs7.promises.readFile(filePath, "utf-8");
10525
10597
  } catch {
10526
10598
  return values;
10527
10599
  }
@@ -10589,16 +10661,16 @@ function addWithVariants(set, value) {
10589
10661
  async function discoverEnvFiles(repoRoot) {
10590
10662
  let entries;
10591
10663
  try {
10592
- entries = await fs6.promises.readdir(repoRoot);
10664
+ entries = await fs7.promises.readdir(repoRoot);
10593
10665
  } catch {
10594
10666
  return [];
10595
10667
  }
10596
10668
  const envFiles = [];
10597
10669
  for (const name of entries) {
10598
10670
  if (!name.startsWith(".env")) continue;
10599
- const filePath = path7.join(repoRoot, name);
10671
+ const filePath = path8.join(repoRoot, name);
10600
10672
  try {
10601
- const stat = await fs6.promises.stat(filePath);
10673
+ const stat = await fs7.promises.stat(filePath);
10602
10674
  if (stat.isFile()) envFiles.push(name);
10603
10675
  } catch {
10604
10676
  }
@@ -10621,7 +10693,7 @@ async function collectSecrets(repoRoot, envFiles, additionalFiles) {
10621
10693
  }
10622
10694
  }
10623
10695
  for (const filePath of additionalFiles) {
10624
- const resolved = path7.resolve(repoRoot, filePath);
10696
+ const resolved = path8.resolve(repoRoot, filePath);
10625
10697
  sourceFiles.push(resolved);
10626
10698
  for (const value of await parseEnvFile(resolved)) {
10627
10699
  if (isUsableValue(value)) {
@@ -10646,7 +10718,7 @@ async function collectSecrets(repoRoot, envFiles, additionalFiles) {
10646
10718
  }
10647
10719
 
10648
10720
  // src/normalizer/index.ts
10649
- import path8 from "path";
10721
+ import path9 from "path";
10650
10722
 
10651
10723
  // src/normalizer/claude.ts
10652
10724
  function stringify(value) {
@@ -11514,7 +11586,7 @@ var NormalizeMiddleware = class {
11514
11586
  if (!["claude", "codex", "cursor"].includes(file.sourceName)) continue;
11515
11587
  const content = file.content ? file.content.toString("utf-8") : null;
11516
11588
  if (!content) continue;
11517
- const sessionId = file.metadata?.sessionId ?? path8.basename(file.absolutePath, ".jsonl");
11589
+ const sessionId = file.metadata?.sessionId ?? path9.basename(file.absolutePath, ".jsonl");
11518
11590
  try {
11519
11591
  const trajectory = normalizeContent(
11520
11592
  file.sourceName,
@@ -11548,14 +11620,14 @@ import archiver from "archiver";
11548
11620
 
11549
11621
  // src/outputs/archive.ts
11550
11622
  import os4 from "os";
11551
- import path9 from "path";
11623
+ import path10 from "path";
11552
11624
  function getSourceBaseDir(sourceName) {
11553
11625
  const home = os4.homedir();
11554
11626
  switch (sourceName) {
11555
11627
  case "claude":
11556
- return path9.join(home, ".claude", "projects");
11628
+ return path10.join(home, ".claude", "projects");
11557
11629
  case "codex":
11558
- return path9.join(home, ".codex", "sessions");
11630
+ return path10.join(home, ".codex", "sessions");
11559
11631
  default:
11560
11632
  return home;
11561
11633
  }
@@ -11564,8 +11636,8 @@ function addGroupToArchive(archive, group, selectedSources) {
11564
11636
  for (const file of group.files) {
11565
11637
  if (!selectedSources.has(file.sourceName)) continue;
11566
11638
  const baseDir = getSourceBaseDir(file.sourceName);
11567
- const relativePath = file.absolutePath.startsWith(baseDir) ? path9.relative(baseDir, file.absolutePath) : path9.basename(file.absolutePath);
11568
- const archivePath = path9.join(file.sourceName, relativePath);
11639
+ const relativePath = file.absolutePath.startsWith(baseDir) ? path10.relative(baseDir, file.absolutePath) : path10.basename(file.absolutePath);
11640
+ const archivePath = path10.join(file.sourceName, relativePath);
11569
11641
  if (file.content) {
11570
11642
  archive.append(file.content, { name: archivePath });
11571
11643
  } else {
@@ -11639,21 +11711,21 @@ var PlatformUploadOutput = class {
11639
11711
  };
11640
11712
 
11641
11713
  // src/pipeline.ts
11642
- import fs7 from "fs";
11643
- import path10 from "path";
11714
+ import fs8 from "fs";
11715
+ import path11 from "path";
11644
11716
  function canonicalizePath(p7) {
11645
- let resolved = path10.resolve(p7);
11646
- if (resolved.endsWith(path10.sep) && resolved !== path10.sep) {
11717
+ let resolved = path11.resolve(p7);
11718
+ if (resolved.endsWith(path11.sep) && resolved !== path11.sep) {
11647
11719
  resolved = resolved.slice(0, -1);
11648
11720
  }
11649
11721
  return resolved;
11650
11722
  }
11651
11723
  function computeLabel(repoPath, allPaths) {
11652
- const segments = repoPath.split(path10.sep).filter(Boolean);
11724
+ const segments = repoPath.split(path11.sep).filter(Boolean);
11653
11725
  for (let depth = 1; depth <= segments.length; depth++) {
11654
11726
  const label = segments.slice(-depth).join("/");
11655
11727
  const matches = allPaths.filter((p7) => {
11656
- const s = p7.split(path10.sep).filter(Boolean);
11728
+ const s = p7.split(path11.sep).filter(Boolean);
11657
11729
  return s.slice(-depth).join("/") === label;
11658
11730
  });
11659
11731
  if (matches.length === 1) return label;
@@ -11675,7 +11747,7 @@ async function mergeByRepo(files) {
11675
11747
  const groups = [];
11676
11748
  for (const [repoPath, groupFiles] of grouped) {
11677
11749
  const stats = await Promise.all(
11678
- groupFiles.map((f) => fs7.promises.stat(f.absolutePath).catch(() => null))
11750
+ groupFiles.map((f) => fs8.promises.stat(f.absolutePath).catch(() => null))
11679
11751
  );
11680
11752
  let lastModified = /* @__PURE__ */ new Date(0);
11681
11753
  for (const stat of stats) {
@@ -11698,7 +11770,7 @@ async function preloadFiles(group) {
11698
11770
  group.files.map(async (file) => {
11699
11771
  if (file.content) return file;
11700
11772
  try {
11701
- const buf = await fs7.promises.readFile(file.absolutePath);
11773
+ const buf = await fs8.promises.readFile(file.absolutePath);
11702
11774
  const checkLen = Math.min(buf.length, 8192);
11703
11775
  for (let i = 0; i < checkLen; i++) {
11704
11776
  if (buf[i] === 0) {
@@ -11743,7 +11815,7 @@ function lineHasAssistant(line) {
11743
11815
  return line.includes('"type":"assistant"') || line.includes('"role":"assistant"') || line.includes('"type":"assistant.');
11744
11816
  }
11745
11817
  async function hasAssistantMessage(transcriptPath) {
11746
- const stream = fs8.createReadStream(transcriptPath, { encoding: "utf-8" });
11818
+ const stream = fs9.createReadStream(transcriptPath, { encoding: "utf-8" });
11747
11819
  let buffer = "";
11748
11820
  try {
11749
11821
  for await (const chunk of stream) {
@@ -11776,7 +11848,7 @@ function resolveCursorTranscriptPath(payload) {
11776
11848
  const workspace = payload.workspace_roots?.[0];
11777
11849
  if (!id || !workspace) return void 0;
11778
11850
  const encoded = workspace.replace(/^\//, "").replace(/\//g, "-");
11779
- return path11.join(
11851
+ return path12.join(
11780
11852
  os5.homedir(),
11781
11853
  ".cursor",
11782
11854
  "projects",
@@ -11802,9 +11874,9 @@ async function runUploadInner(payload) {
11802
11874
  );
11803
11875
  return;
11804
11876
  }
11805
- const transcriptResolved = path11.resolve(transcriptPath);
11877
+ const transcriptResolved = path12.resolve(transcriptPath);
11806
11878
  try {
11807
- const stat = await fs8.promises.stat(transcriptResolved);
11879
+ const stat = await fs9.promises.stat(transcriptResolved);
11808
11880
  if (!stat.isFile()) {
11809
11881
  appendLog(
11810
11882
  "warn",
@@ -11853,13 +11925,13 @@ async function uploadSession(args) {
11853
11925
  };
11854
11926
  const group = {
11855
11927
  repoPath: repoRoot,
11856
- label: path11.basename(repoRoot),
11928
+ label: path12.basename(repoRoot),
11857
11929
  files: [sourceFile],
11858
11930
  sourceNames: [sourceTool],
11859
11931
  lastModified: now
11860
11932
  };
11861
11933
  const envFileNames = await discoverEnvFiles(repoRoot);
11862
- const envFilePaths = envFileNames.map((n) => path11.join(repoRoot, n));
11934
+ const envFilePaths = envFileNames.map((n) => path12.join(repoRoot, n));
11863
11935
  const secretResult = await collectSecrets(repoRoot, envFilePaths, []);
11864
11936
  const mwChain = [];
11865
11937
  if (secretResult.values.size > 0) {
@@ -12039,9 +12111,9 @@ import { execFileSync as execFileSync2 } from "child_process";
12039
12111
 
12040
12112
  // src/git-traces/git-ops.ts
12041
12113
  import { execFileSync } from "child_process";
12042
- import fs9 from "fs";
12114
+ import fs10 from "fs";
12043
12115
  import os6 from "os";
12044
- import path12 from "path";
12116
+ import path13 from "path";
12045
12117
  import { gzipSync } from "zlib";
12046
12118
  var GIT_COMMAND_TIMEOUT_MS = 12e4;
12047
12119
  var EXEC_OPTS = {
@@ -12165,7 +12237,7 @@ function buildUntrackedTree(repoRoot) {
12165
12237
  for (const relPath of list.split("\0")) {
12166
12238
  if (!relPath) continue;
12167
12239
  try {
12168
- const stat = fs9.lstatSync(path12.join(repoRoot, relPath));
12240
+ const stat = fs10.lstatSync(path13.join(repoRoot, relPath));
12169
12241
  if (stat.size > MAX_UNTRACKED_FILE_BYTES) {
12170
12242
  appendLog(
12171
12243
  "warn",
@@ -12178,7 +12250,7 @@ function buildUntrackedTree(repoRoot) {
12178
12250
  }
12179
12251
  }
12180
12252
  if (kept.length === 0) return null;
12181
- const tmpIndex = path12.join(
12253
+ const tmpIndex = path13.join(
12182
12254
  os6.tmpdir(),
12183
12255
  `hillclimb-untracked-${Date.now()}-${process.pid}`
12184
12256
  );
@@ -12191,7 +12263,7 @@ function buildUntrackedTree(repoRoot) {
12191
12263
  return gitWithEnv(repoRoot, ["write-tree"], env);
12192
12264
  } finally {
12193
12265
  try {
12194
- fs9.unlinkSync(tmpIndex);
12266
+ fs10.unlinkSync(tmpIndex);
12195
12267
  } catch {
12196
12268
  }
12197
12269
  }
@@ -12202,7 +12274,7 @@ function buildSnapshotTree(repoRoot, stashSha) {
12202
12274
  if (!untrackedTree || untrackedTree === EMPTY_TREE_SHA) {
12203
12275
  return trackedTree;
12204
12276
  }
12205
- const tmpIndex = path12.join(
12277
+ const tmpIndex = path13.join(
12206
12278
  os6.tmpdir(),
12207
12279
  `hillclimb-index-${Date.now()}-${process.pid}`
12208
12280
  );
@@ -12230,7 +12302,7 @@ function buildSnapshotTree(repoRoot, stashSha) {
12230
12302
  return gitWithEnv(repoRoot, ["write-tree"], env);
12231
12303
  } finally {
12232
12304
  try {
12233
- fs9.unlinkSync(tmpIndex);
12305
+ fs10.unlinkSync(tmpIndex);
12234
12306
  } catch {
12235
12307
  }
12236
12308
  }
@@ -12244,16 +12316,16 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
12244
12316
  ]);
12245
12317
  const orphanRef = `refs/hillclimb/bundle/${sessionId}`;
12246
12318
  pinRef(repoRoot, orphanRef, orphanCommit);
12247
- const tmpFile = path12.join(
12319
+ const tmpFile = path13.join(
12248
12320
  os6.tmpdir(),
12249
12321
  `hillclimb-bundle-${Date.now()}.bundle`
12250
12322
  );
12251
12323
  try {
12252
12324
  git(repoRoot, ["bundle", "create", tmpFile, orphanRef]);
12253
- return fs9.readFileSync(tmpFile);
12325
+ return fs10.readFileSync(tmpFile);
12254
12326
  } finally {
12255
12327
  try {
12256
- fs9.unlinkSync(tmpFile);
12328
+ fs10.unlinkSync(tmpFile);
12257
12329
  } catch {
12258
12330
  }
12259
12331
  deleteRef(repoRoot, orphanRef);
@@ -12406,9 +12478,9 @@ function parseCommitFiles(repoRoot, sha) {
12406
12478
  oldPath
12407
12479
  });
12408
12480
  } else {
12409
- const path20 = parts[parts.length - 1];
12410
- indexByPath.set(path20, files.length);
12411
- files.push({ path: path20, status, additions: 0, deletions: 0 });
12481
+ const path21 = parts[parts.length - 1];
12482
+ indexByPath.set(path21, files.length);
12483
+ files.push({ path: path21, status, additions: 0, deletions: 0 });
12412
12484
  }
12413
12485
  }
12414
12486
  for (const line of numstat.split("\n")) {
@@ -12462,21 +12534,21 @@ function cleanupSessionRefs(repoRoot, sessionId) {
12462
12534
 
12463
12535
  // src/git-traces/session-state.ts
12464
12536
  import crypto from "crypto";
12465
- import fs10 from "fs";
12537
+ import fs11 from "fs";
12466
12538
  import os7 from "os";
12467
- import path13 from "path";
12539
+ import path14 from "path";
12468
12540
  var CURRENT_SCHEMA_VERSION = 3;
12469
- var STATE_DIR = path13.join(os7.homedir(), ".hillclimb", "git-traces");
12541
+ var STATE_DIR = path14.join(os7.homedir(), ".hillclimb", "git-traces");
12470
12542
  function stateFileForRepo(repoRoot, tool) {
12471
- const hash = crypto.createHash("sha256").update(`${path13.resolve(repoRoot)}\0${tool}`).digest("hex").slice(0, 16);
12472
- return path13.join(STATE_DIR, `${hash}.json`);
12543
+ const hash = crypto.createHash("sha256").update(`${path14.resolve(repoRoot)}\0${tool}`).digest("hex").slice(0, 16);
12544
+ return path14.join(STATE_DIR, `${hash}.json`);
12473
12545
  }
12474
12546
  function lockFileForRepo(repoRoot, tool) {
12475
12547
  return `${stateFileForRepo(repoRoot, tool)}.lock`;
12476
12548
  }
12477
12549
  async function readSessionState(repoRoot, tool) {
12478
12550
  try {
12479
- const raw = await fs10.promises.readFile(
12551
+ const raw = await fs11.promises.readFile(
12480
12552
  stateFileForRepo(repoRoot, tool),
12481
12553
  "utf-8"
12482
12554
  );
@@ -12491,27 +12563,27 @@ async function readSessionState(repoRoot, tool) {
12491
12563
  }
12492
12564
  async function writeSessionState(state, tool) {
12493
12565
  const file = stateFileForRepo(state.repoRoot, tool);
12494
- await fs10.promises.mkdir(STATE_DIR, { recursive: true, mode: 448 });
12566
+ await fs11.promises.mkdir(STATE_DIR, { recursive: true, mode: 448 });
12495
12567
  const tmp = `${file}.tmp`;
12496
- await fs10.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
12568
+ await fs11.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
12497
12569
  mode: 384
12498
12570
  });
12499
- await fs10.promises.rename(tmp, file);
12571
+ await fs11.promises.rename(tmp, file);
12500
12572
  }
12501
12573
  async function deleteSessionState(repoRoot, tool) {
12502
12574
  try {
12503
- await fs10.promises.unlink(stateFileForRepo(repoRoot, tool));
12575
+ await fs11.promises.unlink(stateFileForRepo(repoRoot, tool));
12504
12576
  } catch {
12505
12577
  }
12506
12578
  }
12507
12579
  async function acquireLock(repoRoot, tool, retries = 3, delayMs = 200) {
12508
12580
  const lockPath = lockFileForRepo(repoRoot, tool);
12509
- await fs10.promises.mkdir(STATE_DIR, { recursive: true, mode: 448 });
12581
+ await fs11.promises.mkdir(STATE_DIR, { recursive: true, mode: 448 });
12510
12582
  for (let i = 0; i < retries; i++) {
12511
12583
  try {
12512
- const fd = await fs10.promises.open(
12584
+ const fd = await fs11.promises.open(
12513
12585
  lockPath,
12514
- fs10.constants.O_CREAT | fs10.constants.O_EXCL | fs10.constants.O_WRONLY
12586
+ fs11.constants.O_CREAT | fs11.constants.O_EXCL | fs11.constants.O_WRONLY
12515
12587
  );
12516
12588
  await fd.write(String(process.pid));
12517
12589
  await fd.close();
@@ -12528,7 +12600,7 @@ async function acquireLock(repoRoot, tool, retries = 3, delayMs = 200) {
12528
12600
  }
12529
12601
  async function releaseLock(repoRoot, tool) {
12530
12602
  try {
12531
- await fs10.promises.unlink(lockFileForRepo(repoRoot, tool));
12603
+ await fs11.promises.unlink(lockFileForRepo(repoRoot, tool));
12532
12604
  } catch {
12533
12605
  }
12534
12606
  }
@@ -12574,14 +12646,16 @@ function execGit(cwd, args) {
12574
12646
  maxBuffer: 50 * 1024 * 1024
12575
12647
  }).toString("utf-8").trim();
12576
12648
  }
12649
+ function canUploadFile(filename, buffer) {
12650
+ if (buffer.byteLength <= MAX_UPLOAD_BYTES) return true;
12651
+ appendLog(
12652
+ "warn",
12653
+ `git-traces: skipping ${filename} (${buffer.byteLength} bytes > ${MAX_UPLOAD_BYTES} limit)`
12654
+ );
12655
+ return false;
12656
+ }
12577
12657
  async function uploadFile(client, contributionId, filename, mimeType, buffer) {
12578
- if (buffer.byteLength > MAX_UPLOAD_BYTES) {
12579
- appendLog(
12580
- "warn",
12581
- `git-traces: skipping ${filename} (${buffer.byteLength} bytes > ${MAX_UPLOAD_BYTES} limit)`
12582
- );
12583
- return;
12584
- }
12658
+ if (!canUploadFile(filename, buffer)) return false;
12585
12659
  const presigned = await client.createUpload(contributionId, {
12586
12660
  originalFilename: filename,
12587
12661
  mimeType,
@@ -12596,6 +12670,11 @@ async function uploadFile(client, contributionId, filename, mimeType, buffer) {
12596
12670
  "info",
12597
12671
  `git-traces: uploaded ${filename} (${buffer.byteLength} bytes)`
12598
12672
  );
12673
+ return true;
12674
+ }
12675
+ function canUploadEpochBaselineArtifacts(epoch, artifacts) {
12676
+ const prefix = epochPrefix(epoch);
12677
+ return canUploadFile(`${prefix}-baseline.bundle`, artifacts.bundleBuffer) && canUploadFile(`${prefix}-baseline.json`, artifacts.metadataBuffer);
12599
12678
  }
12600
12679
  function pinEpochBaseline(repoRoot, sessionId, epoch) {
12601
12680
  const baselineSha = captureBaselineSha(repoRoot);
@@ -12650,30 +12729,34 @@ function buildEpochBaselineArtifacts(params) {
12650
12729
  async function uploadEpochBaselineArtifacts(params) {
12651
12730
  const { client, contributionId, epoch, artifacts } = params;
12652
12731
  const prefix = epochPrefix(epoch);
12653
- await uploadFile(
12654
- client,
12655
- contributionId,
12656
- `${prefix}-baseline.json`,
12657
- "application/json",
12658
- artifacts.metadataBuffer
12659
- );
12660
- await uploadFile(
12732
+ if (!canUploadEpochBaselineArtifacts(epoch, artifacts)) return false;
12733
+ const bundleUploaded = await uploadFile(
12661
12734
  client,
12662
12735
  contributionId,
12663
12736
  `${prefix}-baseline.bundle`,
12664
12737
  "application/x-git-bundle",
12665
12738
  artifacts.bundleBuffer
12666
12739
  );
12740
+ if (!bundleUploaded) return false;
12741
+ return uploadFile(
12742
+ client,
12743
+ contributionId,
12744
+ `${prefix}-baseline.json`,
12745
+ "application/json",
12746
+ artifacts.metadataBuffer
12747
+ );
12667
12748
  }
12668
12749
  async function uploadEpochBaseline(params) {
12669
12750
  const artifacts = buildEpochBaselineArtifacts(params);
12670
12751
  if (!artifacts) return null;
12671
- await uploadEpochBaselineArtifacts({
12752
+ if (!canUploadEpochBaselineArtifacts(params.epoch, artifacts)) return null;
12753
+ const uploaded = await uploadEpochBaselineArtifacts({
12672
12754
  client: params.client,
12673
12755
  contributionId: params.contributionId,
12674
12756
  epoch: params.epoch,
12675
12757
  artifacts
12676
12758
  });
12759
+ if (!uploaded) return null;
12677
12760
  return { baselineTreeSha: artifacts.baselineTreeSha };
12678
12761
  }
12679
12762
  async function openEpoch(params) {
@@ -12783,6 +12866,7 @@ async function handleStop(payload, tool) {
12783
12866
  startedAt: state.startedAt
12784
12867
  });
12785
12868
  if (!artifacts) return;
12869
+ if (!canUploadEpochBaselineArtifacts(1, artifacts)) return;
12786
12870
  const toolLabel = TOOL_LABELS[tool] ?? "Claude";
12787
12871
  const now = /* @__PURE__ */ new Date();
12788
12872
  const epochSeconds = formatEpochSeconds2(now);
@@ -12798,12 +12882,13 @@ Repo: ${cwd}
12798
12882
  Uploaded: ${now.toISOString()}`
12799
12883
  }
12800
12884
  );
12801
- await uploadEpochBaselineArtifacts({
12885
+ const uploaded = await uploadEpochBaselineArtifacts({
12802
12886
  client,
12803
12887
  contributionId: contribution.id,
12804
12888
  epoch: 1,
12805
12889
  artifacts
12806
12890
  });
12891
+ if (!uploaded) return;
12807
12892
  await client.submitContribution(contribution.id);
12808
12893
  state.contributionId = contribution.id;
12809
12894
  state.baselineTreeSha = artifacts.baselineTreeSha;
@@ -13100,15 +13185,15 @@ ${stack}` : ""}`
13100
13185
  }
13101
13186
 
13102
13187
  // src/outputs/zip.ts
13103
- import fs12 from "fs";
13104
- import path15 from "path";
13188
+ import fs13 from "fs";
13189
+ import path16 from "path";
13105
13190
  import archiver2 from "archiver";
13106
13191
 
13107
13192
  // src/outputs/downloads.ts
13108
13193
  import { execSync as execSync2 } from "child_process";
13109
- import fs11 from "fs";
13194
+ import fs12 from "fs";
13110
13195
  import os8 from "os";
13111
- import path14 from "path";
13196
+ import path15 from "path";
13112
13197
  function getDownloadsFolder() {
13113
13198
  const home = os8.homedir();
13114
13199
  if (process.platform === "linux") {
@@ -13117,12 +13202,12 @@ function getDownloadsFolder() {
13117
13202
  encoding: "utf-8",
13118
13203
  timeout: 3e3
13119
13204
  }).trim();
13120
- if (xdgDir && fs11.existsSync(xdgDir)) return xdgDir;
13205
+ if (xdgDir && fs12.existsSync(xdgDir)) return xdgDir;
13121
13206
  } catch {
13122
13207
  }
13123
13208
  }
13124
- const downloads = path14.join(home, "Downloads");
13125
- if (fs11.existsSync(downloads)) return downloads;
13209
+ const downloads = path15.join(home, "Downloads");
13210
+ if (fs12.existsSync(downloads)) return downloads;
13126
13211
  return home;
13127
13212
  }
13128
13213
 
@@ -13131,11 +13216,11 @@ function sanitizeFilename(name) {
13131
13216
  return name.replace(/[^a-zA-Z0-9._-]/g, "_");
13132
13217
  }
13133
13218
  function getUniqueFilename(dir, base, ext) {
13134
- let candidate = path15.join(dir, `${base}${ext}`);
13135
- if (!fs12.existsSync(candidate)) return candidate;
13219
+ let candidate = path16.join(dir, `${base}${ext}`);
13220
+ if (!fs13.existsSync(candidate)) return candidate;
13136
13221
  let i = 1;
13137
- while (fs12.existsSync(candidate)) {
13138
- candidate = path15.join(dir, `${base}-${i}${ext}`);
13222
+ while (fs13.existsSync(candidate)) {
13223
+ candidate = path16.join(dir, `${base}-${i}${ext}`);
13139
13224
  i++;
13140
13225
  }
13141
13226
  return candidate;
@@ -13145,13 +13230,13 @@ var ZipOutput = class {
13145
13230
  label = "Save as .zip to Downloads";
13146
13231
  async emit(group, options) {
13147
13232
  const downloadsDir = getDownloadsFolder();
13148
- const repoName = sanitizeFilename(path15.basename(group.repoPath));
13233
+ const repoName = sanitizeFilename(path16.basename(group.repoPath));
13149
13234
  const timeRange = options.timeRange;
13150
13235
  const rangePart = timeRange?.label ?? "all";
13151
13236
  const epochSeconds = Math.floor(Date.now() / 1e3);
13152
13237
  const baseName = `logs-${repoName}-${rangePart}-${epochSeconds}`;
13153
13238
  const outputPath = getUniqueFilename(downloadsDir, baseName, ".zip");
13154
- const output = fs12.createWriteStream(outputPath);
13239
+ const output = fs13.createWriteStream(outputPath);
13155
13240
  const archive = archiver2("zip", { zlib: { level: 6 } });
13156
13241
  const done = new Promise((resolve, reject) => {
13157
13242
  output.on("close", resolve);
@@ -13345,15 +13430,15 @@ async function confirmExport(group, output) {
13345
13430
  }
13346
13431
 
13347
13432
  // src/sources/claude.ts
13348
- import fs13 from "fs";
13433
+ import fs14 from "fs";
13349
13434
  import os9 from "os";
13350
- import path16 from "path";
13435
+ import path17 from "path";
13351
13436
  import readline from "readline";
13352
13437
  var SKIP_DIRS = /* @__PURE__ */ new Set(["tool-results"]);
13353
13438
  async function resolveRepoPath(projectDir) {
13354
- const indexPath = path16.join(projectDir, "sessions-index.json");
13439
+ const indexPath = path17.join(projectDir, "sessions-index.json");
13355
13440
  try {
13356
- const raw = await fs13.promises.readFile(indexPath, "utf-8");
13441
+ const raw = await fs14.promises.readFile(indexPath, "utf-8");
13357
13442
  const data = JSON.parse(raw);
13358
13443
  if (data.originalPath && typeof data.originalPath === "string") {
13359
13444
  return data.originalPath;
@@ -13361,12 +13446,12 @@ async function resolveRepoPath(projectDir) {
13361
13446
  } catch {
13362
13447
  }
13363
13448
  const cwdCounts = /* @__PURE__ */ new Map();
13364
- const entries = await fs13.promises.readdir(projectDir, {
13449
+ const entries = await fs14.promises.readdir(projectDir, {
13365
13450
  withFileTypes: true
13366
13451
  });
13367
13452
  for (const entry of entries) {
13368
13453
  if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
13369
- const cwd = await extractCwdFromJsonl(path16.join(projectDir, entry.name));
13454
+ const cwd = await extractCwdFromJsonl(path17.join(projectDir, entry.name));
13370
13455
  if (cwd) {
13371
13456
  cwdCounts.set(cwd, (cwdCounts.get(cwd) ?? 0) + 1);
13372
13457
  }
@@ -13385,7 +13470,7 @@ async function resolveRepoPath(projectDir) {
13385
13470
  return null;
13386
13471
  }
13387
13472
  async function extractCwdFromJsonl(filePath) {
13388
- const stream = fs13.createReadStream(filePath, { encoding: "utf-8" });
13473
+ const stream = fs14.createReadStream(filePath, { encoding: "utf-8" });
13389
13474
  const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
13390
13475
  try {
13391
13476
  for await (const line of rl) {
@@ -13407,12 +13492,12 @@ async function extractCwdFromJsonl(filePath) {
13407
13492
  async function collectFiles(dir, baseDir, sourceName, repoPath, results) {
13408
13493
  let entries;
13409
13494
  try {
13410
- entries = await fs13.promises.readdir(dir, { withFileTypes: true });
13495
+ entries = await fs14.promises.readdir(dir, { withFileTypes: true });
13411
13496
  } catch {
13412
13497
  return;
13413
13498
  }
13414
13499
  for (const entry of entries) {
13415
- const fullPath = path16.join(dir, entry.name);
13500
+ const fullPath = path17.join(dir, entry.name);
13416
13501
  if (entry.isDirectory()) {
13417
13502
  if (SKIP_DIRS.has(entry.name)) continue;
13418
13503
  await collectFiles(fullPath, baseDir, sourceName, repoPath, results);
@@ -13434,19 +13519,19 @@ function fallbackDecode(encodedName) {
13434
13519
  var ClaudeSource = class {
13435
13520
  name = "claude";
13436
13521
  async scan() {
13437
- const baseDir = path16.join(os9.homedir(), ".claude", "projects");
13522
+ const baseDir = path17.join(os9.homedir(), ".claude", "projects");
13438
13523
  try {
13439
- await fs13.promises.access(baseDir);
13524
+ await fs14.promises.access(baseDir);
13440
13525
  } catch {
13441
13526
  return [];
13442
13527
  }
13443
- const projectDirs = await fs13.promises.readdir(baseDir, {
13528
+ const projectDirs = await fs14.promises.readdir(baseDir, {
13444
13529
  withFileTypes: true
13445
13530
  });
13446
13531
  const dirEntries = projectDirs.filter((d) => d.isDirectory());
13447
13532
  const resultArrays = await Promise.all(
13448
13533
  dirEntries.map(async (dir) => {
13449
- const projectPath = path16.join(baseDir, dir.name);
13534
+ const projectPath = path17.join(baseDir, dir.name);
13450
13535
  const repoPath = await resolveRepoPath(projectPath) ?? fallbackDecode(dir.name);
13451
13536
  const files = [];
13452
13537
  await collectFiles(
@@ -13464,12 +13549,12 @@ var ClaudeSource = class {
13464
13549
  };
13465
13550
 
13466
13551
  // src/sources/codex.ts
13467
- import fs14 from "fs";
13552
+ import fs15 from "fs";
13468
13553
  import os10 from "os";
13469
- import path17 from "path";
13554
+ import path18 from "path";
13470
13555
  import readline2 from "readline";
13471
13556
  async function parseSessionMeta(filePath) {
13472
- const stream = fs14.createReadStream(filePath, { encoding: "utf-8" });
13557
+ const stream = fs15.createReadStream(filePath, { encoding: "utf-8" });
13473
13558
  const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
13474
13559
  try {
13475
13560
  for await (const line of rl) {
@@ -13494,12 +13579,12 @@ async function findJsonlFiles(dir) {
13494
13579
  async function walk(d) {
13495
13580
  let entries;
13496
13581
  try {
13497
- entries = await fs14.promises.readdir(d, { withFileTypes: true });
13582
+ entries = await fs15.promises.readdir(d, { withFileTypes: true });
13498
13583
  } catch {
13499
13584
  return;
13500
13585
  }
13501
13586
  for (const entry of entries) {
13502
- const full = path17.join(d, entry.name);
13587
+ const full = path18.join(d, entry.name);
13503
13588
  if (entry.isDirectory()) {
13504
13589
  await walk(full);
13505
13590
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -13513,11 +13598,11 @@ async function findJsonlFiles(dir) {
13513
13598
  async function loadHistory(historyPath) {
13514
13599
  const map = /* @__PURE__ */ new Map();
13515
13600
  try {
13516
- await fs14.promises.access(historyPath);
13601
+ await fs15.promises.access(historyPath);
13517
13602
  } catch {
13518
13603
  return map;
13519
13604
  }
13520
- const stream = fs14.createReadStream(historyPath, { encoding: "utf-8" });
13605
+ const stream = fs15.createReadStream(historyPath, { encoding: "utf-8" });
13521
13606
  const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
13522
13607
  try {
13523
13608
  for await (const line of rl) {
@@ -13544,14 +13629,14 @@ async function loadHistory(historyPath) {
13544
13629
  var CodexSource = class {
13545
13630
  name = "codex";
13546
13631
  async scan() {
13547
- const codexDir = path17.join(os10.homedir(), ".codex");
13548
- const sessionsDir = path17.join(codexDir, "sessions");
13632
+ const codexDir = path18.join(os10.homedir(), ".codex");
13633
+ const sessionsDir = path18.join(codexDir, "sessions");
13549
13634
  try {
13550
- await fs14.promises.access(sessionsDir);
13635
+ await fs15.promises.access(sessionsDir);
13551
13636
  } catch {
13552
13637
  return [];
13553
13638
  }
13554
- const historyPath = path17.join(codexDir, "history.jsonl");
13639
+ const historyPath = path18.join(codexDir, "history.jsonl");
13555
13640
  const [jsonlFiles, historyMap] = await Promise.all([
13556
13641
  findJsonlFiles(sessionsDir),
13557
13642
  loadHistory(historyPath)
@@ -13574,8 +13659,8 @@ var CodexSource = class {
13574
13659
  });
13575
13660
  const historyLines = historyMap.get(meta.sessionId);
13576
13661
  if (historyLines) {
13577
- const sessionDir = path17.relative(sessionsDir, path17.dirname(filePath));
13578
- const historyAbsPath = path17.join(
13662
+ const sessionDir = path18.relative(sessionsDir, path18.dirname(filePath));
13663
+ const historyAbsPath = path18.join(
13579
13664
  sessionsDir,
13580
13665
  sessionDir,
13581
13666
  `history-${meta.sessionId}.jsonl`
@@ -13595,18 +13680,18 @@ var CodexSource = class {
13595
13680
  };
13596
13681
 
13597
13682
  // src/sources/copilotChat.ts
13598
- import fs15 from "fs";
13683
+ import fs16 from "fs";
13599
13684
  import os11 from "os";
13600
- import path18 from "path";
13685
+ import path19 from "path";
13601
13686
  import { fileURLToPath } from "url";
13602
13687
  function vsCodeUserDirs() {
13603
13688
  const home = os11.homedir();
13604
13689
  const dirs = [
13605
- path18.join(home, "Library", "Application Support", "Code", "User"),
13606
- path18.join(home, ".config", "Code", "User")
13690
+ path19.join(home, "Library", "Application Support", "Code", "User"),
13691
+ path19.join(home, ".config", "Code", "User")
13607
13692
  ];
13608
13693
  if (process.env.APPDATA) {
13609
- dirs.push(path18.join(process.env.APPDATA, "Code", "User"));
13694
+ dirs.push(path19.join(process.env.APPDATA, "Code", "User"));
13610
13695
  }
13611
13696
  return dirs;
13612
13697
  }
@@ -13621,7 +13706,7 @@ function uriToFsPath(uri) {
13621
13706
  async function readWorkspaceFolder(workspaceJsonPath) {
13622
13707
  let raw;
13623
13708
  try {
13624
- raw = await fs15.promises.readFile(workspaceJsonPath, "utf-8");
13709
+ raw = await fs16.promises.readFile(workspaceJsonPath, "utf-8");
13625
13710
  } catch {
13626
13711
  return null;
13627
13712
  }
@@ -13643,10 +13728,10 @@ var CopilotChatSource = class {
13643
13728
  async scan() {
13644
13729
  const results = [];
13645
13730
  for (const userDir of vsCodeUserDirs()) {
13646
- const workspaceStorage = path18.join(userDir, "workspaceStorage");
13731
+ const workspaceStorage = path19.join(userDir, "workspaceStorage");
13647
13732
  let hashDirs;
13648
13733
  try {
13649
- hashDirs = await fs15.promises.readdir(workspaceStorage, {
13734
+ hashDirs = await fs16.promises.readdir(workspaceStorage, {
13650
13735
  withFileTypes: true
13651
13736
  });
13652
13737
  } catch {
@@ -13654,22 +13739,22 @@ var CopilotChatSource = class {
13654
13739
  }
13655
13740
  for (const hash of hashDirs) {
13656
13741
  if (!hash.isDirectory()) continue;
13657
- const wsRoot = path18.join(workspaceStorage, hash.name);
13658
- const transcriptsDir = path18.join(
13742
+ const wsRoot = path19.join(workspaceStorage, hash.name);
13743
+ const transcriptsDir = path19.join(
13659
13744
  wsRoot,
13660
13745
  "GitHub.copilot-chat",
13661
13746
  "transcripts"
13662
13747
  );
13663
13748
  let transcriptEntries;
13664
13749
  try {
13665
- transcriptEntries = await fs15.promises.readdir(transcriptsDir, {
13750
+ transcriptEntries = await fs16.promises.readdir(transcriptsDir, {
13666
13751
  withFileTypes: true
13667
13752
  });
13668
13753
  } catch {
13669
13754
  continue;
13670
13755
  }
13671
13756
  const repoPath = await readWorkspaceFolder(
13672
- path18.join(wsRoot, "workspace.json")
13757
+ path19.join(wsRoot, "workspace.json")
13673
13758
  );
13674
13759
  if (!repoPath) continue;
13675
13760
  for (const entry of transcriptEntries) {
@@ -13677,7 +13762,7 @@ var CopilotChatSource = class {
13677
13762
  const sessionId = entry.name.slice(0, -".jsonl".length);
13678
13763
  results.push({
13679
13764
  sourceName: this.name,
13680
- absolutePath: path18.join(transcriptsDir, entry.name),
13765
+ absolutePath: path19.join(transcriptsDir, entry.name),
13681
13766
  repoPath,
13682
13767
  metadata: { sessionId }
13683
13768
  });
@@ -13717,7 +13802,7 @@ function reportRedactionStats(noun, stats) {
13717
13802
  async function filterByTimeRange(group, range) {
13718
13803
  const results = await Promise.all(
13719
13804
  group.files.map(
13720
- (f) => fs16.promises.stat(f.absolutePath).then((s) => ({ file: f, stat: s })).catch(() => null)
13805
+ (f) => fs17.promises.stat(f.absolutePath).then((s) => ({ file: f, stat: s })).catch(() => null)
13721
13806
  )
13722
13807
  );
13723
13808
  const filtered = [];
@@ -13744,10 +13829,10 @@ async function runInteractive() {
13744
13829
  s.start(`Scanning ${source.name} logs...`);
13745
13830
  const allFiles = await source.scan();
13746
13831
  const allGroups = await mergeByRepo(allFiles);
13747
- const repoRoot = path19.resolve(repo.root);
13832
+ const repoRoot = path20.resolve(repo.root);
13748
13833
  const matching = allGroups.filter((g) => {
13749
- const resolved = path19.resolve(g.repoPath);
13750
- return resolved === repoRoot || resolved.startsWith(repoRoot + path19.sep);
13834
+ const resolved = path20.resolve(g.repoPath);
13835
+ return resolved === repoRoot || resolved.startsWith(repoRoot + path20.sep);
13751
13836
  });
13752
13837
  if (matching.length === 0) {
13753
13838
  s.stop(`No ${source.name} logs found for ${repo.name}.`);
@@ -13778,7 +13863,7 @@ async function runInteractive() {
13778
13863
  }
13779
13864
  }
13780
13865
  const envFileNames = await discoverEnvFiles(repoRoot);
13781
- const envFilePaths = envFileNames.map((n) => path19.join(repoRoot, n));
13866
+ const envFilePaths = envFileNames.map((n) => path20.join(repoRoot, n));
13782
13867
  const additionalFiles = await promptSecretFiles(envFileNames);
13783
13868
  const secretResult = await collectSecrets(
13784
13869
  repoRoot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hillclimb",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Extract and export AI coding tool logs grouped by repo",
5
5
  "license": "MIT",
6
6
  "type": "module",