hillclimb 0.9.1 → 0.9.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.
|
@@ -160,7 +160,7 @@ async function repoIdentity(repoRoot) {
|
|
|
160
160
|
}
|
|
161
161
|
async function logHeal(level, message) {
|
|
162
162
|
try {
|
|
163
|
-
const { appendLog: appendLog2 } = await import("./log-
|
|
163
|
+
const { appendLog: appendLog2 } = await import("./log-UZPPBFMO.js");
|
|
164
164
|
appendLog2(level, message);
|
|
165
165
|
} catch {
|
|
166
166
|
}
|
|
@@ -182,7 +182,7 @@ function matchByPath(projects, resolved) {
|
|
|
182
182
|
var healMisses = /* @__PURE__ */ new Set();
|
|
183
183
|
async function healRenamedProject(file, resolved) {
|
|
184
184
|
const dead = Object.entries(file.projects).filter(
|
|
185
|
-
([key]) =>
|
|
185
|
+
([key]) => pathIsGone(path.resolve(key))
|
|
186
186
|
);
|
|
187
187
|
if (dead.length === 0) return null;
|
|
188
188
|
const missKey = `${configPath()}|${resolved}`;
|
|
@@ -288,11 +288,26 @@ async function matchLinkedWorktree(projects, resolved) {
|
|
|
288
288
|
}
|
|
289
289
|
return null;
|
|
290
290
|
}
|
|
291
|
+
function hasNestedGitFile(root, target) {
|
|
292
|
+
let dir = target;
|
|
293
|
+
while (dir !== root && dir.startsWith(root + path.sep)) {
|
|
294
|
+
try {
|
|
295
|
+
if (fs.statSync(path.join(dir, ".git")).isFile()) return true;
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
dir = path.dirname(dir);
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
291
302
|
async function findProjectForCwd(cwd) {
|
|
292
303
|
const file = await loadProjects();
|
|
293
304
|
const resolved = path.resolve(cwd);
|
|
294
305
|
const match = matchByPath(file.projects, resolved);
|
|
295
306
|
if (match) {
|
|
307
|
+
if (hasNestedGitFile(match.repoRoot, resolved)) {
|
|
308
|
+
const nested = await matchLinkedWorktree(file.projects, resolved);
|
|
309
|
+
if (nested) return nested;
|
|
310
|
+
}
|
|
296
311
|
await backfillIdentity(file, match);
|
|
297
312
|
return { repoRoot: match.repoRoot, config: match.config };
|
|
298
313
|
}
|
|
@@ -383,5 +398,6 @@ export {
|
|
|
383
398
|
configPath,
|
|
384
399
|
loadProjects,
|
|
385
400
|
upsertProject,
|
|
401
|
+
hasNestedGitFile,
|
|
386
402
|
findProjectForCwd
|
|
387
403
|
};
|
package/dist/main.js
CHANGED
|
@@ -5,11 +5,12 @@ import {
|
|
|
5
5
|
configDir,
|
|
6
6
|
configPath,
|
|
7
7
|
findProjectForCwd,
|
|
8
|
+
hasNestedGitFile,
|
|
8
9
|
loadProjects,
|
|
9
10
|
setLogPrefix,
|
|
10
11
|
todayLogPath,
|
|
11
12
|
upsertProject
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-UPXMA7RC.js";
|
|
13
14
|
|
|
14
15
|
// src/main.ts
|
|
15
16
|
import fs27 from "fs";
|
|
@@ -568,7 +569,7 @@ var PlatformClient = class {
|
|
|
568
569
|
const sizeBytes = body.kind === "buffer" ? body.buffer.length : body.sizeBytes;
|
|
569
570
|
const prefix = `/api/v1/contributions/${encodeURIComponent(contributionId)}`;
|
|
570
571
|
let digest;
|
|
571
|
-
const verify = async (uploadId) => {
|
|
572
|
+
const verify = async (uploadId, options = {}) => {
|
|
572
573
|
if (digest === void 0) {
|
|
573
574
|
const hash = createHash("sha256");
|
|
574
575
|
let localSize = 0;
|
|
@@ -590,7 +591,8 @@ var PlatformClient = class {
|
|
|
590
591
|
return this.verifyReplayArtifact(
|
|
591
592
|
`${prefix}/uploads/${encodeURIComponent(uploadId)}/download`,
|
|
592
593
|
sizeBytes,
|
|
593
|
-
digest
|
|
594
|
+
digest,
|
|
595
|
+
options
|
|
594
596
|
);
|
|
595
597
|
};
|
|
596
598
|
const put = async (target2) => {
|
|
@@ -642,7 +644,7 @@ var PlatformClient = class {
|
|
|
642
644
|
"UPLOAD_REPLAY_MISMATCH"
|
|
643
645
|
);
|
|
644
646
|
}
|
|
645
|
-
const verified = await verify(existing.id);
|
|
647
|
+
const verified = await verify(existing.id, { forbiddenIsAbsent: true });
|
|
646
648
|
if (verified.exists) return;
|
|
647
649
|
target = await this.request(
|
|
648
650
|
"POST",
|
|
@@ -665,7 +667,7 @@ var PlatformClient = class {
|
|
|
665
667
|
}
|
|
666
668
|
await put(target);
|
|
667
669
|
}
|
|
668
|
-
async verifyReplayArtifact(relative, sizeBytes, expectedDigest) {
|
|
670
|
+
async verifyReplayArtifact(relative, sizeBytes, expectedDigest, options = {}) {
|
|
669
671
|
const redirect = await fetch(this.url(relative), {
|
|
670
672
|
headers: this.headers(),
|
|
671
673
|
redirect: "manual",
|
|
@@ -708,8 +710,13 @@ var PlatformClient = class {
|
|
|
708
710
|
} catch {
|
|
709
711
|
throw new PlatformError("Stored artifact verification request failed");
|
|
710
712
|
}
|
|
711
|
-
if (response.status === 404) {
|
|
713
|
+
if (response.status === 404 || response.status === 403 && options.forbiddenIsAbsent) {
|
|
712
714
|
await response.body?.cancel();
|
|
715
|
+
if (response.status === 403)
|
|
716
|
+
appendLog(
|
|
717
|
+
"info",
|
|
718
|
+
"upload-replay: storage refused the pending artifact (403); treating it as never written and letting the guarded PUT decide"
|
|
719
|
+
);
|
|
713
720
|
return { exists: false, url: location };
|
|
714
721
|
}
|
|
715
722
|
if (!response.ok) {
|
|
@@ -986,7 +993,7 @@ var TOOLS = [
|
|
|
986
993
|
{ eventName: "Stop", command: GIT_TRACES_CMD("claude") },
|
|
987
994
|
{ eventName: "SessionEnd", command: GIT_TRACES_CMD("claude") }
|
|
988
995
|
],
|
|
989
|
-
detect: () => isDir(path4.join(
|
|
996
|
+
detect: () => isDir(path4.join(homeDir(), ".claude"))
|
|
990
997
|
},
|
|
991
998
|
{
|
|
992
999
|
tool: "cursor",
|
|
@@ -1003,7 +1010,7 @@ var TOOLS = [
|
|
|
1003
1010
|
{ eventName: "stop", command: GIT_TRACES_CMD("cursor") },
|
|
1004
1011
|
{ eventName: "sessionEnd", command: GIT_TRACES_CMD("cursor") }
|
|
1005
1012
|
],
|
|
1006
|
-
detect: () => isDir(path4.join(
|
|
1013
|
+
detect: () => isDir(path4.join(homeDir(), ".cursor"))
|
|
1007
1014
|
},
|
|
1008
1015
|
{
|
|
1009
1016
|
// opencode has no settings-file hook system (the `experimental.hook` block
|
|
@@ -1018,7 +1025,7 @@ var TOOLS = [
|
|
|
1018
1025
|
format: "opencode",
|
|
1019
1026
|
events: [],
|
|
1020
1027
|
// Events are wired inside the plugin itself; see OPENCODE_PLUGIN_CONTENT.
|
|
1021
|
-
detect: () => isDir(path4.join(
|
|
1028
|
+
detect: () => isDir(path4.join(homeDir(), ".local", "share", "opencode")) || isDir(path4.join(homeDir(), ".config", "opencode"))
|
|
1022
1029
|
},
|
|
1023
1030
|
{
|
|
1024
1031
|
// VS Code Copilot Chat. Same "no SessionEnd" constraint as Codex (the
|
|
@@ -1058,7 +1065,7 @@ var TOOLS = [
|
|
|
1058
1065
|
{ eventName: "SessionStart", command: GIT_TRACES_CMD("codex") },
|
|
1059
1066
|
{ eventName: "Stop", command: GIT_TRACES_CMD("codex") }
|
|
1060
1067
|
],
|
|
1061
|
-
detect: () => isDir(path4.join(
|
|
1068
|
+
detect: () => isDir(path4.join(homeDir(), ".codex"))
|
|
1062
1069
|
}
|
|
1063
1070
|
];
|
|
1064
1071
|
function isDir(p7) {
|
|
@@ -1068,8 +1075,11 @@ function isDir(p7) {
|
|
|
1068
1075
|
return false;
|
|
1069
1076
|
}
|
|
1070
1077
|
}
|
|
1078
|
+
function homeDir() {
|
|
1079
|
+
return process.env.HOME || os.homedir();
|
|
1080
|
+
}
|
|
1071
1081
|
function copilotChatDetect() {
|
|
1072
|
-
const home =
|
|
1082
|
+
const home = homeDir();
|
|
1073
1083
|
const suffix = path4.join("User", "globalStorage", "github.copilot-chat");
|
|
1074
1084
|
const candidates = [
|
|
1075
1085
|
path4.join(home, "Library", "Application Support", "Code", suffix),
|
|
@@ -2714,22 +2724,76 @@ import { createReadStream } from "fs";
|
|
|
2714
2724
|
import fs11 from "fs/promises";
|
|
2715
2725
|
import path10 from "path";
|
|
2716
2726
|
|
|
2727
|
+
// src/timing-profile.ts
|
|
2728
|
+
var DEFAULT_STAGE_MIN_MS = 250;
|
|
2729
|
+
var DEFAULT_LOCK_WAIT_MIN_MS = 1e3;
|
|
2730
|
+
var STAGE_MIN_MS_ENV = "HILLCLIMB_TIMING_STAGE_MIN_MS";
|
|
2731
|
+
var LOCK_WAIT_MIN_MS_ENV = "HILLCLIMB_TIMING_LOCK_WAIT_MIN_MS";
|
|
2732
|
+
function thresholdMs(name, fallback) {
|
|
2733
|
+
const raw = process.env[name];
|
|
2734
|
+
if (raw === void 0 || raw.trim() === "") return fallback;
|
|
2735
|
+
const value = Number(raw);
|
|
2736
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
2737
|
+
}
|
|
2738
|
+
function shouldLogStage(stage, elapsedMs, status) {
|
|
2739
|
+
if (stage === "lock-wait") {
|
|
2740
|
+
return elapsedMs >= thresholdMs(LOCK_WAIT_MIN_MS_ENV, DEFAULT_LOCK_WAIT_MIN_MS);
|
|
2741
|
+
}
|
|
2742
|
+
return status === "failed" || elapsedMs >= thresholdMs(STAGE_MIN_MS_ENV, DEFAULT_STAGE_MIN_MS);
|
|
2743
|
+
}
|
|
2744
|
+
var StageProfile = class {
|
|
2745
|
+
constructor(prefix) {
|
|
2746
|
+
this.prefix = prefix;
|
|
2747
|
+
}
|
|
2748
|
+
totals = /* @__PURE__ */ new Map();
|
|
2749
|
+
record(stage, elapsedMs, status) {
|
|
2750
|
+
const totals = this.totals.get(stage) ?? {
|
|
2751
|
+
count: 0,
|
|
2752
|
+
totalMs: 0,
|
|
2753
|
+
maxMs: 0,
|
|
2754
|
+
failed: 0
|
|
2755
|
+
};
|
|
2756
|
+
totals.count++;
|
|
2757
|
+
totals.totalMs += elapsedMs;
|
|
2758
|
+
totals.maxMs = Math.max(totals.maxMs, elapsedMs);
|
|
2759
|
+
if (status === "failed") totals.failed++;
|
|
2760
|
+
this.totals.set(stage, totals);
|
|
2761
|
+
}
|
|
2762
|
+
flush() {
|
|
2763
|
+
if (this.totals.size === 0) return;
|
|
2764
|
+
const entries = [...this.totals].map(([stage, totals]) => {
|
|
2765
|
+
let entry = `${stage}=${totals.totalMs}ms/${totals.count}`;
|
|
2766
|
+
if (totals.count > 1) entry += ` max=${totals.maxMs}ms`;
|
|
2767
|
+
if (totals.failed > 0) entry += ` failed=${totals.failed}`;
|
|
2768
|
+
return entry;
|
|
2769
|
+
});
|
|
2770
|
+
appendLog(
|
|
2771
|
+
"info",
|
|
2772
|
+
`${this.prefix} (stages=${this.totals.size}, ${entries.join(", ")})`
|
|
2773
|
+
);
|
|
2774
|
+
this.totals.clear();
|
|
2775
|
+
}
|
|
2776
|
+
};
|
|
2777
|
+
|
|
2717
2778
|
// src/agent-timing.ts
|
|
2779
|
+
var profile = new StageProfile("agent-upload: timing profile");
|
|
2718
2780
|
function startAgentStage(stage, context = "") {
|
|
2719
2781
|
const startedAt = Date.now();
|
|
2720
2782
|
const started = performance.now();
|
|
2721
2783
|
const suffix = context ? `, ${context}` : "";
|
|
2722
|
-
appendLog(
|
|
2723
|
-
"info",
|
|
2724
|
-
`agent-upload: timing (stage=${stage}, status=started, startedAt=${startedAt}${suffix})`
|
|
2725
|
-
);
|
|
2726
2784
|
return (status, details) => {
|
|
2785
|
+
const elapsedMs = Math.round(performance.now() - started);
|
|
2786
|
+
profile.record(stage, elapsedMs, status);
|
|
2787
|
+
if (!shouldLogStage(stage, elapsedMs, status)) return;
|
|
2727
2788
|
appendLog(
|
|
2728
2789
|
"info",
|
|
2729
|
-
`agent-upload: timing (stage=${stage}, status=${status}, elapsedMs=${
|
|
2790
|
+
`agent-upload: timing (stage=${stage}, status=${status}, elapsedMs=${elapsedMs}, startedAt=${startedAt}, finishedAt=${Date.now()}${suffix}${details ? `, ${details}` : ""})`
|
|
2730
2791
|
);
|
|
2731
2792
|
};
|
|
2732
2793
|
}
|
|
2794
|
+
function flushAgentProfile() {
|
|
2795
|
+
profile.flush();
|
|
2796
|
+
}
|
|
2733
2797
|
async function measureAgentStage(stage, action, context) {
|
|
2734
2798
|
const finish = startAgentStage(stage, context);
|
|
2735
2799
|
let status = "failed";
|
|
@@ -3193,6 +3257,47 @@ async function detectCodexLineage(childPath) {
|
|
|
3193
3257
|
}
|
|
3194
3258
|
}
|
|
3195
3259
|
|
|
3260
|
+
// src/git-traces/timing.ts
|
|
3261
|
+
import { performance as performance2 } from "perf_hooks";
|
|
3262
|
+
var profile2 = new StageProfile("git-traces: capture profile");
|
|
3263
|
+
function startCaptureStage(stage, context, startedAt = Date.now()) {
|
|
3264
|
+
const started = performance2.now();
|
|
3265
|
+
return (status, details) => {
|
|
3266
|
+
const elapsedMs = Math.round(performance2.now() - started);
|
|
3267
|
+
profile2.record(stage, elapsedMs, status);
|
|
3268
|
+
if (!shouldLogStage(stage, elapsedMs, status)) return;
|
|
3269
|
+
appendLog(
|
|
3270
|
+
"info",
|
|
3271
|
+
`git-traces: capture timing (${context}, stage=${stage}, status=${status}, elapsedMs=${elapsedMs}, startedAt=${startedAt}, finishedAt=${Date.now()}${details ? `, ${details}` : ""})`
|
|
3272
|
+
);
|
|
3273
|
+
};
|
|
3274
|
+
}
|
|
3275
|
+
function flushCaptureProfile() {
|
|
3276
|
+
profile2.flush();
|
|
3277
|
+
}
|
|
3278
|
+
async function timeAsyncCaptureStage(stage, context, run) {
|
|
3279
|
+
const finish = startCaptureStage(stage, context);
|
|
3280
|
+
let status = "failed";
|
|
3281
|
+
try {
|
|
3282
|
+
const result = await run();
|
|
3283
|
+
status = "ok";
|
|
3284
|
+
return result;
|
|
3285
|
+
} finally {
|
|
3286
|
+
finish(status);
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
function timeCaptureStage(repoRoot, stage, run) {
|
|
3290
|
+
const finish = startCaptureStage(stage, `repo=${repoRoot}`);
|
|
3291
|
+
let status = "failed";
|
|
3292
|
+
try {
|
|
3293
|
+
const result = run();
|
|
3294
|
+
status = "ok";
|
|
3295
|
+
return result;
|
|
3296
|
+
} finally {
|
|
3297
|
+
finish(status);
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3196
3301
|
// src/hook-timing.ts
|
|
3197
3302
|
var HOOK_RECEIVED_AT_ENV = "HILLCLIMB_HOOK_RECEIVED_AT";
|
|
3198
3303
|
function pinHookReceivedAt(env, now = Date.now()) {
|
|
@@ -3216,6 +3321,8 @@ async function measureHookWork(kind, action) {
|
|
|
3216
3321
|
status = "completed";
|
|
3217
3322
|
return result;
|
|
3218
3323
|
} finally {
|
|
3324
|
+
flushCaptureProfile();
|
|
3325
|
+
flushAgentProfile();
|
|
3219
3326
|
appendLog(
|
|
3220
3327
|
"info",
|
|
3221
3328
|
`hook-work: kind=${kind} status=${status} finishedAt=${Date.now()} workMs=${Math.round(performance.now() - started)} hookAgeMs=${Math.max(0, Date.now() - receivedAt)}`
|
|
@@ -4463,17 +4570,22 @@ async function gitCommonDir(cwd) {
|
|
|
4463
4570
|
async function classifyForkCwd(cwd, parentRepoRoot) {
|
|
4464
4571
|
const resolved = await canonicalPath(cwd);
|
|
4465
4572
|
const parentRoot = await canonicalPath(parentRepoRoot);
|
|
4466
|
-
|
|
4573
|
+
const below = isAtOrBelow(resolved, parentRoot);
|
|
4574
|
+
if (below && !hasNestedGitFile(parentRoot, resolved))
|
|
4467
4575
|
return { kind: "same-tree", repoRoot: parentRoot };
|
|
4468
4576
|
const toplevel = await gitOut(resolved, ["rev-parse", "--show-toplevel"]);
|
|
4469
|
-
if (!toplevel)
|
|
4577
|
+
if (!toplevel)
|
|
4578
|
+
return below ? { kind: "same-tree", repoRoot: parentRoot } : { kind: "unrelated", repoRoot: null };
|
|
4579
|
+
const worktreeRoot = await canonicalPath(toplevel);
|
|
4580
|
+
if (worktreeRoot === parentRoot)
|
|
4581
|
+
return { kind: "same-tree", repoRoot: parentRoot };
|
|
4470
4582
|
const [forkCommon, parentCommon] = await Promise.all([
|
|
4471
4583
|
gitCommonDir(resolved),
|
|
4472
4584
|
gitCommonDir(parentRoot)
|
|
4473
4585
|
]);
|
|
4474
4586
|
if (forkCommon && parentCommon && forkCommon === parentCommon)
|
|
4475
|
-
return { kind: "worktree", repoRoot:
|
|
4476
|
-
return { kind: "unrelated", repoRoot: null };
|
|
4587
|
+
return { kind: "worktree", repoRoot: worktreeRoot };
|
|
4588
|
+
return below ? { kind: "same-tree", repoRoot: parentRoot } : { kind: "unrelated", repoRoot: null };
|
|
4477
4589
|
}
|
|
4478
4590
|
async function discoverCodexForkThreads(params) {
|
|
4479
4591
|
const nowMs = params.nowMs ?? Date.now();
|
|
@@ -14656,46 +14768,6 @@ import fs17 from "fs";
|
|
|
14656
14768
|
import os7 from "os";
|
|
14657
14769
|
import path16 from "path";
|
|
14658
14770
|
import { createGzip } from "zlib";
|
|
14659
|
-
|
|
14660
|
-
// src/git-traces/timing.ts
|
|
14661
|
-
import { performance as performance2 } from "perf_hooks";
|
|
14662
|
-
function startCaptureStage(stage, context, startedAt = Date.now()) {
|
|
14663
|
-
const started = performance2.now();
|
|
14664
|
-
appendLog(
|
|
14665
|
-
"info",
|
|
14666
|
-
`git-traces: capture timing (${context}, stage=${stage}, status=started, startedAt=${startedAt})`
|
|
14667
|
-
);
|
|
14668
|
-
return (status, details) => {
|
|
14669
|
-
appendLog(
|
|
14670
|
-
"info",
|
|
14671
|
-
`git-traces: capture timing (${context}, stage=${stage}, status=${status}, elapsedMs=${Math.round(performance2.now() - started)}, startedAt=${startedAt}, finishedAt=${Date.now()}${details ? `, ${details}` : ""})`
|
|
14672
|
-
);
|
|
14673
|
-
};
|
|
14674
|
-
}
|
|
14675
|
-
async function timeAsyncCaptureStage(stage, context, run) {
|
|
14676
|
-
const finish = startCaptureStage(stage, context);
|
|
14677
|
-
let status = "failed";
|
|
14678
|
-
try {
|
|
14679
|
-
const result = await run();
|
|
14680
|
-
status = "ok";
|
|
14681
|
-
return result;
|
|
14682
|
-
} finally {
|
|
14683
|
-
finish(status);
|
|
14684
|
-
}
|
|
14685
|
-
}
|
|
14686
|
-
function timeCaptureStage(repoRoot, stage, run) {
|
|
14687
|
-
const finish = startCaptureStage(stage, `repo=${repoRoot}`);
|
|
14688
|
-
let status = "failed";
|
|
14689
|
-
try {
|
|
14690
|
-
const result = run();
|
|
14691
|
-
status = "ok";
|
|
14692
|
-
return result;
|
|
14693
|
-
} finally {
|
|
14694
|
-
finish(status);
|
|
14695
|
-
}
|
|
14696
|
-
}
|
|
14697
|
-
|
|
14698
|
-
// src/git-traces/git-ops.ts
|
|
14699
14771
|
var DEFAULT_GIT_COMMAND_TIMEOUT_MS = 12e4;
|
|
14700
14772
|
var DEFAULT_BUNDLE_TIMEOUT_MS = 3e5;
|
|
14701
14773
|
var FULL_TREE_SCAN_TIMEOUT_MS = 3e5;
|
|
@@ -15036,9 +15108,11 @@ function isGitRepo(dir) {
|
|
|
15036
15108
|
function stashRetryReason(failure) {
|
|
15037
15109
|
const status = failure.gitExitStatus;
|
|
15038
15110
|
if (status !== 1 && status !== 128) return null;
|
|
15039
|
-
|
|
15040
|
-
|
|
15041
|
-
|
|
15111
|
+
const stderr = failure.gitStderr ?? "";
|
|
15112
|
+
if (/Unable to create [^\n]*index\.lock[^\n]*: File exists/.test(stderr)) {
|
|
15113
|
+
return "index-lock-contention";
|
|
15114
|
+
}
|
|
15115
|
+
if (status === 1 && stderr.trim() === "error: could not write index") {
|
|
15042
15116
|
return "index-lock-contention";
|
|
15043
15117
|
}
|
|
15044
15118
|
if (status === 1 && !failure.gitStderr && !failure.gitStdout) {
|
|
@@ -15046,7 +15120,60 @@ function stashRetryReason(failure) {
|
|
|
15046
15120
|
}
|
|
15047
15121
|
return null;
|
|
15048
15122
|
}
|
|
15123
|
+
var PRIVATE_INDEX_ENV = "HILLCLIMB_GIT_PRIVATE_INDEX";
|
|
15124
|
+
function withPrivateIndex(repoRoot, run) {
|
|
15125
|
+
if (process.env[PRIVATE_INDEX_ENV] === "0") return run(null);
|
|
15126
|
+
let tmpDir = null;
|
|
15127
|
+
let copy = null;
|
|
15128
|
+
try {
|
|
15129
|
+
const indexPath = path16.resolve(
|
|
15130
|
+
repoRoot,
|
|
15131
|
+
git(repoRoot, ["rev-parse", "--git-path", "index"])
|
|
15132
|
+
);
|
|
15133
|
+
tmpDir = fs17.mkdtempSync(path16.join(os7.tmpdir(), "hillclimb-index-"));
|
|
15134
|
+
copy = path16.join(tmpDir, "index");
|
|
15135
|
+
const fd = fs17.openSync(indexPath, "r");
|
|
15136
|
+
let stat;
|
|
15137
|
+
let data;
|
|
15138
|
+
try {
|
|
15139
|
+
stat = fs17.fstatSync(fd);
|
|
15140
|
+
data = fs17.readFileSync(fd);
|
|
15141
|
+
} finally {
|
|
15142
|
+
fs17.closeSync(fd);
|
|
15143
|
+
}
|
|
15144
|
+
fs17.writeFileSync(copy, data);
|
|
15145
|
+
fs17.utimesSync(copy, stat.atime, stat.mtime);
|
|
15146
|
+
} catch (err) {
|
|
15147
|
+
const missing = err.code === "ENOENT";
|
|
15148
|
+
appendLog(
|
|
15149
|
+
missing ? "info" : "warn",
|
|
15150
|
+
`git-traces: private index unavailable; capturing on the repository index (repo=${repoRoot}, reason=${err instanceof Error ? err.message : String(err)})`
|
|
15151
|
+
);
|
|
15152
|
+
copy = null;
|
|
15153
|
+
}
|
|
15154
|
+
try {
|
|
15155
|
+
return run(copy ? privateIndexEnv(copy) : null);
|
|
15156
|
+
} finally {
|
|
15157
|
+
if (tmpDir) fs17.rmSync(tmpDir, { recursive: true, force: true });
|
|
15158
|
+
}
|
|
15159
|
+
}
|
|
15160
|
+
function privateIndexEnv(indexPath) {
|
|
15161
|
+
const existing = Number(process.env.GIT_CONFIG_COUNT);
|
|
15162
|
+
const slot = Number.isInteger(existing) && existing > 0 ? existing : 0;
|
|
15163
|
+
return {
|
|
15164
|
+
GIT_INDEX_FILE: indexPath,
|
|
15165
|
+
GIT_CONFIG_COUNT: String(slot + 1),
|
|
15166
|
+
[`GIT_CONFIG_KEY_${slot}`]: "core.splitIndex",
|
|
15167
|
+
[`GIT_CONFIG_VALUE_${slot}`]: "false"
|
|
15168
|
+
};
|
|
15169
|
+
}
|
|
15049
15170
|
function captureWorkingCommitSha(repoRoot) {
|
|
15171
|
+
return withPrivateIndex(
|
|
15172
|
+
repoRoot,
|
|
15173
|
+
(indexEnv) => captureWorkingCommitShaOnIndex(repoRoot, indexEnv)
|
|
15174
|
+
);
|
|
15175
|
+
}
|
|
15176
|
+
function captureWorkingCommitShaOnIndex(repoRoot, indexEnv) {
|
|
15050
15177
|
for (let attempt = 1; ; attempt++) {
|
|
15051
15178
|
let sha;
|
|
15052
15179
|
try {
|
|
@@ -15057,6 +15184,7 @@ function captureWorkingCommitSha(repoRoot) {
|
|
|
15057
15184
|
// Keep the two expected stderr classifications independent of locale.
|
|
15058
15185
|
gitWithEnv(repoRoot, ["stash", "create"], {
|
|
15059
15186
|
...collectorIdentityEnv(),
|
|
15187
|
+
...indexEnv,
|
|
15060
15188
|
LC_ALL: "C"
|
|
15061
15189
|
})
|
|
15062
15190
|
)
|
|
@@ -15069,7 +15197,10 @@ function captureWorkingCommitSha(repoRoot) {
|
|
|
15069
15197
|
"info",
|
|
15070
15198
|
`git-traces: stash create unavailable (repo=${repoRoot}, reason=unborn-head); capturing index tree`
|
|
15071
15199
|
);
|
|
15072
|
-
const tree =
|
|
15200
|
+
const tree = gitWithEnv(repoRoot, ["write-tree"], {
|
|
15201
|
+
...process.env,
|
|
15202
|
+
...indexEnv
|
|
15203
|
+
});
|
|
15073
15204
|
return commitTree(repoRoot, tree, "empty baseline");
|
|
15074
15205
|
}
|
|
15075
15206
|
const retryReason = stashRetryReason(failure);
|
|
@@ -15771,7 +15902,10 @@ function buildBaselineMetadata(repoRoot, sessionId, tool, baselineSha, cliVersio
|
|
|
15771
15902
|
const headSha = safeGit(repoRoot, ["rev-parse", "HEAD"]) ?? "unknown";
|
|
15772
15903
|
const branch = safeGit(repoRoot, ["rev-parse", "--abbrev-ref", "HEAD"]) ?? null;
|
|
15773
15904
|
const remoteUrl = safeGit(repoRoot, ["config", "--get", "remote.origin.url"]) ?? null;
|
|
15774
|
-
const statusRaw =
|
|
15905
|
+
const statusRaw = withPrivateIndex(
|
|
15906
|
+
repoRoot,
|
|
15907
|
+
(indexEnv) => safeGitBuffer(repoRoot, ["status", "--porcelain=v1", "-z"], indexEnv)
|
|
15908
|
+
) ?? Buffer.alloc(0);
|
|
15775
15909
|
const dirtyFiles = parseDirtyFilesFromStatus(statusRaw);
|
|
15776
15910
|
const authorName = safeGit(repoRoot, ["config", "user.name"]) ?? "";
|
|
15777
15911
|
const authorEmail = safeGit(repoRoot, ["config", "user.email"]) ?? "";
|
|
@@ -15920,9 +16054,13 @@ function safeGit(repoRoot, args) {
|
|
|
15920
16054
|
return null;
|
|
15921
16055
|
}
|
|
15922
16056
|
}
|
|
15923
|
-
function safeGitBuffer(repoRoot, args) {
|
|
16057
|
+
function safeGitBuffer(repoRoot, args, env = null) {
|
|
15924
16058
|
try {
|
|
15925
|
-
return gitBuffer(
|
|
16059
|
+
return gitBuffer(
|
|
16060
|
+
repoRoot,
|
|
16061
|
+
args,
|
|
16062
|
+
env ? { env: { ...process.env, ...env } } : {}
|
|
16063
|
+
);
|
|
15926
16064
|
} catch (err) {
|
|
15927
16065
|
if (isGitTimeoutError(err)) {
|
|
15928
16066
|
appendLog(
|
|
@@ -16831,7 +16969,7 @@ async function uploadArtifact(client, contributionId, filename, mimeType, buffer
|
|
|
16831
16969
|
}
|
|
16832
16970
|
|
|
16833
16971
|
// package.json
|
|
16834
|
-
var version = "0.9.
|
|
16972
|
+
var version = "0.9.3";
|
|
16835
16973
|
|
|
16836
16974
|
// src/version.ts
|
|
16837
16975
|
var CLI_VERSION = version;
|
|
@@ -16972,8 +17110,14 @@ function isPhantomClaudeHook(payload, toolOverride) {
|
|
|
16972
17110
|
}
|
|
16973
17111
|
var MAX_FORK_SWEEP_THREADS = 8;
|
|
16974
17112
|
async function runUploadInner(payload, options = {}) {
|
|
16975
|
-
|
|
16976
|
-
|
|
17113
|
+
let outcome;
|
|
17114
|
+
let failure;
|
|
17115
|
+
try {
|
|
17116
|
+
outcome = await runUploadForSession(payload, options);
|
|
17117
|
+
} catch (err) {
|
|
17118
|
+
failure = err;
|
|
17119
|
+
}
|
|
17120
|
+
const settled = failure !== void 0 || outcome === "uploaded" || outcome === "unchanged" || outcome === "skipped";
|
|
16977
17121
|
if (!options.sweptFrom && payload.tool === "codex" && settled) {
|
|
16978
17122
|
await sweepCodexForkTranscripts(payload);
|
|
16979
17123
|
}
|
|
@@ -16983,6 +17127,7 @@ async function runUploadInner(payload, options = {}) {
|
|
|
16983
17127
|
if (!options.sweptFrom && settled) {
|
|
16984
17128
|
await sweepStrandedAgentUploads(resolveHookSessionId(payload));
|
|
16985
17129
|
}
|
|
17130
|
+
if (failure !== void 0) throw failure;
|
|
16986
17131
|
return outcome;
|
|
16987
17132
|
}
|
|
16988
17133
|
var MAX_SUBAGENT_SWEEP = 32;
|