hillclimb 0.3.0 → 0.4.0
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.
- package/dist/cli.js +1037 -585
- 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
|
|
5
|
-
import
|
|
4
|
+
import fs18 from "fs";
|
|
5
|
+
import path22 from "path";
|
|
6
6
|
import * as p6 from "@clack/prompts";
|
|
7
7
|
|
|
8
8
|
// src/commands/init.ts
|
|
@@ -2149,9 +2149,14 @@ async function runStatus(args = []) {
|
|
|
2149
2149
|
|
|
2150
2150
|
// src/commands/upload.ts
|
|
2151
2151
|
import { spawn as spawn2 } from "child_process";
|
|
2152
|
-
import
|
|
2152
|
+
import fs10 from "fs";
|
|
2153
2153
|
import os5 from "os";
|
|
2154
|
-
import
|
|
2154
|
+
import path13 from "path";
|
|
2155
|
+
|
|
2156
|
+
// src/debug-logs.ts
|
|
2157
|
+
import crypto from "crypto";
|
|
2158
|
+
import fs9 from "fs";
|
|
2159
|
+
import path11 from "path";
|
|
2155
2160
|
|
|
2156
2161
|
// src/middleware/pattern-redact.ts
|
|
2157
2162
|
import os3 from "os";
|
|
@@ -10844,161 +10849,732 @@ async function collectSecrets(repoRoot, envFiles, additionalFiles) {
|
|
|
10844
10849
|
return { values, sourceFiles, processEnvCount, skippedCount };
|
|
10845
10850
|
}
|
|
10846
10851
|
|
|
10847
|
-
// src/
|
|
10848
|
-
import
|
|
10852
|
+
// src/outputs/platform.ts
|
|
10853
|
+
import { PassThrough } from "stream";
|
|
10854
|
+
import archiver from "archiver";
|
|
10849
10855
|
|
|
10850
|
-
// src/
|
|
10851
|
-
|
|
10852
|
-
|
|
10853
|
-
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10856
|
+
// src/outputs/archive.ts
|
|
10857
|
+
import os4 from "os";
|
|
10858
|
+
import path9 from "path";
|
|
10859
|
+
function getSourceBaseDir(sourceName) {
|
|
10860
|
+
const home = os4.homedir();
|
|
10861
|
+
switch (sourceName) {
|
|
10862
|
+
case "claude":
|
|
10863
|
+
return path9.join(home, ".claude", "projects");
|
|
10864
|
+
case "codex":
|
|
10865
|
+
return path9.join(home, ".codex", "sessions");
|
|
10866
|
+
case "debug-logs":
|
|
10867
|
+
return path9.join(configDir(), "logs");
|
|
10868
|
+
default:
|
|
10869
|
+
return home;
|
|
10857
10870
|
}
|
|
10858
10871
|
}
|
|
10859
|
-
function
|
|
10860
|
-
|
|
10861
|
-
|
|
10862
|
-
|
|
10863
|
-
|
|
10864
|
-
|
|
10865
|
-
|
|
10866
|
-
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
textParts.push(stringify(block));
|
|
10870
|
-
continue;
|
|
10871
|
-
}
|
|
10872
|
-
const b = block;
|
|
10873
|
-
const blockType = b.type;
|
|
10874
|
-
if (blockType === "tool_use") {
|
|
10875
|
-
toolBlocks.push(b);
|
|
10876
|
-
continue;
|
|
10877
|
-
}
|
|
10878
|
-
if (blockType === "thinking" || blockType === "reasoning" || blockType === "analysis") {
|
|
10879
|
-
const textValue2 = b.text !== void 0 && b.text !== null ? b.text : b.thinking;
|
|
10880
|
-
if (typeof textValue2 === "string") {
|
|
10881
|
-
reasoningParts.push(textValue2.trim());
|
|
10882
|
-
} else {
|
|
10883
|
-
reasoningParts.push(stringify(textValue2));
|
|
10884
|
-
}
|
|
10885
|
-
continue;
|
|
10886
|
-
}
|
|
10887
|
-
if (blockType === "code" && typeof b.code === "string") {
|
|
10888
|
-
textParts.push(b.code);
|
|
10889
|
-
continue;
|
|
10890
|
-
}
|
|
10891
|
-
const textValue = b.text;
|
|
10892
|
-
if (typeof textValue === "string") {
|
|
10893
|
-
textParts.push(textValue);
|
|
10894
|
-
} else {
|
|
10895
|
-
textParts.push(stringify(b));
|
|
10896
|
-
}
|
|
10872
|
+
function addGroupToArchive(archive, group, selectedSources) {
|
|
10873
|
+
for (const file of group.files) {
|
|
10874
|
+
if (!selectedSources.has(file.sourceName)) continue;
|
|
10875
|
+
const baseDir = getSourceBaseDir(file.sourceName);
|
|
10876
|
+
const relativePath = file.absolutePath.startsWith(baseDir) ? path9.relative(baseDir, file.absolutePath) : path9.basename(file.absolutePath);
|
|
10877
|
+
const archivePath = path9.join(file.sourceName, relativePath);
|
|
10878
|
+
if (file.content) {
|
|
10879
|
+
archive.append(file.content, { name: archivePath });
|
|
10880
|
+
} else {
|
|
10881
|
+
archive.file(file.absolutePath, { name: archivePath });
|
|
10897
10882
|
}
|
|
10898
|
-
} else if (content !== void 0 && content !== null) {
|
|
10899
|
-
textParts.push(stringify(content));
|
|
10900
10883
|
}
|
|
10901
|
-
const text3 = textParts.filter((p7) => p7 && p7.trim()).map((p7) => p7.trim()).join("\n\n");
|
|
10902
|
-
const reasoning = reasoningParts.filter((p7) => p7 && p7.trim()).map((p7) => p7.trim()).join("\n\n");
|
|
10903
|
-
return [text3, reasoning || void 0, toolBlocks];
|
|
10904
10884
|
}
|
|
10905
|
-
|
|
10906
|
-
|
|
10907
|
-
|
|
10908
|
-
const
|
|
10909
|
-
const
|
|
10910
|
-
|
|
10911
|
-
const
|
|
10912
|
-
const
|
|
10913
|
-
|
|
10914
|
-
|
|
10915
|
-
|
|
10916
|
-
|
|
10917
|
-
}
|
|
10918
|
-
|
|
10919
|
-
|
|
10920
|
-
|
|
10921
|
-
|
|
10922
|
-
extra: Object.keys(extra).length > 0 ? extra : void 0
|
|
10923
|
-
};
|
|
10885
|
+
|
|
10886
|
+
// src/outputs/platform.ts
|
|
10887
|
+
async function buildZipBuffer(group, selectedSources) {
|
|
10888
|
+
const archive = archiver("zip", { zlib: { level: 6 } });
|
|
10889
|
+
const stream = new PassThrough();
|
|
10890
|
+
archive.pipe(stream);
|
|
10891
|
+
const chunks = [];
|
|
10892
|
+
const done = new Promise((resolve, reject) => {
|
|
10893
|
+
stream.on("data", (chunk) => chunks.push(chunk));
|
|
10894
|
+
stream.on("end", resolve);
|
|
10895
|
+
stream.on("error", reject);
|
|
10896
|
+
archive.on("error", reject);
|
|
10897
|
+
});
|
|
10898
|
+
addGroupToArchive(archive, group, selectedSources);
|
|
10899
|
+
await archive.finalize();
|
|
10900
|
+
await done;
|
|
10901
|
+
return Buffer.concat(chunks);
|
|
10924
10902
|
}
|
|
10925
|
-
|
|
10926
|
-
|
|
10927
|
-
|
|
10928
|
-
if (typeof content === "string") {
|
|
10929
|
-
if (content.trim()) parts.push(content.trim());
|
|
10930
|
-
} else if (Array.isArray(content)) {
|
|
10931
|
-
for (const item of content) {
|
|
10932
|
-
const text3 = stringify(item);
|
|
10933
|
-
if (text3.trim()) parts.push(text3.trim());
|
|
10934
|
-
}
|
|
10935
|
-
} else if (content !== void 0 && content !== null && content !== "") {
|
|
10936
|
-
parts.push(stringify(content));
|
|
10903
|
+
var PlatformUploadOutput = class {
|
|
10904
|
+
constructor(opts) {
|
|
10905
|
+
this.opts = opts;
|
|
10937
10906
|
}
|
|
10938
|
-
|
|
10939
|
-
|
|
10940
|
-
|
|
10941
|
-
const
|
|
10942
|
-
const
|
|
10943
|
-
const
|
|
10944
|
-
|
|
10945
|
-
|
|
10946
|
-
|
|
10947
|
-
|
|
10948
|
-
|
|
10949
|
-
|
|
10950
|
-
|
|
10951
|
-
|
|
10952
|
-
|
|
10953
|
-
|
|
10954
|
-
|
|
10955
|
-
|
|
10956
|
-
|
|
10957
|
-
|
|
10958
|
-
|
|
10959
|
-
"
|
|
10960
|
-
|
|
10961
|
-
|
|
10962
|
-
|
|
10963
|
-
|
|
10964
|
-
|
|
10965
|
-
|
|
10966
|
-
|
|
10967
|
-
|
|
10968
|
-
|
|
10969
|
-
|
|
10970
|
-
|
|
10971
|
-
|
|
10907
|
+
name = "platform";
|
|
10908
|
+
label = "Upload to hillclimb platform";
|
|
10909
|
+
async emit(group, options) {
|
|
10910
|
+
const selectedSources = new Set(options.selectedSources);
|
|
10911
|
+
const buffer = await buildZipBuffer(group, selectedSources);
|
|
10912
|
+
const {
|
|
10913
|
+
client,
|
|
10914
|
+
projectId,
|
|
10915
|
+
contributionTypeSlug,
|
|
10916
|
+
contributionTitle,
|
|
10917
|
+
contributionBody,
|
|
10918
|
+
zipFilename,
|
|
10919
|
+
autoSubmit
|
|
10920
|
+
} = this.opts;
|
|
10921
|
+
const contribution = await client.createContribution(projectId, {
|
|
10922
|
+
contributionTypeSlug,
|
|
10923
|
+
title: contributionTitle,
|
|
10924
|
+
body: contributionBody
|
|
10925
|
+
});
|
|
10926
|
+
const presigned = await client.createUpload(contribution.id, {
|
|
10927
|
+
originalFilename: zipFilename,
|
|
10928
|
+
mimeType: "application/zip",
|
|
10929
|
+
sizeBytes: buffer.byteLength
|
|
10930
|
+
});
|
|
10931
|
+
appendLog(
|
|
10932
|
+
"info",
|
|
10933
|
+
`uploading ${zipFilename} (${buffer.byteLength} bytes) to presigned URL`
|
|
10934
|
+
);
|
|
10935
|
+
await client.uploadToPresignedUrl(
|
|
10936
|
+
presigned.presignedUrl,
|
|
10937
|
+
presigned.headers,
|
|
10938
|
+
buffer
|
|
10939
|
+
);
|
|
10940
|
+
appendLog("info", `PUT to presigned URL succeeded for ${zipFilename}`);
|
|
10941
|
+
if (autoSubmit) {
|
|
10942
|
+
appendLog("info", `submitting contribution ${contribution.id}`);
|
|
10943
|
+
await client.submitContribution(contribution.id);
|
|
10944
|
+
appendLog("info", `contribution ${contribution.id} submitted`);
|
|
10972
10945
|
}
|
|
10946
|
+
return contribution.id;
|
|
10973
10947
|
}
|
|
10974
|
-
|
|
10975
|
-
|
|
10976
|
-
|
|
10977
|
-
|
|
10948
|
+
};
|
|
10949
|
+
|
|
10950
|
+
// src/pipeline.ts
|
|
10951
|
+
import fs8 from "fs";
|
|
10952
|
+
import path10 from "path";
|
|
10953
|
+
function canonicalizePath(p7) {
|
|
10954
|
+
let resolved = path10.resolve(p7);
|
|
10955
|
+
if (resolved.endsWith(path10.sep) && resolved !== path10.sep) {
|
|
10956
|
+
resolved = resolved.slice(0, -1);
|
|
10978
10957
|
}
|
|
10979
|
-
|
|
10980
|
-
|
|
10981
|
-
|
|
10982
|
-
|
|
10958
|
+
return resolved;
|
|
10959
|
+
}
|
|
10960
|
+
function computeLabel(repoPath, allPaths) {
|
|
10961
|
+
const segments = repoPath.split(path10.sep).filter(Boolean);
|
|
10962
|
+
for (let depth = 1; depth <= segments.length; depth++) {
|
|
10963
|
+
const label = segments.slice(-depth).join("/");
|
|
10964
|
+
const matches = allPaths.filter((p7) => {
|
|
10965
|
+
const s = p7.split(path10.sep).filter(Boolean);
|
|
10966
|
+
return s.slice(-depth).join("/") === label;
|
|
10967
|
+
});
|
|
10968
|
+
if (matches.length === 1) return label;
|
|
10983
10969
|
}
|
|
10984
|
-
|
|
10985
|
-
return [resultText || void 0, metadata];
|
|
10970
|
+
return repoPath;
|
|
10986
10971
|
}
|
|
10987
|
-
function
|
|
10988
|
-
const
|
|
10989
|
-
for (const
|
|
10990
|
-
const
|
|
10991
|
-
|
|
10992
|
-
|
|
10993
|
-
|
|
10994
|
-
}
|
|
10972
|
+
async function mergeByRepo(files) {
|
|
10973
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
10974
|
+
for (const file of files) {
|
|
10975
|
+
const key = canonicalizePath(file.repoPath);
|
|
10976
|
+
const existing = grouped.get(key);
|
|
10977
|
+
if (existing) {
|
|
10978
|
+
existing.push({ ...file, repoPath: key });
|
|
10979
|
+
} else {
|
|
10980
|
+
grouped.set(key, [{ ...file, repoPath: key }]);
|
|
10995
10981
|
}
|
|
10996
10982
|
}
|
|
10997
|
-
|
|
10998
|
-
const
|
|
10999
|
-
|
|
11000
|
-
|
|
11001
|
-
|
|
10983
|
+
const allPaths = [...grouped.keys()];
|
|
10984
|
+
const groups = [];
|
|
10985
|
+
for (const [repoPath, groupFiles] of grouped) {
|
|
10986
|
+
const stats = await Promise.all(
|
|
10987
|
+
groupFiles.map((f) => fs8.promises.stat(f.absolutePath).catch(() => null))
|
|
10988
|
+
);
|
|
10989
|
+
let lastModified = /* @__PURE__ */ new Date(0);
|
|
10990
|
+
for (const stat of stats) {
|
|
10991
|
+
if (stat && stat.mtime > lastModified) lastModified = stat.mtime;
|
|
10992
|
+
}
|
|
10993
|
+
const sourceNames = [...new Set(groupFiles.map((f) => f.sourceName))];
|
|
10994
|
+
groups.push({
|
|
10995
|
+
repoPath,
|
|
10996
|
+
label: computeLabel(repoPath, allPaths),
|
|
10997
|
+
files: groupFiles,
|
|
10998
|
+
sourceNames,
|
|
10999
|
+
lastModified
|
|
11000
|
+
});
|
|
11001
|
+
}
|
|
11002
|
+
groups.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
11003
|
+
return groups.filter((g) => g.files.length > 0);
|
|
11004
|
+
}
|
|
11005
|
+
async function preloadFiles(group) {
|
|
11006
|
+
const files = await Promise.all(
|
|
11007
|
+
group.files.map(async (file) => {
|
|
11008
|
+
if (file.content) return file;
|
|
11009
|
+
try {
|
|
11010
|
+
const buf = await fs8.promises.readFile(file.absolutePath);
|
|
11011
|
+
const checkLen = Math.min(buf.length, 8192);
|
|
11012
|
+
for (let i = 0; i < checkLen; i++) {
|
|
11013
|
+
if (buf[i] === 0) {
|
|
11014
|
+
return { ...file, content: buf, isBinary: true };
|
|
11015
|
+
}
|
|
11016
|
+
}
|
|
11017
|
+
return { ...file, content: buf };
|
|
11018
|
+
} catch {
|
|
11019
|
+
return file;
|
|
11020
|
+
}
|
|
11021
|
+
})
|
|
11022
|
+
);
|
|
11023
|
+
return { ...group, files };
|
|
11024
|
+
}
|
|
11025
|
+
async function runPipeline(group, middleware2, output, options, onProgress) {
|
|
11026
|
+
const preloaded = await preloadFiles(group);
|
|
11027
|
+
let processed = preloaded;
|
|
11028
|
+
for (const mw of middleware2) {
|
|
11029
|
+
onProgress?.(`${mw.name} (${processed.files.length} files)`);
|
|
11030
|
+
processed = await mw.process(processed);
|
|
11031
|
+
}
|
|
11032
|
+
onProgress?.(`Compressing ${processed.files.length} files`);
|
|
11033
|
+
return output.emit(processed, options);
|
|
11034
|
+
}
|
|
11035
|
+
|
|
11036
|
+
// src/debug-logs.ts
|
|
11037
|
+
var DEBUG_LOGS_SLUG = "debug-logs";
|
|
11038
|
+
var CURRENT_SCHEMA_VERSION = 1;
|
|
11039
|
+
var DEFAULT_WAIT_MS = 6e4;
|
|
11040
|
+
var LOCK_RETRIES = 100;
|
|
11041
|
+
var LOCK_RETRY_DELAY_MS = 100;
|
|
11042
|
+
function stateDir() {
|
|
11043
|
+
return process.env.HILLCLIMB_DEBUG_LOG_STATE_DIR ?? path11.join(configDir(), "debug-log-uploads");
|
|
11044
|
+
}
|
|
11045
|
+
function waitMs() {
|
|
11046
|
+
const raw = process.env.HILLCLIMB_DEBUG_LOG_WAIT_MS;
|
|
11047
|
+
if (!raw) return DEFAULT_WAIT_MS;
|
|
11048
|
+
const parsed = Number(raw);
|
|
11049
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_WAIT_MS;
|
|
11050
|
+
}
|
|
11051
|
+
function stateFile(eventId) {
|
|
11052
|
+
return path11.join(stateDir(), `${eventId}.json`);
|
|
11053
|
+
}
|
|
11054
|
+
function lockFile(eventId) {
|
|
11055
|
+
return `${stateFile(eventId)}.lock`;
|
|
11056
|
+
}
|
|
11057
|
+
function sleep2(ms) {
|
|
11058
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11059
|
+
}
|
|
11060
|
+
function sanitize(value) {
|
|
11061
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
11062
|
+
}
|
|
11063
|
+
function formatEpochSeconds(date) {
|
|
11064
|
+
return String(Math.floor(date.getTime() / 1e3));
|
|
11065
|
+
}
|
|
11066
|
+
function toolLabel(tool) {
|
|
11067
|
+
const labels = {
|
|
11068
|
+
cursor: "Cursor",
|
|
11069
|
+
codex: "Codex",
|
|
11070
|
+
claude: "Claude",
|
|
11071
|
+
"copilot-chat": "GitHub Copilot Chat",
|
|
11072
|
+
opencode: "opencode"
|
|
11073
|
+
};
|
|
11074
|
+
return labels[tool] ?? tool;
|
|
11075
|
+
}
|
|
11076
|
+
function classifyHookEvent(event) {
|
|
11077
|
+
switch (event) {
|
|
11078
|
+
case "Stop":
|
|
11079
|
+
case "stop":
|
|
11080
|
+
case "session.idle":
|
|
11081
|
+
return "stop";
|
|
11082
|
+
case "SessionEnd":
|
|
11083
|
+
case "sessionEnd":
|
|
11084
|
+
case "session.deleted":
|
|
11085
|
+
case "server.instance.disposed":
|
|
11086
|
+
return "sessionEnd";
|
|
11087
|
+
default:
|
|
11088
|
+
return null;
|
|
11089
|
+
}
|
|
11090
|
+
}
|
|
11091
|
+
function resolveCwd(payload) {
|
|
11092
|
+
return payload.cwd ?? payload.workspace_roots?.[0] ?? null;
|
|
11093
|
+
}
|
|
11094
|
+
function resolveSessionId(payload) {
|
|
11095
|
+
return payload.session_id ?? payload.conversation_id ?? null;
|
|
11096
|
+
}
|
|
11097
|
+
function stringOrNull(value) {
|
|
11098
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
11099
|
+
}
|
|
11100
|
+
function expectedKinds(tool, eventKind, payload) {
|
|
11101
|
+
if (eventKind === "stop") {
|
|
11102
|
+
return new Set(
|
|
11103
|
+
tool === "codex" || tool === "copilot-chat" ? ["agent", "git"] : ["git"]
|
|
11104
|
+
);
|
|
11105
|
+
}
|
|
11106
|
+
if (tool === "opencode" && !resolveSessionId(payload)) {
|
|
11107
|
+
return /* @__PURE__ */ new Set(["git"]);
|
|
11108
|
+
}
|
|
11109
|
+
return /* @__PURE__ */ new Set(["agent", "git"]);
|
|
11110
|
+
}
|
|
11111
|
+
async function transcriptFingerprint(payload) {
|
|
11112
|
+
const transcriptPath = stringOrNull(payload.transcript_path);
|
|
11113
|
+
if (!transcriptPath) return {};
|
|
11114
|
+
const resolved = path11.resolve(transcriptPath);
|
|
11115
|
+
try {
|
|
11116
|
+
const stat = await fs9.promises.stat(resolved);
|
|
11117
|
+
return {
|
|
11118
|
+
transcriptPath: resolved,
|
|
11119
|
+
transcriptMtimeMs: stat.mtimeMs,
|
|
11120
|
+
transcriptSizeBytes: stat.size
|
|
11121
|
+
};
|
|
11122
|
+
} catch {
|
|
11123
|
+
return { transcriptPath: resolved };
|
|
11124
|
+
}
|
|
11125
|
+
}
|
|
11126
|
+
async function eventContext(tool, payload) {
|
|
11127
|
+
const eventKind = classifyHookEvent(payload.hook_event_name);
|
|
11128
|
+
if (!eventKind) return null;
|
|
11129
|
+
const cwd = resolveCwd(payload);
|
|
11130
|
+
if (!cwd) return null;
|
|
11131
|
+
const project = await findProjectForCwd(cwd);
|
|
11132
|
+
if (!project) return null;
|
|
11133
|
+
const sessionId = resolveSessionId(payload);
|
|
11134
|
+
const turnId = stringOrNull(payload.turn_id);
|
|
11135
|
+
const transcriptPath = stringOrNull(payload.transcript_path);
|
|
11136
|
+
if (!sessionId && !turnId && !transcriptPath) {
|
|
11137
|
+
return null;
|
|
11138
|
+
}
|
|
11139
|
+
const expected = expectedKinds(tool, eventKind, payload);
|
|
11140
|
+
const eventNonce = eventKind === "stop" && expected.size === 1 && expected.has("git") && !turnId && !transcriptPath ? crypto.randomBytes(8).toString("hex") : null;
|
|
11141
|
+
const fingerprint = {
|
|
11142
|
+
schema: "debug-log-event-v1",
|
|
11143
|
+
apiBaseUrl: project.config.apiBaseUrl,
|
|
11144
|
+
projectId: project.config.projectId,
|
|
11145
|
+
repoRoot: project.repoRoot,
|
|
11146
|
+
tool,
|
|
11147
|
+
eventKind,
|
|
11148
|
+
hookEventName: payload.hook_event_name ?? null,
|
|
11149
|
+
sessionId,
|
|
11150
|
+
conversationId: stringOrNull(payload.conversation_id),
|
|
11151
|
+
turnId,
|
|
11152
|
+
eventNonce,
|
|
11153
|
+
...await transcriptFingerprint(payload)
|
|
11154
|
+
};
|
|
11155
|
+
const eventId = crypto.createHash("sha256").update(JSON.stringify(fingerprint)).digest("hex").slice(0, 32);
|
|
11156
|
+
return {
|
|
11157
|
+
eventId,
|
|
11158
|
+
repoRoot: project.repoRoot,
|
|
11159
|
+
config: project.config,
|
|
11160
|
+
tool,
|
|
11161
|
+
eventKind,
|
|
11162
|
+
hookEventName: payload.hook_event_name ?? null,
|
|
11163
|
+
sessionId,
|
|
11164
|
+
expectedKinds: expected
|
|
11165
|
+
};
|
|
11166
|
+
}
|
|
11167
|
+
async function readState(eventId) {
|
|
11168
|
+
try {
|
|
11169
|
+
const raw = await fs9.promises.readFile(stateFile(eventId), "utf-8");
|
|
11170
|
+
const parsed = JSON.parse(raw);
|
|
11171
|
+
if (parsed.schemaVersion !== CURRENT_SCHEMA_VERSION) return null;
|
|
11172
|
+
return parsed;
|
|
11173
|
+
} catch {
|
|
11174
|
+
return null;
|
|
11175
|
+
}
|
|
11176
|
+
}
|
|
11177
|
+
async function writeState(state) {
|
|
11178
|
+
await fs9.promises.mkdir(stateDir(), { recursive: true, mode: 448 });
|
|
11179
|
+
const file = stateFile(state.eventId);
|
|
11180
|
+
const tmp = `${file}.tmp`;
|
|
11181
|
+
await fs9.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
|
|
11182
|
+
mode: 384
|
|
11183
|
+
});
|
|
11184
|
+
await fs9.promises.rename(tmp, file);
|
|
11185
|
+
}
|
|
11186
|
+
async function acquireLock(eventId) {
|
|
11187
|
+
await fs9.promises.mkdir(stateDir(), { recursive: true, mode: 448 });
|
|
11188
|
+
for (let i = 0; i < LOCK_RETRIES; i++) {
|
|
11189
|
+
try {
|
|
11190
|
+
const fd = await fs9.promises.open(
|
|
11191
|
+
lockFile(eventId),
|
|
11192
|
+
fs9.constants.O_CREAT | fs9.constants.O_EXCL | fs9.constants.O_WRONLY
|
|
11193
|
+
);
|
|
11194
|
+
await fd.write(String(process.pid));
|
|
11195
|
+
await fd.close();
|
|
11196
|
+
return;
|
|
11197
|
+
} catch (err) {
|
|
11198
|
+
if (err.code === "EEXIST" && i < LOCK_RETRIES - 1) {
|
|
11199
|
+
await sleep2(LOCK_RETRY_DELAY_MS);
|
|
11200
|
+
continue;
|
|
11201
|
+
}
|
|
11202
|
+
throw err;
|
|
11203
|
+
}
|
|
11204
|
+
}
|
|
11205
|
+
}
|
|
11206
|
+
async function releaseLock(eventId) {
|
|
11207
|
+
try {
|
|
11208
|
+
await fs9.promises.unlink(lockFile(eventId));
|
|
11209
|
+
} catch {
|
|
11210
|
+
}
|
|
11211
|
+
}
|
|
11212
|
+
function initialState(ctx, now) {
|
|
11213
|
+
return {
|
|
11214
|
+
schemaVersion: CURRENT_SCHEMA_VERSION,
|
|
11215
|
+
eventId: ctx.eventId,
|
|
11216
|
+
apiBaseUrl: ctx.config.apiBaseUrl,
|
|
11217
|
+
projectId: ctx.config.projectId,
|
|
11218
|
+
projectSlug: ctx.config.projectSlug,
|
|
11219
|
+
repoRoot: ctx.repoRoot,
|
|
11220
|
+
tool: ctx.tool,
|
|
11221
|
+
eventKind: ctx.eventKind,
|
|
11222
|
+
hookEventName: ctx.hookEventName,
|
|
11223
|
+
sessionId: ctx.sessionId,
|
|
11224
|
+
logDate: path11.basename(todayLogPath(), ".log"),
|
|
11225
|
+
firstSeenAt: now.toISOString()
|
|
11226
|
+
};
|
|
11227
|
+
}
|
|
11228
|
+
function markDone(state, kind, now) {
|
|
11229
|
+
if (kind === "agent") {
|
|
11230
|
+
return { ...state, agentDoneAt: state.agentDoneAt ?? now.toISOString() };
|
|
11231
|
+
}
|
|
11232
|
+
return { ...state, gitDoneAt: state.gitDoneAt ?? now.toISOString() };
|
|
11233
|
+
}
|
|
11234
|
+
function expectedComplete(state, expected) {
|
|
11235
|
+
if (expected.has("agent") && !state.agentDoneAt) return false;
|
|
11236
|
+
if (expected.has("git") && !state.gitDoneAt) return false;
|
|
11237
|
+
return true;
|
|
11238
|
+
}
|
|
11239
|
+
function formatKinds(kinds) {
|
|
11240
|
+
return [...kinds].sort().join("+") || "<none>";
|
|
11241
|
+
}
|
|
11242
|
+
function observedKinds(state) {
|
|
11243
|
+
const observed = [];
|
|
11244
|
+
if (state.agentDoneAt) observed.push("agent");
|
|
11245
|
+
if (state.gitDoneAt) observed.push("git");
|
|
11246
|
+
return observed;
|
|
11247
|
+
}
|
|
11248
|
+
function elapsedSince(iso, now) {
|
|
11249
|
+
const started = Date.parse(iso);
|
|
11250
|
+
return Number.isNaN(started) ? 0 : Math.max(0, now.getTime() - started);
|
|
11251
|
+
}
|
|
11252
|
+
async function recordMarker(ctx, kind) {
|
|
11253
|
+
await acquireLock(ctx.eventId);
|
|
11254
|
+
try {
|
|
11255
|
+
const now = /* @__PURE__ */ new Date();
|
|
11256
|
+
const state = await readState(ctx.eventId) ?? initialState(ctx, now);
|
|
11257
|
+
const next = markDone(state, kind, now);
|
|
11258
|
+
await writeState(next);
|
|
11259
|
+
return next;
|
|
11260
|
+
} finally {
|
|
11261
|
+
await releaseLock(ctx.eventId);
|
|
11262
|
+
}
|
|
11263
|
+
}
|
|
11264
|
+
async function waitForExpectedKinds(ctx, state) {
|
|
11265
|
+
if (expectedComplete(state, ctx.expectedKinds) || state.uploadedAt) {
|
|
11266
|
+
return state;
|
|
11267
|
+
}
|
|
11268
|
+
const started = Date.now();
|
|
11269
|
+
const maxWaitMs = waitMs();
|
|
11270
|
+
while (Date.now() - started < maxWaitMs) {
|
|
11271
|
+
await sleep2(Math.min(250, Math.max(25, maxWaitMs)));
|
|
11272
|
+
const latest = await readState(ctx.eventId);
|
|
11273
|
+
if (!latest) continue;
|
|
11274
|
+
if (expectedComplete(latest, ctx.expectedKinds) || latest.uploadedAt) {
|
|
11275
|
+
return latest;
|
|
11276
|
+
}
|
|
11277
|
+
}
|
|
11278
|
+
appendLog(
|
|
11279
|
+
"warn",
|
|
11280
|
+
`debug-logs: timed out waiting for ${[...ctx.expectedKinds].join("+")} completion (event=${ctx.eventId}, tool=${ctx.tool})`
|
|
11281
|
+
);
|
|
11282
|
+
return await readState(ctx.eventId) ?? state;
|
|
11283
|
+
}
|
|
11284
|
+
async function buildMiddleware(repoRoot) {
|
|
11285
|
+
const envFileNames = await discoverEnvFiles(repoRoot);
|
|
11286
|
+
const envFilePaths = envFileNames.map((n) => path11.join(repoRoot, n));
|
|
11287
|
+
const secretResult = await collectSecrets(repoRoot, envFilePaths, []);
|
|
11288
|
+
const middleware2 = [];
|
|
11289
|
+
if (secretResult.values.size > 0) {
|
|
11290
|
+
middleware2.push(new RedactMiddleware(secretResult.values));
|
|
11291
|
+
}
|
|
11292
|
+
middleware2.push(new PatternRedactMiddleware());
|
|
11293
|
+
return middleware2;
|
|
11294
|
+
}
|
|
11295
|
+
async function uploadDebugLog(ctx, state) {
|
|
11296
|
+
const logPath = todayLogPath();
|
|
11297
|
+
let content;
|
|
11298
|
+
try {
|
|
11299
|
+
content = await fs9.promises.readFile(logPath);
|
|
11300
|
+
} catch (err) {
|
|
11301
|
+
appendLog(
|
|
11302
|
+
"warn",
|
|
11303
|
+
`debug-logs: skipped upload; log file not readable (${logPath}): ${err instanceof Error ? err.message : String(err)}`
|
|
11304
|
+
);
|
|
11305
|
+
return null;
|
|
11306
|
+
}
|
|
11307
|
+
if (content.byteLength === 0) {
|
|
11308
|
+
appendLog("info", "debug-logs: skipped upload; log file is empty");
|
|
11309
|
+
return null;
|
|
11310
|
+
}
|
|
11311
|
+
const identity = await loadIdentity(ctx.config.apiBaseUrl);
|
|
11312
|
+
if (!identity) {
|
|
11313
|
+
appendLog(
|
|
11314
|
+
"warn",
|
|
11315
|
+
`debug-logs: skipped upload; no saved login for ${ctx.config.apiBaseUrl}`
|
|
11316
|
+
);
|
|
11317
|
+
return null;
|
|
11318
|
+
}
|
|
11319
|
+
const now = /* @__PURE__ */ new Date();
|
|
11320
|
+
appendLog(
|
|
11321
|
+
"info",
|
|
11322
|
+
`debug-logs: uploading event=${ctx.eventId} session=${ctx.sessionId ?? "<none>"} tool=${ctx.tool} expected=${formatKinds(ctx.expectedKinds)} observed=${formatKinds(observedKinds(state))} waitedMs=${elapsedSince(state.firstSeenAt, now)} project=${ctx.config.projectSlug} (${ctx.config.projectId}) apiBaseUrl=${ctx.config.apiBaseUrl}`
|
|
11323
|
+
);
|
|
11324
|
+
const sourceFile = {
|
|
11325
|
+
sourceName: DEBUG_LOGS_SLUG,
|
|
11326
|
+
absolutePath: logPath,
|
|
11327
|
+
repoPath: ctx.repoRoot,
|
|
11328
|
+
content
|
|
11329
|
+
};
|
|
11330
|
+
const group = {
|
|
11331
|
+
repoPath: ctx.repoRoot,
|
|
11332
|
+
label: path11.basename(ctx.repoRoot),
|
|
11333
|
+
files: [sourceFile],
|
|
11334
|
+
sourceNames: [DEBUG_LOGS_SLUG],
|
|
11335
|
+
lastModified: now
|
|
11336
|
+
};
|
|
11337
|
+
const shortSession = (ctx.sessionId ?? ctx.eventId).slice(0, 12);
|
|
11338
|
+
const epochSeconds = formatEpochSeconds(now);
|
|
11339
|
+
const label = toolLabel(ctx.tool);
|
|
11340
|
+
const client = new PlatformClient(
|
|
11341
|
+
ctx.config.apiBaseUrl,
|
|
11342
|
+
identity.sessionCookie
|
|
11343
|
+
);
|
|
11344
|
+
const output = new PlatformUploadOutput({
|
|
11345
|
+
client,
|
|
11346
|
+
projectId: ctx.config.projectId,
|
|
11347
|
+
contributionTypeSlug: DEBUG_LOGS_SLUG,
|
|
11348
|
+
contributionTitle: `${label} debug log ${shortSession} - ${epochSeconds}`,
|
|
11349
|
+
contributionBody: [
|
|
11350
|
+
`Session ID: ${ctx.sessionId ?? "<none>"}`,
|
|
11351
|
+
`Tool: ${label}`,
|
|
11352
|
+
`Event: ${ctx.hookEventName ?? ctx.eventKind}`,
|
|
11353
|
+
`Repo: ${ctx.repoRoot}`,
|
|
11354
|
+
`Log: ${path11.basename(logPath)}`,
|
|
11355
|
+
`Agent done: ${state.agentDoneAt ?? "<not observed>"}`,
|
|
11356
|
+
`Git done: ${state.gitDoneAt ?? "<not observed>"}`,
|
|
11357
|
+
`Uploaded: ${now.toISOString()}`
|
|
11358
|
+
].join("\n"),
|
|
11359
|
+
zipFilename: `hillclimb-debug-log-${sanitize(ctx.tool)}-${sanitize(shortSession)}-${epochSeconds}.zip`,
|
|
11360
|
+
autoSubmit: true
|
|
11361
|
+
});
|
|
11362
|
+
try {
|
|
11363
|
+
const contributionId = await runPipeline(
|
|
11364
|
+
group,
|
|
11365
|
+
await buildMiddleware(ctx.repoRoot),
|
|
11366
|
+
output,
|
|
11367
|
+
{ selectedSources: [DEBUG_LOGS_SLUG] }
|
|
11368
|
+
);
|
|
11369
|
+
appendLog(
|
|
11370
|
+
"info",
|
|
11371
|
+
`debug-logs: uploaded ${path11.basename(logPath)} to project ${ctx.config.projectSlug} (${ctx.config.projectId}) as contribution ${contributionId}`
|
|
11372
|
+
);
|
|
11373
|
+
return contributionId;
|
|
11374
|
+
} catch (err) {
|
|
11375
|
+
if (err instanceof PlatformError && err.status === 404) {
|
|
11376
|
+
appendLog(
|
|
11377
|
+
"warn",
|
|
11378
|
+
"debug-logs: upload skipped; platform does not have the debug-logs contribution type yet"
|
|
11379
|
+
);
|
|
11380
|
+
return null;
|
|
11381
|
+
}
|
|
11382
|
+
appendLog(
|
|
11383
|
+
"error",
|
|
11384
|
+
`debug-logs: upload failed: ${err instanceof Error ? err.message : String(err)}`
|
|
11385
|
+
);
|
|
11386
|
+
return null;
|
|
11387
|
+
}
|
|
11388
|
+
}
|
|
11389
|
+
async function uploadOnce(ctx, state) {
|
|
11390
|
+
await acquireLock(ctx.eventId);
|
|
11391
|
+
try {
|
|
11392
|
+
const latest = await readState(ctx.eventId) ?? state;
|
|
11393
|
+
if (latest.uploadedAt) return;
|
|
11394
|
+
const contributionId = await uploadDebugLog(ctx, latest);
|
|
11395
|
+
if (!contributionId) return;
|
|
11396
|
+
const uploadedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11397
|
+
await writeState({
|
|
11398
|
+
...latest,
|
|
11399
|
+
uploadedAt,
|
|
11400
|
+
uploadContributionId: contributionId ?? void 0
|
|
11401
|
+
});
|
|
11402
|
+
} finally {
|
|
11403
|
+
await releaseLock(ctx.eventId);
|
|
11404
|
+
}
|
|
11405
|
+
}
|
|
11406
|
+
async function recordDebugLogCompletion(args) {
|
|
11407
|
+
if (process.env.HILLCLIMB_DEBUG_LOG_UPLOAD === "0") return;
|
|
11408
|
+
try {
|
|
11409
|
+
const ctx = await eventContext(args.tool, args.payload);
|
|
11410
|
+
if (!ctx) return;
|
|
11411
|
+
const marked = await recordMarker(ctx, args.kind);
|
|
11412
|
+
const ready = await waitForExpectedKinds(ctx, marked);
|
|
11413
|
+
if (ready.uploadedAt) return;
|
|
11414
|
+
await uploadOnce(ctx, ready);
|
|
11415
|
+
} catch (err) {
|
|
11416
|
+
appendLog(
|
|
11417
|
+
"warn",
|
|
11418
|
+
`debug-logs: failed to record completion: ${err instanceof Error ? err.message : String(err)}`
|
|
11419
|
+
);
|
|
11420
|
+
}
|
|
11421
|
+
}
|
|
11422
|
+
|
|
11423
|
+
// src/normalizer/index.ts
|
|
11424
|
+
import path12 from "path";
|
|
11425
|
+
|
|
11426
|
+
// src/normalizer/claude.ts
|
|
11427
|
+
function stringify(value) {
|
|
11428
|
+
if (typeof value === "string") return value;
|
|
11429
|
+
try {
|
|
11430
|
+
return JSON.stringify(value);
|
|
11431
|
+
} catch {
|
|
11432
|
+
return String(value);
|
|
11433
|
+
}
|
|
11434
|
+
}
|
|
11435
|
+
function extractTextReasoningToolUses(content) {
|
|
11436
|
+
if (typeof content === "string") {
|
|
11437
|
+
return [content.trim(), void 0, []];
|
|
11438
|
+
}
|
|
11439
|
+
const textParts = [];
|
|
11440
|
+
const reasoningParts = [];
|
|
11441
|
+
const toolBlocks = [];
|
|
11442
|
+
if (Array.isArray(content)) {
|
|
11443
|
+
for (const block of content) {
|
|
11444
|
+
if (typeof block !== "object" || block === null) {
|
|
11445
|
+
textParts.push(stringify(block));
|
|
11446
|
+
continue;
|
|
11447
|
+
}
|
|
11448
|
+
const b = block;
|
|
11449
|
+
const blockType = b.type;
|
|
11450
|
+
if (blockType === "tool_use") {
|
|
11451
|
+
toolBlocks.push(b);
|
|
11452
|
+
continue;
|
|
11453
|
+
}
|
|
11454
|
+
if (blockType === "thinking" || blockType === "reasoning" || blockType === "analysis") {
|
|
11455
|
+
const textValue2 = b.text !== void 0 && b.text !== null ? b.text : b.thinking;
|
|
11456
|
+
if (typeof textValue2 === "string") {
|
|
11457
|
+
reasoningParts.push(textValue2.trim());
|
|
11458
|
+
} else {
|
|
11459
|
+
reasoningParts.push(stringify(textValue2));
|
|
11460
|
+
}
|
|
11461
|
+
continue;
|
|
11462
|
+
}
|
|
11463
|
+
if (blockType === "code" && typeof b.code === "string") {
|
|
11464
|
+
textParts.push(b.code);
|
|
11465
|
+
continue;
|
|
11466
|
+
}
|
|
11467
|
+
const textValue = b.text;
|
|
11468
|
+
if (typeof textValue === "string") {
|
|
11469
|
+
textParts.push(textValue);
|
|
11470
|
+
} else {
|
|
11471
|
+
textParts.push(stringify(b));
|
|
11472
|
+
}
|
|
11473
|
+
}
|
|
11474
|
+
} else if (content !== void 0 && content !== null) {
|
|
11475
|
+
textParts.push(stringify(content));
|
|
11476
|
+
}
|
|
11477
|
+
const text3 = textParts.filter((p7) => p7 && p7.trim()).map((p7) => p7.trim()).join("\n\n");
|
|
11478
|
+
const reasoning = reasoningParts.filter((p7) => p7 && p7.trim()).map((p7) => p7.trim()).join("\n\n");
|
|
11479
|
+
return [text3, reasoning || void 0, toolBlocks];
|
|
11480
|
+
}
|
|
11481
|
+
function buildMetrics(usage) {
|
|
11482
|
+
if (typeof usage !== "object" || usage === null) return void 0;
|
|
11483
|
+
const u = usage;
|
|
11484
|
+
const cachedTokens = u.cache_read_input_tokens || 0;
|
|
11485
|
+
const creation = u.cache_creation_input_tokens || 0;
|
|
11486
|
+
const inputTokens = u.input_tokens || 0;
|
|
11487
|
+
const promptTokens = inputTokens + cachedTokens + creation;
|
|
11488
|
+
const completionTokens = u.output_tokens || 0;
|
|
11489
|
+
const extra = {};
|
|
11490
|
+
for (const [key, value] of Object.entries(u)) {
|
|
11491
|
+
if (key === "input_tokens" || key === "output_tokens") continue;
|
|
11492
|
+
extra[key] = value;
|
|
11493
|
+
}
|
|
11494
|
+
return {
|
|
11495
|
+
prompt_tokens: promptTokens,
|
|
11496
|
+
completion_tokens: completionTokens,
|
|
11497
|
+
cached_tokens: cachedTokens,
|
|
11498
|
+
extra: Object.keys(extra).length > 0 ? extra : void 0
|
|
11499
|
+
};
|
|
11500
|
+
}
|
|
11501
|
+
function formatToolResult(block, toolUseResult) {
|
|
11502
|
+
const parts = [];
|
|
11503
|
+
const content = block.content;
|
|
11504
|
+
if (typeof content === "string") {
|
|
11505
|
+
if (content.trim()) parts.push(content.trim());
|
|
11506
|
+
} else if (Array.isArray(content)) {
|
|
11507
|
+
for (const item of content) {
|
|
11508
|
+
const text3 = stringify(item);
|
|
11509
|
+
if (text3.trim()) parts.push(text3.trim());
|
|
11510
|
+
}
|
|
11511
|
+
} else if (content !== void 0 && content !== null && content !== "") {
|
|
11512
|
+
parts.push(stringify(content));
|
|
11513
|
+
}
|
|
11514
|
+
let metadata;
|
|
11515
|
+
if (toolUseResult && typeof toolUseResult === "object") {
|
|
11516
|
+
metadata = { tool_use_result: toolUseResult };
|
|
11517
|
+
const stdout = toolUseResult.stdout;
|
|
11518
|
+
const stderr = toolUseResult.stderr;
|
|
11519
|
+
const exitCode = toolUseResult.exitCode ?? toolUseResult.exit_code;
|
|
11520
|
+
const interrupted = toolUseResult.interrupted;
|
|
11521
|
+
const isImage = toolUseResult.isImage;
|
|
11522
|
+
const formatted = [];
|
|
11523
|
+
if (stdout) formatted.push(`[stdout]
|
|
11524
|
+
${stdout}`.trimEnd());
|
|
11525
|
+
if (stderr) formatted.push(`[stderr]
|
|
11526
|
+
${stderr}`.trimEnd());
|
|
11527
|
+
if (exitCode !== void 0 && exitCode !== null && exitCode !== 0)
|
|
11528
|
+
formatted.push(`[exit_code] ${exitCode}`);
|
|
11529
|
+
if (interrupted) formatted.push(`[interrupted] ${interrupted}`);
|
|
11530
|
+
if (isImage) formatted.push(`[is_image] ${isImage}`);
|
|
11531
|
+
const skipKeys = /* @__PURE__ */ new Set([
|
|
11532
|
+
"stdout",
|
|
11533
|
+
"stderr",
|
|
11534
|
+
"exitCode",
|
|
11535
|
+
"exit_code",
|
|
11536
|
+
"interrupted",
|
|
11537
|
+
"isImage"
|
|
11538
|
+
]);
|
|
11539
|
+
const remainingMeta = {};
|
|
11540
|
+
for (const [k, v] of Object.entries(toolUseResult)) {
|
|
11541
|
+
if (!skipKeys.has(k)) remainingMeta[k] = v;
|
|
11542
|
+
}
|
|
11543
|
+
if (Object.keys(remainingMeta).length > 0) {
|
|
11544
|
+
formatted.push(`[metadata] ${JSON.stringify(remainingMeta)}`);
|
|
11545
|
+
}
|
|
11546
|
+
if (formatted.length > 0) {
|
|
11547
|
+
parts.push(formatted.filter(Boolean).join("\n"));
|
|
11548
|
+
}
|
|
11549
|
+
}
|
|
11550
|
+
if (block.is_error === true) {
|
|
11551
|
+
parts.push("[error] tool reported failure");
|
|
11552
|
+
metadata = metadata || {};
|
|
11553
|
+
metadata.is_error = true;
|
|
11554
|
+
}
|
|
11555
|
+
if (metadata !== void 0) {
|
|
11556
|
+
if (!("raw_tool_result" in metadata)) {
|
|
11557
|
+
metadata.raw_tool_result = block;
|
|
11558
|
+
}
|
|
11559
|
+
}
|
|
11560
|
+
const resultText = parts.filter(Boolean).join("\n\n").trim();
|
|
11561
|
+
return [resultText || void 0, metadata];
|
|
11562
|
+
}
|
|
11563
|
+
function convertClaudeToTrajectory(jsonlContent, sessionId) {
|
|
11564
|
+
const rawEvents = [];
|
|
11565
|
+
for (const line of jsonlContent.split("\n")) {
|
|
11566
|
+
const trimmed = line.trim();
|
|
11567
|
+
if (!trimmed) continue;
|
|
11568
|
+
try {
|
|
11569
|
+
rawEvents.push(JSON.parse(trimmed));
|
|
11570
|
+
} catch {
|
|
11571
|
+
}
|
|
11572
|
+
}
|
|
11573
|
+
if (rawEvents.length === 0) return null;
|
|
11574
|
+
const events = rawEvents.sort(
|
|
11575
|
+
(a, b) => (a.timestamp ?? "").localeCompare(b.timestamp ?? "")
|
|
11576
|
+
);
|
|
11577
|
+
if (events.length === 0) return null;
|
|
11002
11578
|
let sid = sessionId ?? "";
|
|
11003
11579
|
for (const event of events) {
|
|
11004
11580
|
if (typeof event.sessionId === "string") {
|
|
@@ -12450,308 +13026,126 @@ function convertRunEventsToTrajectory(events, sessionId) {
|
|
|
12450
13026
|
}
|
|
12451
13027
|
}
|
|
12452
13028
|
const steps = [];
|
|
12453
|
-
for (const turn of turns) {
|
|
12454
|
-
const parts = [...turn.parts];
|
|
12455
|
-
if (turn.finish) {
|
|
12456
|
-
parts.push({ ...turn.finish, type: "step-finish" });
|
|
12457
|
-
}
|
|
12458
|
-
const step = buildAgentStep(
|
|
12459
|
-
parts,
|
|
12460
|
-
{ time: { created: turn.timestamp } },
|
|
12461
|
-
steps.length + 1,
|
|
12462
|
-
void 0
|
|
12463
|
-
);
|
|
12464
|
-
if (step) steps.push(step);
|
|
12465
|
-
}
|
|
12466
|
-
if (steps.length === 0) return null;
|
|
12467
|
-
return {
|
|
12468
|
-
schema_version: ATIF_VERSION,
|
|
12469
|
-
session_id: session,
|
|
12470
|
-
agent: {
|
|
12471
|
-
name: "opencode",
|
|
12472
|
-
version: "unknown"
|
|
12473
|
-
},
|
|
12474
|
-
steps,
|
|
12475
|
-
final_metrics: finalMetricsFromSteps2(steps)
|
|
12476
|
-
};
|
|
12477
|
-
}
|
|
12478
|
-
function isRunEvent(lines) {
|
|
12479
|
-
return lines.some(
|
|
12480
|
-
(line) => ["step_start", "step_finish", "text", "reasoning", "tool_use"].includes(
|
|
12481
|
-
String(line.type ?? "")
|
|
12482
|
-
)
|
|
12483
|
-
);
|
|
12484
|
-
}
|
|
12485
|
-
function convertOpenCodeToTrajectory(content, sessionId) {
|
|
12486
|
-
const trimmed = content.trim();
|
|
12487
|
-
if (!trimmed) return null;
|
|
12488
|
-
try {
|
|
12489
|
-
const parsed = JSON.parse(trimmed);
|
|
12490
|
-
const parsedObj = asObject3(parsed);
|
|
12491
|
-
const messages = asArray(parsedObj?.messages);
|
|
12492
|
-
if (parsedObj && messages.length > 0) {
|
|
12493
|
-
const entries2 = messages.flatMap((message) => {
|
|
12494
|
-
const entry = entryFromLine(asObject3(message) ?? {});
|
|
12495
|
-
return entry ? [entry] : [];
|
|
12496
|
-
});
|
|
12497
|
-
return convertMessageEntriesToTrajectory(
|
|
12498
|
-
entries2,
|
|
12499
|
-
sessionId,
|
|
12500
|
-
asObject3(parsedObj.info)
|
|
12501
|
-
);
|
|
12502
|
-
}
|
|
12503
|
-
} catch {
|
|
12504
|
-
}
|
|
12505
|
-
const lines = parseJsonLines2(content);
|
|
12506
|
-
if (lines.length === 0) return null;
|
|
12507
|
-
if (isRunEvent(lines)) return convertRunEventsToTrajectory(lines, sessionId);
|
|
12508
|
-
const wrappedEntries = entriesFromEventWrappers(lines);
|
|
12509
|
-
const entries = wrappedEntries.length > 0 ? wrappedEntries : lines.flatMap((line) => {
|
|
12510
|
-
const entry = entryFromLine(line);
|
|
12511
|
-
return entry ? [entry] : [];
|
|
12512
|
-
});
|
|
12513
|
-
return convertMessageEntriesToTrajectory(entries, sessionId);
|
|
12514
|
-
}
|
|
12515
|
-
|
|
12516
|
-
// src/normalizer/index.ts
|
|
12517
|
-
function normalizeContent(sourceName, content, sessionId) {
|
|
12518
|
-
switch (sourceName) {
|
|
12519
|
-
case "claude":
|
|
12520
|
-
return convertClaudeToTrajectory(content, sessionId);
|
|
12521
|
-
case "codex":
|
|
12522
|
-
return convertCodexToTrajectory(content, sessionId);
|
|
12523
|
-
case "cursor":
|
|
12524
|
-
return convertCursorToTrajectory(content, sessionId);
|
|
12525
|
-
case "opencode":
|
|
12526
|
-
return convertOpenCodeToTrajectory(content, sessionId);
|
|
12527
|
-
case "copilot-chat":
|
|
12528
|
-
return convertCopilotChatToTrajectory(content, sessionId);
|
|
12529
|
-
default:
|
|
12530
|
-
return null;
|
|
12531
|
-
}
|
|
12532
|
-
}
|
|
12533
|
-
var NormalizeMiddleware = class {
|
|
12534
|
-
name = "normalize";
|
|
12535
|
-
async process(group) {
|
|
12536
|
-
const newFiles = [];
|
|
12537
|
-
for (const file of group.files) {
|
|
12538
|
-
newFiles.push(file);
|
|
12539
|
-
if (!file.absolutePath.endsWith(".jsonl")) continue;
|
|
12540
|
-
if (!["claude", "codex", "cursor", "opencode", "copilot-chat"].includes(
|
|
12541
|
-
file.sourceName
|
|
12542
|
-
))
|
|
12543
|
-
continue;
|
|
12544
|
-
const content = file.content ? file.content.toString("utf-8") : null;
|
|
12545
|
-
if (!content) continue;
|
|
12546
|
-
const sessionId = file.metadata?.sessionId ?? (file.sourceName === "codex" ? void 0 : path9.basename(file.absolutePath, ".jsonl"));
|
|
12547
|
-
try {
|
|
12548
|
-
const trajectory = normalizeContent(
|
|
12549
|
-
file.sourceName,
|
|
12550
|
-
content,
|
|
12551
|
-
sessionId
|
|
12552
|
-
);
|
|
12553
|
-
if (!trajectory) continue;
|
|
12554
|
-
const json = JSON.stringify(
|
|
12555
|
-
excludeNone(trajectory),
|
|
12556
|
-
null,
|
|
12557
|
-
2
|
|
12558
|
-
);
|
|
12559
|
-
const atifPath = file.absolutePath.replace(/\.jsonl$/, ".atif.json");
|
|
12560
|
-
newFiles.push({
|
|
12561
|
-
sourceName: file.sourceName,
|
|
12562
|
-
absolutePath: atifPath,
|
|
12563
|
-
repoPath: file.repoPath,
|
|
12564
|
-
metadata: { ...file.metadata, isAtif: true },
|
|
12565
|
-
content: Buffer.from(json, "utf-8")
|
|
12566
|
-
});
|
|
12567
|
-
} catch {
|
|
12568
|
-
}
|
|
12569
|
-
}
|
|
12570
|
-
return { ...group, files: newFiles };
|
|
12571
|
-
}
|
|
12572
|
-
};
|
|
12573
|
-
|
|
12574
|
-
// src/outputs/platform.ts
|
|
12575
|
-
import { PassThrough } from "stream";
|
|
12576
|
-
import archiver from "archiver";
|
|
12577
|
-
|
|
12578
|
-
// src/outputs/archive.ts
|
|
12579
|
-
import os4 from "os";
|
|
12580
|
-
import path10 from "path";
|
|
12581
|
-
function getSourceBaseDir(sourceName) {
|
|
12582
|
-
const home = os4.homedir();
|
|
12583
|
-
switch (sourceName) {
|
|
12584
|
-
case "claude":
|
|
12585
|
-
return path10.join(home, ".claude", "projects");
|
|
12586
|
-
case "codex":
|
|
12587
|
-
return path10.join(home, ".codex", "sessions");
|
|
12588
|
-
default:
|
|
12589
|
-
return home;
|
|
12590
|
-
}
|
|
12591
|
-
}
|
|
12592
|
-
function addGroupToArchive(archive, group, selectedSources) {
|
|
12593
|
-
for (const file of group.files) {
|
|
12594
|
-
if (!selectedSources.has(file.sourceName)) continue;
|
|
12595
|
-
const baseDir = getSourceBaseDir(file.sourceName);
|
|
12596
|
-
const relativePath = file.absolutePath.startsWith(baseDir) ? path10.relative(baseDir, file.absolutePath) : path10.basename(file.absolutePath);
|
|
12597
|
-
const archivePath = path10.join(file.sourceName, relativePath);
|
|
12598
|
-
if (file.content) {
|
|
12599
|
-
archive.append(file.content, { name: archivePath });
|
|
12600
|
-
} else {
|
|
12601
|
-
archive.file(file.absolutePath, { name: archivePath });
|
|
12602
|
-
}
|
|
12603
|
-
}
|
|
12604
|
-
}
|
|
12605
|
-
|
|
12606
|
-
// src/outputs/platform.ts
|
|
12607
|
-
async function buildZipBuffer(group, selectedSources) {
|
|
12608
|
-
const archive = archiver("zip", { zlib: { level: 6 } });
|
|
12609
|
-
const stream = new PassThrough();
|
|
12610
|
-
archive.pipe(stream);
|
|
12611
|
-
const chunks = [];
|
|
12612
|
-
const done = new Promise((resolve, reject) => {
|
|
12613
|
-
stream.on("data", (chunk) => chunks.push(chunk));
|
|
12614
|
-
stream.on("end", resolve);
|
|
12615
|
-
stream.on("error", reject);
|
|
12616
|
-
archive.on("error", reject);
|
|
12617
|
-
});
|
|
12618
|
-
addGroupToArchive(archive, group, selectedSources);
|
|
12619
|
-
await archive.finalize();
|
|
12620
|
-
await done;
|
|
12621
|
-
return Buffer.concat(chunks);
|
|
12622
|
-
}
|
|
12623
|
-
var PlatformUploadOutput = class {
|
|
12624
|
-
constructor(opts) {
|
|
12625
|
-
this.opts = opts;
|
|
12626
|
-
}
|
|
12627
|
-
name = "platform";
|
|
12628
|
-
label = "Upload to hillclimb platform";
|
|
12629
|
-
async emit(group, options) {
|
|
12630
|
-
const selectedSources = new Set(options.selectedSources);
|
|
12631
|
-
const buffer = await buildZipBuffer(group, selectedSources);
|
|
12632
|
-
const {
|
|
12633
|
-
client,
|
|
12634
|
-
projectId,
|
|
12635
|
-
contributionTypeSlug,
|
|
12636
|
-
contributionTitle,
|
|
12637
|
-
contributionBody,
|
|
12638
|
-
zipFilename,
|
|
12639
|
-
autoSubmit
|
|
12640
|
-
} = this.opts;
|
|
12641
|
-
const contribution = await client.createContribution(projectId, {
|
|
12642
|
-
contributionTypeSlug,
|
|
12643
|
-
title: contributionTitle,
|
|
12644
|
-
body: contributionBody
|
|
12645
|
-
});
|
|
12646
|
-
const presigned = await client.createUpload(contribution.id, {
|
|
12647
|
-
originalFilename: zipFilename,
|
|
12648
|
-
mimeType: "application/zip",
|
|
12649
|
-
sizeBytes: buffer.byteLength
|
|
12650
|
-
});
|
|
12651
|
-
appendLog(
|
|
12652
|
-
"info",
|
|
12653
|
-
`uploading ${zipFilename} (${buffer.byteLength} bytes) to presigned URL`
|
|
12654
|
-
);
|
|
12655
|
-
await client.uploadToPresignedUrl(
|
|
12656
|
-
presigned.presignedUrl,
|
|
12657
|
-
presigned.headers,
|
|
12658
|
-
buffer
|
|
12659
|
-
);
|
|
12660
|
-
appendLog("info", `PUT to presigned URL succeeded for ${zipFilename}`);
|
|
12661
|
-
if (autoSubmit) {
|
|
12662
|
-
appendLog("info", `submitting contribution ${contribution.id}`);
|
|
12663
|
-
await client.submitContribution(contribution.id);
|
|
12664
|
-
appendLog("info", `contribution ${contribution.id} submitted`);
|
|
12665
|
-
}
|
|
12666
|
-
return contribution.id;
|
|
12667
|
-
}
|
|
12668
|
-
};
|
|
12669
|
-
|
|
12670
|
-
// src/pipeline.ts
|
|
12671
|
-
import fs8 from "fs";
|
|
12672
|
-
import path11 from "path";
|
|
12673
|
-
function canonicalizePath(p7) {
|
|
12674
|
-
let resolved = path11.resolve(p7);
|
|
12675
|
-
if (resolved.endsWith(path11.sep) && resolved !== path11.sep) {
|
|
12676
|
-
resolved = resolved.slice(0, -1);
|
|
13029
|
+
for (const turn of turns) {
|
|
13030
|
+
const parts = [...turn.parts];
|
|
13031
|
+
if (turn.finish) {
|
|
13032
|
+
parts.push({ ...turn.finish, type: "step-finish" });
|
|
13033
|
+
}
|
|
13034
|
+
const step = buildAgentStep(
|
|
13035
|
+
parts,
|
|
13036
|
+
{ time: { created: turn.timestamp } },
|
|
13037
|
+
steps.length + 1,
|
|
13038
|
+
void 0
|
|
13039
|
+
);
|
|
13040
|
+
if (step) steps.push(step);
|
|
12677
13041
|
}
|
|
12678
|
-
return
|
|
13042
|
+
if (steps.length === 0) return null;
|
|
13043
|
+
return {
|
|
13044
|
+
schema_version: ATIF_VERSION,
|
|
13045
|
+
session_id: session,
|
|
13046
|
+
agent: {
|
|
13047
|
+
name: "opencode",
|
|
13048
|
+
version: "unknown"
|
|
13049
|
+
},
|
|
13050
|
+
steps,
|
|
13051
|
+
final_metrics: finalMetricsFromSteps2(steps)
|
|
13052
|
+
};
|
|
12679
13053
|
}
|
|
12680
|
-
function
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12685
|
-
|
|
12686
|
-
return s.slice(-depth).join("/") === label;
|
|
12687
|
-
});
|
|
12688
|
-
if (matches.length === 1) return label;
|
|
12689
|
-
}
|
|
12690
|
-
return repoPath;
|
|
13054
|
+
function isRunEvent(lines) {
|
|
13055
|
+
return lines.some(
|
|
13056
|
+
(line) => ["step_start", "step_finish", "text", "reasoning", "tool_use"].includes(
|
|
13057
|
+
String(line.type ?? "")
|
|
13058
|
+
)
|
|
13059
|
+
);
|
|
12691
13060
|
}
|
|
12692
|
-
|
|
12693
|
-
const
|
|
12694
|
-
|
|
12695
|
-
|
|
12696
|
-
const
|
|
12697
|
-
|
|
12698
|
-
|
|
12699
|
-
|
|
12700
|
-
|
|
13061
|
+
function convertOpenCodeToTrajectory(content, sessionId) {
|
|
13062
|
+
const trimmed = content.trim();
|
|
13063
|
+
if (!trimmed) return null;
|
|
13064
|
+
try {
|
|
13065
|
+
const parsed = JSON.parse(trimmed);
|
|
13066
|
+
const parsedObj = asObject3(parsed);
|
|
13067
|
+
const messages = asArray(parsedObj?.messages);
|
|
13068
|
+
if (parsedObj && messages.length > 0) {
|
|
13069
|
+
const entries2 = messages.flatMap((message) => {
|
|
13070
|
+
const entry = entryFromLine(asObject3(message) ?? {});
|
|
13071
|
+
return entry ? [entry] : [];
|
|
13072
|
+
});
|
|
13073
|
+
return convertMessageEntriesToTrajectory(
|
|
13074
|
+
entries2,
|
|
13075
|
+
sessionId,
|
|
13076
|
+
asObject3(parsedObj.info)
|
|
13077
|
+
);
|
|
12701
13078
|
}
|
|
13079
|
+
} catch {
|
|
12702
13080
|
}
|
|
12703
|
-
const
|
|
12704
|
-
|
|
12705
|
-
|
|
12706
|
-
|
|
12707
|
-
|
|
12708
|
-
);
|
|
12709
|
-
|
|
12710
|
-
|
|
12711
|
-
|
|
12712
|
-
|
|
12713
|
-
|
|
12714
|
-
|
|
12715
|
-
|
|
12716
|
-
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
|
|
12720
|
-
|
|
13081
|
+
const lines = parseJsonLines2(content);
|
|
13082
|
+
if (lines.length === 0) return null;
|
|
13083
|
+
if (isRunEvent(lines)) return convertRunEventsToTrajectory(lines, sessionId);
|
|
13084
|
+
const wrappedEntries = entriesFromEventWrappers(lines);
|
|
13085
|
+
const entries = wrappedEntries.length > 0 ? wrappedEntries : lines.flatMap((line) => {
|
|
13086
|
+
const entry = entryFromLine(line);
|
|
13087
|
+
return entry ? [entry] : [];
|
|
13088
|
+
});
|
|
13089
|
+
return convertMessageEntriesToTrajectory(entries, sessionId);
|
|
13090
|
+
}
|
|
13091
|
+
|
|
13092
|
+
// src/normalizer/index.ts
|
|
13093
|
+
function normalizeContent(sourceName, content, sessionId) {
|
|
13094
|
+
switch (sourceName) {
|
|
13095
|
+
case "claude":
|
|
13096
|
+
return convertClaudeToTrajectory(content, sessionId);
|
|
13097
|
+
case "codex":
|
|
13098
|
+
return convertCodexToTrajectory(content, sessionId);
|
|
13099
|
+
case "cursor":
|
|
13100
|
+
return convertCursorToTrajectory(content, sessionId);
|
|
13101
|
+
case "opencode":
|
|
13102
|
+
return convertOpenCodeToTrajectory(content, sessionId);
|
|
13103
|
+
case "copilot-chat":
|
|
13104
|
+
return convertCopilotChatToTrajectory(content, sessionId);
|
|
13105
|
+
default:
|
|
13106
|
+
return null;
|
|
12721
13107
|
}
|
|
12722
|
-
groups.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
12723
|
-
return groups.filter((g) => g.files.length > 0);
|
|
12724
13108
|
}
|
|
12725
|
-
|
|
12726
|
-
|
|
12727
|
-
|
|
12728
|
-
|
|
13109
|
+
var NormalizeMiddleware = class {
|
|
13110
|
+
name = "normalize";
|
|
13111
|
+
async process(group) {
|
|
13112
|
+
const newFiles = [];
|
|
13113
|
+
for (const file of group.files) {
|
|
13114
|
+
newFiles.push(file);
|
|
13115
|
+
if (!file.absolutePath.endsWith(".jsonl")) continue;
|
|
13116
|
+
if (!["claude", "codex", "cursor", "opencode", "copilot-chat"].includes(
|
|
13117
|
+
file.sourceName
|
|
13118
|
+
))
|
|
13119
|
+
continue;
|
|
13120
|
+
const content = file.content ? file.content.toString("utf-8") : null;
|
|
13121
|
+
if (!content) continue;
|
|
13122
|
+
const sessionId = file.metadata?.sessionId ?? (file.sourceName === "codex" ? void 0 : path12.basename(file.absolutePath, ".jsonl"));
|
|
12729
13123
|
try {
|
|
12730
|
-
const
|
|
12731
|
-
|
|
12732
|
-
|
|
12733
|
-
|
|
12734
|
-
|
|
12735
|
-
|
|
12736
|
-
|
|
12737
|
-
|
|
13124
|
+
const trajectory = normalizeContent(
|
|
13125
|
+
file.sourceName,
|
|
13126
|
+
content,
|
|
13127
|
+
sessionId
|
|
13128
|
+
);
|
|
13129
|
+
if (!trajectory) continue;
|
|
13130
|
+
const json = JSON.stringify(
|
|
13131
|
+
excludeNone(trajectory),
|
|
13132
|
+
null,
|
|
13133
|
+
2
|
|
13134
|
+
);
|
|
13135
|
+
const atifPath = file.absolutePath.replace(/\.jsonl$/, ".atif.json");
|
|
13136
|
+
newFiles.push({
|
|
13137
|
+
sourceName: file.sourceName,
|
|
13138
|
+
absolutePath: atifPath,
|
|
13139
|
+
repoPath: file.repoPath,
|
|
13140
|
+
metadata: { ...file.metadata, isAtif: true },
|
|
13141
|
+
content: Buffer.from(json, "utf-8")
|
|
13142
|
+
});
|
|
12738
13143
|
} catch {
|
|
12739
|
-
return file;
|
|
12740
13144
|
}
|
|
12741
|
-
}
|
|
12742
|
-
|
|
12743
|
-
return { ...group, files };
|
|
12744
|
-
}
|
|
12745
|
-
async function runPipeline(group, middleware2, output, options, onProgress) {
|
|
12746
|
-
const preloaded = await preloadFiles(group);
|
|
12747
|
-
let processed = preloaded;
|
|
12748
|
-
for (const mw of middleware2) {
|
|
12749
|
-
onProgress?.(`${mw.name} (${processed.files.length} files)`);
|
|
12750
|
-
processed = await mw.process(processed);
|
|
13145
|
+
}
|
|
13146
|
+
return { ...group, files: newFiles };
|
|
12751
13147
|
}
|
|
12752
|
-
|
|
12753
|
-
return output.emit(processed, options);
|
|
12754
|
-
}
|
|
13148
|
+
};
|
|
12755
13149
|
|
|
12756
13150
|
// src/commands/upload.ts
|
|
12757
13151
|
async function readStdin() {
|
|
@@ -12762,17 +13156,17 @@ async function readStdin() {
|
|
|
12762
13156
|
}
|
|
12763
13157
|
return Buffer.concat(chunks).toString("utf-8");
|
|
12764
13158
|
}
|
|
12765
|
-
function
|
|
13159
|
+
function sanitize2(value) {
|
|
12766
13160
|
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
12767
13161
|
}
|
|
12768
|
-
function
|
|
13162
|
+
function formatEpochSeconds2(date) {
|
|
12769
13163
|
return String(Math.floor(date.getTime() / 1e3));
|
|
12770
13164
|
}
|
|
12771
13165
|
function lineHasAssistant(line) {
|
|
12772
13166
|
return line.includes('"type":"assistant"') || line.includes('"role":"assistant"') || line.includes('"type":"assistant.');
|
|
12773
13167
|
}
|
|
12774
13168
|
async function hasAssistantMessage(transcriptPath) {
|
|
12775
|
-
const stream =
|
|
13169
|
+
const stream = fs10.createReadStream(transcriptPath, { encoding: "utf-8" });
|
|
12776
13170
|
let buffer = "";
|
|
12777
13171
|
try {
|
|
12778
13172
|
for await (const chunk of stream) {
|
|
@@ -12800,6 +13194,23 @@ function resolveSourceTool(payload) {
|
|
|
12800
13194
|
if (payload.hook_event_name === "Stop") return "codex";
|
|
12801
13195
|
return "claude";
|
|
12802
13196
|
}
|
|
13197
|
+
function summarizePayload(payload) {
|
|
13198
|
+
const sessionId = payload.session_id ?? payload.conversation_id ?? null;
|
|
13199
|
+
const turnId = payload.turn_id ?? null;
|
|
13200
|
+
const cwd = payload.cwd ?? payload.workspace_roots?.[0] ?? null;
|
|
13201
|
+
return JSON.stringify({
|
|
13202
|
+
session_id: sessionId,
|
|
13203
|
+
turn_id: turnId,
|
|
13204
|
+
cwd,
|
|
13205
|
+
hook_event_name: payload.hook_event_name ?? null,
|
|
13206
|
+
tool: payload.tool ?? null,
|
|
13207
|
+
model: payload.model ?? null,
|
|
13208
|
+
permission_mode: payload.permission_mode ?? null,
|
|
13209
|
+
transcript_path_present: !!payload.transcript_path,
|
|
13210
|
+
workspace_roots_count: payload.workspace_roots?.length ?? 0,
|
|
13211
|
+
cursor_version_present: !!payload.cursor_version
|
|
13212
|
+
});
|
|
13213
|
+
}
|
|
12803
13214
|
async function selfHealHook(repoRoot, tool) {
|
|
12804
13215
|
if (process.env.HILLCLIMB_SKIP_HOOK_SELF_HEAL === "1") return;
|
|
12805
13216
|
try {
|
|
@@ -12823,7 +13234,7 @@ function resolveCursorTranscriptPath(payload) {
|
|
|
12823
13234
|
const workspace = payload.workspace_roots?.[0];
|
|
12824
13235
|
if (!id || !workspace) return void 0;
|
|
12825
13236
|
const encoded = workspace.replace(/^\//, "").replace(/\//g, "-");
|
|
12826
|
-
return
|
|
13237
|
+
return path13.join(
|
|
12827
13238
|
os5.homedir(),
|
|
12828
13239
|
".cursor",
|
|
12829
13240
|
"projects",
|
|
@@ -12859,9 +13270,9 @@ async function runUploadInner(payload) {
|
|
|
12859
13270
|
}
|
|
12860
13271
|
const { repoRoot, config } = match;
|
|
12861
13272
|
await selfHealHook(repoRoot, sourceTool);
|
|
12862
|
-
const transcriptResolved =
|
|
13273
|
+
const transcriptResolved = path13.resolve(transcriptPath);
|
|
12863
13274
|
try {
|
|
12864
|
-
const stat = await
|
|
13275
|
+
const stat = await fs10.promises.stat(transcriptResolved);
|
|
12865
13276
|
if (!stat.isFile()) {
|
|
12866
13277
|
appendLog(
|
|
12867
13278
|
"warn",
|
|
@@ -12901,13 +13312,13 @@ async function uploadSession(args) {
|
|
|
12901
13312
|
};
|
|
12902
13313
|
const group = {
|
|
12903
13314
|
repoPath: repoRoot,
|
|
12904
|
-
label:
|
|
13315
|
+
label: path13.basename(repoRoot),
|
|
12905
13316
|
files: [sourceFile],
|
|
12906
13317
|
sourceNames: [sourceTool],
|
|
12907
13318
|
lastModified: now
|
|
12908
13319
|
};
|
|
12909
13320
|
const envFileNames = await discoverEnvFiles(repoRoot);
|
|
12910
|
-
const envFilePaths = envFileNames.map((n) =>
|
|
13321
|
+
const envFilePaths = envFileNames.map((n) => path13.join(repoRoot, n));
|
|
12911
13322
|
const secretResult = await collectSecrets(repoRoot, envFilePaths, []);
|
|
12912
13323
|
const mwChain = [];
|
|
12913
13324
|
if (secretResult.values.size > 0) {
|
|
@@ -12932,14 +13343,14 @@ async function uploadSession(args) {
|
|
|
12932
13343
|
"copilot-chat": "GitHub Copilot Chat",
|
|
12933
13344
|
opencode: "opencode"
|
|
12934
13345
|
};
|
|
12935
|
-
const
|
|
12936
|
-
const epochSeconds =
|
|
12937
|
-
const title = `${
|
|
13346
|
+
const toolLabel2 = toolLabels[sourceTool] ?? "Claude";
|
|
13347
|
+
const epochSeconds = formatEpochSeconds2(now);
|
|
13348
|
+
const title = `${toolLabel2} session ${shortId} \u2014 ${epochSeconds}`;
|
|
12938
13349
|
const body = `Session ID: ${sessionId}
|
|
12939
|
-
Tool: ${
|
|
13350
|
+
Tool: ${toolLabel2}
|
|
12940
13351
|
Repo: ${repoRoot}
|
|
12941
13352
|
Uploaded: ${now.toISOString()}`;
|
|
12942
|
-
const zipFilename = `${sourceTool}-${
|
|
13353
|
+
const zipFilename = `${sourceTool}-${sanitize2(shortId)}-${epochSeconds}.zip`;
|
|
12943
13354
|
const output = new PlatformUploadOutput({
|
|
12944
13355
|
client,
|
|
12945
13356
|
projectId: config.projectId,
|
|
@@ -13055,7 +13466,6 @@ async function runUploadWorker() {
|
|
|
13055
13466
|
appendLog("warn", "worker: invoked with empty stdin; payload expected.");
|
|
13056
13467
|
return;
|
|
13057
13468
|
}
|
|
13058
|
-
appendLog("info", `worker: raw payload: ${raw.trim()}`);
|
|
13059
13469
|
let payload;
|
|
13060
13470
|
try {
|
|
13061
13471
|
payload = JSON.parse(raw);
|
|
@@ -13068,6 +13478,7 @@ async function runUploadWorker() {
|
|
|
13068
13478
|
}
|
|
13069
13479
|
const toolOverride = process.env[TOOL_ENV_FLAG];
|
|
13070
13480
|
if (toolOverride) payload.tool = toolOverride;
|
|
13481
|
+
appendLog("info", `worker: payload summary: ${summarizePayload(payload)}`);
|
|
13071
13482
|
try {
|
|
13072
13483
|
await runUploadInner(payload);
|
|
13073
13484
|
} catch (err) {
|
|
@@ -13075,22 +13486,28 @@ async function runUploadWorker() {
|
|
|
13075
13486
|
"error",
|
|
13076
13487
|
`worker: unexpected error: ${err instanceof Error ? err.stack ?? err.message : String(err)}`
|
|
13077
13488
|
);
|
|
13489
|
+
} finally {
|
|
13490
|
+
await recordDebugLogCompletion({
|
|
13491
|
+
kind: "agent",
|
|
13492
|
+
tool: resolveSourceTool(payload),
|
|
13493
|
+
payload
|
|
13494
|
+
});
|
|
13078
13495
|
}
|
|
13079
13496
|
}
|
|
13080
13497
|
|
|
13081
13498
|
// src/git-traces/index.ts
|
|
13082
13499
|
import { spawn as spawn3 } from "child_process";
|
|
13083
|
-
import
|
|
13500
|
+
import crypto3 from "crypto";
|
|
13084
13501
|
|
|
13085
13502
|
// src/git-traces/handlers.ts
|
|
13086
13503
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
13087
|
-
import
|
|
13504
|
+
import path16 from "path";
|
|
13088
13505
|
|
|
13089
13506
|
// src/git-traces/git-ops.ts
|
|
13090
13507
|
import { execFileSync } from "child_process";
|
|
13091
|
-
import
|
|
13508
|
+
import fs11 from "fs";
|
|
13092
13509
|
import os6 from "os";
|
|
13093
|
-
import
|
|
13510
|
+
import path14 from "path";
|
|
13094
13511
|
import { gzipSync } from "zlib";
|
|
13095
13512
|
var GIT_COMMAND_TIMEOUT_MS = 12e4;
|
|
13096
13513
|
var EXEC_OPTS = {
|
|
@@ -13214,7 +13631,7 @@ function buildUntrackedTree(repoRoot) {
|
|
|
13214
13631
|
for (const relPath of list.split("\0")) {
|
|
13215
13632
|
if (!relPath) continue;
|
|
13216
13633
|
try {
|
|
13217
|
-
const stat =
|
|
13634
|
+
const stat = fs11.lstatSync(path14.join(repoRoot, relPath));
|
|
13218
13635
|
if (stat.size > MAX_UNTRACKED_FILE_BYTES) {
|
|
13219
13636
|
appendLog(
|
|
13220
13637
|
"warn",
|
|
@@ -13227,7 +13644,7 @@ function buildUntrackedTree(repoRoot) {
|
|
|
13227
13644
|
}
|
|
13228
13645
|
}
|
|
13229
13646
|
if (kept.length === 0) return null;
|
|
13230
|
-
const tmpIndex =
|
|
13647
|
+
const tmpIndex = path14.join(
|
|
13231
13648
|
os6.tmpdir(),
|
|
13232
13649
|
`hillclimb-untracked-${Date.now()}-${process.pid}`
|
|
13233
13650
|
);
|
|
@@ -13240,7 +13657,7 @@ function buildUntrackedTree(repoRoot) {
|
|
|
13240
13657
|
return gitWithEnv(repoRoot, ["write-tree"], env);
|
|
13241
13658
|
} finally {
|
|
13242
13659
|
try {
|
|
13243
|
-
|
|
13660
|
+
fs11.unlinkSync(tmpIndex);
|
|
13244
13661
|
} catch {
|
|
13245
13662
|
}
|
|
13246
13663
|
}
|
|
@@ -13251,7 +13668,7 @@ function buildSnapshotTree(repoRoot, stashSha) {
|
|
|
13251
13668
|
if (!untrackedTree || untrackedTree === EMPTY_TREE_SHA) {
|
|
13252
13669
|
return trackedTree;
|
|
13253
13670
|
}
|
|
13254
|
-
const tmpIndex =
|
|
13671
|
+
const tmpIndex = path14.join(
|
|
13255
13672
|
os6.tmpdir(),
|
|
13256
13673
|
`hillclimb-index-${Date.now()}-${process.pid}`
|
|
13257
13674
|
);
|
|
@@ -13279,7 +13696,7 @@ function buildSnapshotTree(repoRoot, stashSha) {
|
|
|
13279
13696
|
return gitWithEnv(repoRoot, ["write-tree"], env);
|
|
13280
13697
|
} finally {
|
|
13281
13698
|
try {
|
|
13282
|
-
|
|
13699
|
+
fs11.unlinkSync(tmpIndex);
|
|
13283
13700
|
} catch {
|
|
13284
13701
|
}
|
|
13285
13702
|
}
|
|
@@ -13293,16 +13710,16 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
13293
13710
|
]);
|
|
13294
13711
|
const orphanRef = `refs/hillclimb/bundle/${sessionId}`;
|
|
13295
13712
|
pinRef(repoRoot, orphanRef, orphanCommit);
|
|
13296
|
-
const tmpFile =
|
|
13713
|
+
const tmpFile = path14.join(
|
|
13297
13714
|
os6.tmpdir(),
|
|
13298
13715
|
`hillclimb-bundle-${Date.now()}.bundle`
|
|
13299
13716
|
);
|
|
13300
13717
|
try {
|
|
13301
13718
|
git(repoRoot, ["bundle", "create", tmpFile, orphanRef]);
|
|
13302
|
-
return
|
|
13719
|
+
return fs11.readFileSync(tmpFile);
|
|
13303
13720
|
} finally {
|
|
13304
13721
|
try {
|
|
13305
|
-
|
|
13722
|
+
fs11.unlinkSync(tmpFile);
|
|
13306
13723
|
} catch {
|
|
13307
13724
|
}
|
|
13308
13725
|
deleteRef(repoRoot, orphanRef);
|
|
@@ -13477,9 +13894,9 @@ function parseCommitFiles(repoRoot, sha) {
|
|
|
13477
13894
|
oldPath
|
|
13478
13895
|
});
|
|
13479
13896
|
} else {
|
|
13480
|
-
const
|
|
13481
|
-
indexByPath.set(
|
|
13482
|
-
files.push({ path:
|
|
13897
|
+
const path23 = parts[parts.length - 1];
|
|
13898
|
+
indexByPath.set(path23, files.length);
|
|
13899
|
+
files.push({ path: path23, status, additions: 0, deletions: 0 });
|
|
13483
13900
|
}
|
|
13484
13901
|
}
|
|
13485
13902
|
for (const line of numstat.split("\n")) {
|
|
@@ -13545,31 +13962,31 @@ function cleanupSessionRefs(repoRoot, sessionId) {
|
|
|
13545
13962
|
}
|
|
13546
13963
|
|
|
13547
13964
|
// src/git-traces/session-state.ts
|
|
13548
|
-
import
|
|
13549
|
-
import
|
|
13965
|
+
import crypto2 from "crypto";
|
|
13966
|
+
import fs12 from "fs";
|
|
13550
13967
|
import os7 from "os";
|
|
13551
|
-
import
|
|
13552
|
-
var
|
|
13553
|
-
var DEFAULT_STATE_DIR =
|
|
13554
|
-
var
|
|
13555
|
-
var
|
|
13556
|
-
function
|
|
13968
|
+
import path15 from "path";
|
|
13969
|
+
var CURRENT_SCHEMA_VERSION2 = 3;
|
|
13970
|
+
var DEFAULT_STATE_DIR = path15.join(os7.homedir(), ".hillclimb", "git-traces");
|
|
13971
|
+
var LOCK_RETRIES2 = 120;
|
|
13972
|
+
var LOCK_RETRY_DELAY_MS2 = 500;
|
|
13973
|
+
function stateDir2() {
|
|
13557
13974
|
return process.env.HILLCLIMB_GIT_TRACES_STATE_DIR ?? DEFAULT_STATE_DIR;
|
|
13558
13975
|
}
|
|
13559
13976
|
function stateFileForRepo(repoRoot, tool, sessionId) {
|
|
13560
|
-
const hash =
|
|
13561
|
-
sessionId ? `${
|
|
13977
|
+
const hash = crypto2.createHash("sha256").update(
|
|
13978
|
+
sessionId ? `${path15.resolve(repoRoot)}\0${tool}\0${sessionId}` : `${path15.resolve(repoRoot)}\0${tool}`
|
|
13562
13979
|
).digest("hex").slice(0, 16);
|
|
13563
|
-
return
|
|
13980
|
+
return path15.join(stateDir2(), `${hash}.json`);
|
|
13564
13981
|
}
|
|
13565
13982
|
function lockFileForRepo(repoRoot, tool) {
|
|
13566
13983
|
return `${stateFileForRepo(repoRoot, tool)}.lock`;
|
|
13567
13984
|
}
|
|
13568
13985
|
async function readStateFile(file) {
|
|
13569
13986
|
try {
|
|
13570
|
-
const raw = await
|
|
13987
|
+
const raw = await fs12.promises.readFile(file, "utf-8");
|
|
13571
13988
|
const parsed = JSON.parse(raw);
|
|
13572
|
-
if (parsed.schemaVersion !==
|
|
13989
|
+
if (parsed.schemaVersion !== CURRENT_SCHEMA_VERSION2) {
|
|
13573
13990
|
return null;
|
|
13574
13991
|
}
|
|
13575
13992
|
return parsed;
|
|
@@ -13580,26 +13997,26 @@ async function readStateFile(file) {
|
|
|
13580
13997
|
async function listScopedSessionStates(repoRoot, tool) {
|
|
13581
13998
|
let entries;
|
|
13582
13999
|
try {
|
|
13583
|
-
entries = await
|
|
14000
|
+
entries = await fs12.promises.readdir(stateDir2(), { withFileTypes: true });
|
|
13584
14001
|
} catch {
|
|
13585
14002
|
return [];
|
|
13586
14003
|
}
|
|
13587
14004
|
const states = [];
|
|
13588
14005
|
for (const entry of entries) {
|
|
13589
14006
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
13590
|
-
const file =
|
|
14007
|
+
const file = path15.join(stateDir2(), entry.name);
|
|
13591
14008
|
const state = await readStateFile(file);
|
|
13592
14009
|
if (!state) continue;
|
|
13593
14010
|
if (typeof state.repoRoot !== "string" || typeof state.sessionId !== "string") {
|
|
13594
14011
|
continue;
|
|
13595
14012
|
}
|
|
13596
|
-
if (
|
|
13597
|
-
if (
|
|
14013
|
+
if (path15.resolve(state.repoRoot) !== path15.resolve(repoRoot)) continue;
|
|
14014
|
+
if (path15.resolve(file) !== path15.resolve(stateFileForRepo(repoRoot, tool, state.sessionId))) {
|
|
13598
14015
|
continue;
|
|
13599
14016
|
}
|
|
13600
14017
|
let mtimeMs = 0;
|
|
13601
14018
|
try {
|
|
13602
|
-
mtimeMs = (await
|
|
14019
|
+
mtimeMs = (await fs12.promises.stat(file)).mtimeMs;
|
|
13603
14020
|
} catch {
|
|
13604
14021
|
continue;
|
|
13605
14022
|
}
|
|
@@ -13610,26 +14027,26 @@ async function listScopedSessionStates(repoRoot, tool) {
|
|
|
13610
14027
|
async function listSessionStatesForSession(tool, sessionId) {
|
|
13611
14028
|
let entries;
|
|
13612
14029
|
try {
|
|
13613
|
-
entries = await
|
|
14030
|
+
entries = await fs12.promises.readdir(stateDir2(), { withFileTypes: true });
|
|
13614
14031
|
} catch {
|
|
13615
14032
|
return [];
|
|
13616
14033
|
}
|
|
13617
14034
|
const states = [];
|
|
13618
14035
|
for (const entry of entries) {
|
|
13619
14036
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
13620
|
-
const file =
|
|
14037
|
+
const file = path15.join(stateDir2(), entry.name);
|
|
13621
14038
|
const state = await readStateFile(file);
|
|
13622
14039
|
if (!state) continue;
|
|
13623
14040
|
if (typeof state.repoRoot !== "string" || typeof state.sessionId !== "string") {
|
|
13624
14041
|
continue;
|
|
13625
14042
|
}
|
|
13626
14043
|
if (state.sessionId !== sessionId) continue;
|
|
13627
|
-
if (
|
|
14044
|
+
if (path15.resolve(file) !== path15.resolve(stateFileForRepo(state.repoRoot, tool, sessionId))) {
|
|
13628
14045
|
continue;
|
|
13629
14046
|
}
|
|
13630
14047
|
let mtimeMs = 0;
|
|
13631
14048
|
try {
|
|
13632
|
-
mtimeMs = (await
|
|
14049
|
+
mtimeMs = (await fs12.promises.stat(file)).mtimeMs;
|
|
13633
14050
|
} catch {
|
|
13634
14051
|
continue;
|
|
13635
14052
|
}
|
|
@@ -13650,12 +14067,12 @@ async function readSessionState(repoRoot, tool, sessionId) {
|
|
|
13650
14067
|
}
|
|
13651
14068
|
async function writeSessionState(state, tool) {
|
|
13652
14069
|
const file = stateFileForRepo(state.repoRoot, tool, state.sessionId);
|
|
13653
|
-
await
|
|
14070
|
+
await fs12.promises.mkdir(stateDir2(), { recursive: true, mode: 448 });
|
|
13654
14071
|
const tmp = `${file}.tmp`;
|
|
13655
|
-
await
|
|
14072
|
+
await fs12.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
|
|
13656
14073
|
mode: 384
|
|
13657
14074
|
});
|
|
13658
|
-
await
|
|
14075
|
+
await fs12.promises.rename(tmp, file);
|
|
13659
14076
|
const legacyFile = stateFileForRepo(state.repoRoot, tool);
|
|
13660
14077
|
const legacy = await readStateFile(legacyFile);
|
|
13661
14078
|
if (legacy?.sessionId === state.sessionId) {
|
|
@@ -13664,7 +14081,7 @@ async function writeSessionState(state, tool) {
|
|
|
13664
14081
|
}
|
|
13665
14082
|
async function deleteStateFile(file) {
|
|
13666
14083
|
try {
|
|
13667
|
-
await
|
|
14084
|
+
await fs12.promises.unlink(file);
|
|
13668
14085
|
} catch {
|
|
13669
14086
|
}
|
|
13670
14087
|
}
|
|
@@ -13680,14 +14097,14 @@ async function deleteSessionState(repoRoot, tool, sessionId) {
|
|
|
13680
14097
|
}
|
|
13681
14098
|
await deleteStateFile(stateFileForRepo(repoRoot, tool));
|
|
13682
14099
|
}
|
|
13683
|
-
async function
|
|
14100
|
+
async function acquireLock2(repoRoot, tool, retries = LOCK_RETRIES2, delayMs = LOCK_RETRY_DELAY_MS2) {
|
|
13684
14101
|
const lockPath = lockFileForRepo(repoRoot, tool);
|
|
13685
|
-
await
|
|
14102
|
+
await fs12.promises.mkdir(stateDir2(), { recursive: true, mode: 448 });
|
|
13686
14103
|
for (let i = 0; i < retries; i++) {
|
|
13687
14104
|
try {
|
|
13688
|
-
const fd = await
|
|
14105
|
+
const fd = await fs12.promises.open(
|
|
13689
14106
|
lockPath,
|
|
13690
|
-
|
|
14107
|
+
fs12.constants.O_CREAT | fs12.constants.O_EXCL | fs12.constants.O_WRONLY
|
|
13691
14108
|
);
|
|
13692
14109
|
await fd.write(String(process.pid));
|
|
13693
14110
|
await fd.close();
|
|
@@ -13702,9 +14119,9 @@ async function acquireLock(repoRoot, tool, retries = LOCK_RETRIES, delayMs = LOC
|
|
|
13702
14119
|
}
|
|
13703
14120
|
throw new Error(`Failed to acquire lock after ${retries} retries`);
|
|
13704
14121
|
}
|
|
13705
|
-
async function
|
|
14122
|
+
async function releaseLock2(repoRoot, tool) {
|
|
13706
14123
|
try {
|
|
13707
|
-
await
|
|
14124
|
+
await fs12.promises.unlink(lockFileForRepo(repoRoot, tool));
|
|
13708
14125
|
} catch {
|
|
13709
14126
|
}
|
|
13710
14127
|
}
|
|
@@ -13714,7 +14131,7 @@ var CLI_VERSION = "0.2.0";
|
|
|
13714
14131
|
var GIT_TRACES_SLUG = "git-traces";
|
|
13715
14132
|
var MAX_UPLOAD_BYTES = 500 * 1024 * 1024;
|
|
13716
14133
|
var STALE_SCOPED_STATE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
13717
|
-
function
|
|
14134
|
+
function formatEpochSeconds3(date) {
|
|
13718
14135
|
return String(Math.floor(date.getTime() / 1e3));
|
|
13719
14136
|
}
|
|
13720
14137
|
var TOOL_LABELS = {
|
|
@@ -13727,17 +14144,17 @@ var TOOL_LABELS = {
|
|
|
13727
14144
|
async function loadConfiguredRepos() {
|
|
13728
14145
|
const file = await loadProjects();
|
|
13729
14146
|
return Object.entries(file.projects).map(([repoRoot, config]) => ({
|
|
13730
|
-
repoRoot:
|
|
14147
|
+
repoRoot: path16.resolve(repoRoot),
|
|
13731
14148
|
config
|
|
13732
14149
|
})).sort((a, b) => a.repoRoot.localeCompare(b.repoRoot));
|
|
13733
14150
|
}
|
|
13734
14151
|
function repoLabel(repoRoot) {
|
|
13735
|
-
return
|
|
14152
|
+
return path16.basename(repoRoot) || repoRoot;
|
|
13736
14153
|
}
|
|
13737
|
-
function
|
|
14154
|
+
function resolveCwd2(payload) {
|
|
13738
14155
|
return payload.cwd ?? payload.workspace_roots?.[0] ?? null;
|
|
13739
14156
|
}
|
|
13740
|
-
function
|
|
14157
|
+
function resolveSessionId2(payload) {
|
|
13741
14158
|
return payload.session_id ?? payload.conversation_id ?? null;
|
|
13742
14159
|
}
|
|
13743
14160
|
function epochPrefix(epoch) {
|
|
@@ -13935,15 +14352,15 @@ async function uploadEpochBaselineArtifacts(params) {
|
|
|
13935
14352
|
}
|
|
13936
14353
|
async function createGitTracesContribution(params) {
|
|
13937
14354
|
const { client, config, repoRoot, state, tool, now, artifacts } = params;
|
|
13938
|
-
const
|
|
13939
|
-
const epochSeconds =
|
|
14355
|
+
const toolLabel2 = TOOL_LABELS[tool] ?? "Claude";
|
|
14356
|
+
const epochSeconds = formatEpochSeconds3(now);
|
|
13940
14357
|
const shortId = state.sessionId.slice(0, 12);
|
|
13941
14358
|
const repoName = repoLabel(repoRoot);
|
|
13942
14359
|
const contribution = await client.createContribution(config.projectId, {
|
|
13943
14360
|
contributionTypeSlug: GIT_TRACES_SLUG,
|
|
13944
|
-
title: `${
|
|
14361
|
+
title: `${toolLabel2} session ${shortId} \u2014 ${repoName} \u2014 ${epochSeconds}`,
|
|
13945
14362
|
body: `Session ID: ${state.sessionId}
|
|
13946
|
-
Tool: ${
|
|
14363
|
+
Tool: ${toolLabel2}
|
|
13947
14364
|
Repo: ${repoRoot}
|
|
13948
14365
|
Uploaded: ${now.toISOString()}`
|
|
13949
14366
|
});
|
|
@@ -13997,7 +14414,7 @@ async function initializeSession(repoRoot, tool, sessionId) {
|
|
|
13997
14414
|
});
|
|
13998
14415
|
if (!frozen) return null;
|
|
13999
14416
|
const state = {
|
|
14000
|
-
schemaVersion:
|
|
14417
|
+
schemaVersion: CURRENT_SCHEMA_VERSION2,
|
|
14001
14418
|
sessionId,
|
|
14002
14419
|
contributionId: null,
|
|
14003
14420
|
baselineSha: frozen.baselineSha,
|
|
@@ -14047,7 +14464,7 @@ async function processSessionStartRepo(repo, tool, sessionId) {
|
|
|
14047
14464
|
);
|
|
14048
14465
|
return "skipped";
|
|
14049
14466
|
}
|
|
14050
|
-
await
|
|
14467
|
+
await acquireLock2(repoRoot, tool);
|
|
14051
14468
|
try {
|
|
14052
14469
|
const staleLegacy = await readSessionState(repoRoot, tool);
|
|
14053
14470
|
if (staleLegacy && staleLegacy.sessionId !== sessionId) {
|
|
@@ -14078,11 +14495,11 @@ async function processSessionStartRepo(repo, tool, sessionId) {
|
|
|
14078
14495
|
);
|
|
14079
14496
|
return "failed";
|
|
14080
14497
|
} finally {
|
|
14081
|
-
await
|
|
14498
|
+
await releaseLock2(repoRoot, tool);
|
|
14082
14499
|
}
|
|
14083
14500
|
}
|
|
14084
14501
|
async function handleSessionStart(payload, tool) {
|
|
14085
|
-
const cwd =
|
|
14502
|
+
const cwd = resolveCwd2(payload);
|
|
14086
14503
|
if (!cwd) {
|
|
14087
14504
|
appendLog("warn", "git-traces: no cwd in payload, skipping");
|
|
14088
14505
|
return;
|
|
@@ -14095,7 +14512,7 @@ async function handleSessionStart(payload, tool) {
|
|
|
14095
14512
|
);
|
|
14096
14513
|
return;
|
|
14097
14514
|
}
|
|
14098
|
-
const sessionId =
|
|
14515
|
+
const sessionId = resolveSessionId2(payload);
|
|
14099
14516
|
if (!sessionId) {
|
|
14100
14517
|
appendLog("warn", "git-traces: no session_id in payload, skipping");
|
|
14101
14518
|
return;
|
|
@@ -14178,9 +14595,10 @@ async function processStopRepo(repo, tool, sessionId, recordedAt) {
|
|
|
14178
14595
|
);
|
|
14179
14596
|
return "skipped";
|
|
14180
14597
|
}
|
|
14181
|
-
await
|
|
14598
|
+
await acquireLock2(repoRoot, tool);
|
|
14599
|
+
let state = null;
|
|
14182
14600
|
try {
|
|
14183
|
-
|
|
14601
|
+
state = await readSessionState(repoRoot, tool, sessionId);
|
|
14184
14602
|
if (!state) {
|
|
14185
14603
|
appendLog(
|
|
14186
14604
|
"info",
|
|
@@ -14351,17 +14769,42 @@ async function processStopRepo(repo, tool, sessionId, recordedAt) {
|
|
|
14351
14769
|
"error",
|
|
14352
14770
|
`git-traces: Stop failed for repo ${repoRoot}: ${formatError(err)}`
|
|
14353
14771
|
);
|
|
14772
|
+
if (state && err instanceof PlatformError && err.status === 404 && err.code === "CONTRIBUTION_NOT_FOUND") {
|
|
14773
|
+
if (state.baselineTreeSha) {
|
|
14774
|
+
await writeSessionState(
|
|
14775
|
+
{
|
|
14776
|
+
...state,
|
|
14777
|
+
contributionId: null,
|
|
14778
|
+
lastSnapshotSha: state.baselineSha,
|
|
14779
|
+
lastSnapshotTreeSha: state.baselineTreeSha,
|
|
14780
|
+
turnCount: 0
|
|
14781
|
+
},
|
|
14782
|
+
tool
|
|
14783
|
+
);
|
|
14784
|
+
appendLog(
|
|
14785
|
+
"warn",
|
|
14786
|
+
`git-traces: cleared stale contribution state (repo=${repoRoot}, project=${config.projectId}, missingContribution=${state.contributionId})`
|
|
14787
|
+
);
|
|
14788
|
+
} else {
|
|
14789
|
+
cleanupSessionRefs(repoRoot, state.sessionId);
|
|
14790
|
+
await deleteSessionState(repoRoot, tool, state.sessionId);
|
|
14791
|
+
appendLog(
|
|
14792
|
+
"warn",
|
|
14793
|
+
`git-traces: deleted stale contribution state without a frozen baseline (repo=${repoRoot}, project=${config.projectId}, missingContribution=${state.contributionId})`
|
|
14794
|
+
);
|
|
14795
|
+
}
|
|
14796
|
+
}
|
|
14354
14797
|
return "failed";
|
|
14355
14798
|
} finally {
|
|
14356
|
-
await
|
|
14799
|
+
await releaseLock2(repoRoot, tool);
|
|
14357
14800
|
}
|
|
14358
14801
|
}
|
|
14359
14802
|
async function handleStop(payload, tool) {
|
|
14360
|
-
const cwd =
|
|
14803
|
+
const cwd = resolveCwd2(payload);
|
|
14361
14804
|
if (!cwd) return;
|
|
14362
14805
|
const project = await findProjectForCwd(cwd);
|
|
14363
14806
|
if (!project) return;
|
|
14364
|
-
const sessionId =
|
|
14807
|
+
const sessionId = resolveSessionId2(payload);
|
|
14365
14808
|
const recordedAt = Date.now();
|
|
14366
14809
|
const repos = await loadConfiguredRepos();
|
|
14367
14810
|
const repoByRoot = new Map(repos.map((repo) => [repo.repoRoot, repo]));
|
|
@@ -14370,7 +14813,7 @@ async function handleStop(payload, tool) {
|
|
|
14370
14813
|
if (sessionId) {
|
|
14371
14814
|
const storedStates = await listSessionStatesForSession(tool, sessionId);
|
|
14372
14815
|
for (const { state } of storedStates) {
|
|
14373
|
-
const repo = repoByRoot.get(
|
|
14816
|
+
const repo = repoByRoot.get(path16.resolve(state.repoRoot));
|
|
14374
14817
|
if (!repo) {
|
|
14375
14818
|
missingConfig++;
|
|
14376
14819
|
appendLog(
|
|
@@ -14406,7 +14849,7 @@ async function handleStop(payload, tool) {
|
|
|
14406
14849
|
);
|
|
14407
14850
|
}
|
|
14408
14851
|
async function cleanupSessionStateForRepo(repoRoot, tool, sessionId) {
|
|
14409
|
-
await
|
|
14852
|
+
await acquireLock2(repoRoot, tool);
|
|
14410
14853
|
try {
|
|
14411
14854
|
const state = await readSessionState(repoRoot, tool, sessionId);
|
|
14412
14855
|
if (!state) return "no-state";
|
|
@@ -14424,19 +14867,19 @@ async function cleanupSessionStateForRepo(repoRoot, tool, sessionId) {
|
|
|
14424
14867
|
);
|
|
14425
14868
|
return "failed";
|
|
14426
14869
|
} finally {
|
|
14427
|
-
await
|
|
14870
|
+
await releaseLock2(repoRoot, tool);
|
|
14428
14871
|
}
|
|
14429
14872
|
}
|
|
14430
14873
|
async function handleSessionEnd(payload, tool) {
|
|
14431
|
-
const cwd =
|
|
14432
|
-
const sessionId =
|
|
14874
|
+
const cwd = resolveCwd2(payload);
|
|
14875
|
+
const sessionId = resolveSessionId2(payload);
|
|
14433
14876
|
const project = cwd ? await findProjectForCwd(cwd) : null;
|
|
14434
14877
|
const triggerRepo = project?.repoRoot ?? cwd ?? "<none>";
|
|
14435
14878
|
const repoRoots = [];
|
|
14436
14879
|
if (sessionId) {
|
|
14437
14880
|
const states = await listSessionStatesForSession(tool, sessionId);
|
|
14438
14881
|
for (const { state } of states) {
|
|
14439
|
-
repoRoots.push(
|
|
14882
|
+
repoRoots.push(path16.resolve(state.repoRoot));
|
|
14440
14883
|
}
|
|
14441
14884
|
}
|
|
14442
14885
|
if (repoRoots.length === 0 && cwd) {
|
|
@@ -14463,7 +14906,7 @@ var WORKER_ENV_FLAG2 = "HILLCLIMB_GIT_TRACES_WORKER";
|
|
|
14463
14906
|
var TOOL_ENV_FLAG2 = "HILLCLIMB_GIT_TRACES_TOOL";
|
|
14464
14907
|
var FLOW_ID_ENV = "HILLCLIMB_GIT_TRACES_FLOW";
|
|
14465
14908
|
function newFlowId() {
|
|
14466
|
-
return
|
|
14909
|
+
return crypto3.randomBytes(3).toString("hex");
|
|
14467
14910
|
}
|
|
14468
14911
|
var KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
14469
14912
|
"claude",
|
|
@@ -14472,7 +14915,7 @@ var KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
|
14472
14915
|
"cursor",
|
|
14473
14916
|
"opencode"
|
|
14474
14917
|
]);
|
|
14475
|
-
function
|
|
14918
|
+
function classifyHookEvent2(event) {
|
|
14476
14919
|
switch (event) {
|
|
14477
14920
|
case "SessionStart":
|
|
14478
14921
|
case "sessionStart":
|
|
@@ -14507,7 +14950,7 @@ async function readStdin2() {
|
|
|
14507
14950
|
}
|
|
14508
14951
|
return Buffer.concat(chunks).toString("utf-8");
|
|
14509
14952
|
}
|
|
14510
|
-
function
|
|
14953
|
+
function resolveCwd3(payload) {
|
|
14511
14954
|
return payload.cwd ?? payload.workspace_roots?.[0] ?? null;
|
|
14512
14955
|
}
|
|
14513
14956
|
async function repairHookForTool(repoRoot, tool) {
|
|
@@ -14522,7 +14965,7 @@ async function repairHookForTool(repoRoot, tool) {
|
|
|
14522
14965
|
}
|
|
14523
14966
|
async function selfHealHook2(payload, tool) {
|
|
14524
14967
|
if (process.env.HILLCLIMB_SKIP_HOOK_SELF_HEAL === "1") return;
|
|
14525
|
-
const cwd =
|
|
14968
|
+
const cwd = resolveCwd3(payload);
|
|
14526
14969
|
if (!cwd) return;
|
|
14527
14970
|
try {
|
|
14528
14971
|
const project = await findProjectForCwd(cwd);
|
|
@@ -14546,7 +14989,7 @@ async function resolveLegacyBareTool(raw) {
|
|
|
14546
14989
|
);
|
|
14547
14990
|
return null;
|
|
14548
14991
|
}
|
|
14549
|
-
const cwd =
|
|
14992
|
+
const cwd = resolveCwd3(payload);
|
|
14550
14993
|
if (!cwd) {
|
|
14551
14994
|
appendLog(
|
|
14552
14995
|
"error",
|
|
@@ -14705,6 +15148,7 @@ async function runGitTracesWorker() {
|
|
|
14705
15148
|
return;
|
|
14706
15149
|
}
|
|
14707
15150
|
const event = payload.hook_event_name;
|
|
15151
|
+
const eventKind = classifyHookEvent2(event);
|
|
14708
15152
|
appendLog("info", `git-traces worker: handling event=${event}`);
|
|
14709
15153
|
if (tool === "claude" && typeof payload.cursor_version === "string") {
|
|
14710
15154
|
appendLog(
|
|
@@ -14713,9 +15157,9 @@ async function runGitTracesWorker() {
|
|
|
14713
15157
|
);
|
|
14714
15158
|
return;
|
|
14715
15159
|
}
|
|
14716
|
-
await selfHealHook2(payload, tool);
|
|
14717
15160
|
try {
|
|
14718
|
-
|
|
15161
|
+
await selfHealHook2(payload, tool);
|
|
15162
|
+
switch (eventKind) {
|
|
14719
15163
|
case "sessionStart":
|
|
14720
15164
|
await handleSessionStart(payload, tool);
|
|
14721
15165
|
break;
|
|
@@ -14736,19 +15180,27 @@ async function runGitTracesWorker() {
|
|
|
14736
15180
|
`git-traces worker: unexpected error: ${detail}${stack ? `
|
|
14737
15181
|
${stack}` : ""}`
|
|
14738
15182
|
);
|
|
15183
|
+
} finally {
|
|
15184
|
+
if (eventKind === "stop" || eventKind === "sessionEnd") {
|
|
15185
|
+
await recordDebugLogCompletion({
|
|
15186
|
+
kind: "git",
|
|
15187
|
+
tool,
|
|
15188
|
+
payload
|
|
15189
|
+
});
|
|
15190
|
+
}
|
|
14739
15191
|
}
|
|
14740
15192
|
}
|
|
14741
15193
|
|
|
14742
15194
|
// src/outputs/zip.ts
|
|
14743
|
-
import
|
|
14744
|
-
import
|
|
15195
|
+
import fs14 from "fs";
|
|
15196
|
+
import path18 from "path";
|
|
14745
15197
|
import archiver2 from "archiver";
|
|
14746
15198
|
|
|
14747
15199
|
// src/outputs/downloads.ts
|
|
14748
15200
|
import { execSync as execSync2 } from "child_process";
|
|
14749
|
-
import
|
|
15201
|
+
import fs13 from "fs";
|
|
14750
15202
|
import os8 from "os";
|
|
14751
|
-
import
|
|
15203
|
+
import path17 from "path";
|
|
14752
15204
|
function getDownloadsFolder() {
|
|
14753
15205
|
const home = os8.homedir();
|
|
14754
15206
|
if (process.platform === "linux") {
|
|
@@ -14757,12 +15209,12 @@ function getDownloadsFolder() {
|
|
|
14757
15209
|
encoding: "utf-8",
|
|
14758
15210
|
timeout: 3e3
|
|
14759
15211
|
}).trim();
|
|
14760
|
-
if (xdgDir &&
|
|
15212
|
+
if (xdgDir && fs13.existsSync(xdgDir)) return xdgDir;
|
|
14761
15213
|
} catch {
|
|
14762
15214
|
}
|
|
14763
15215
|
}
|
|
14764
|
-
const downloads =
|
|
14765
|
-
if (
|
|
15216
|
+
const downloads = path17.join(home, "Downloads");
|
|
15217
|
+
if (fs13.existsSync(downloads)) return downloads;
|
|
14766
15218
|
return home;
|
|
14767
15219
|
}
|
|
14768
15220
|
|
|
@@ -14771,11 +15223,11 @@ function sanitizeFilename(name) {
|
|
|
14771
15223
|
return name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
14772
15224
|
}
|
|
14773
15225
|
function getUniqueFilename(dir, base, ext) {
|
|
14774
|
-
let candidate =
|
|
14775
|
-
if (!
|
|
15226
|
+
let candidate = path18.join(dir, `${base}${ext}`);
|
|
15227
|
+
if (!fs14.existsSync(candidate)) return candidate;
|
|
14776
15228
|
let i = 1;
|
|
14777
|
-
while (
|
|
14778
|
-
candidate =
|
|
15229
|
+
while (fs14.existsSync(candidate)) {
|
|
15230
|
+
candidate = path18.join(dir, `${base}-${i}${ext}`);
|
|
14779
15231
|
i++;
|
|
14780
15232
|
}
|
|
14781
15233
|
return candidate;
|
|
@@ -14785,13 +15237,13 @@ var ZipOutput = class {
|
|
|
14785
15237
|
label = "Save as .zip to Downloads";
|
|
14786
15238
|
async emit(group, options) {
|
|
14787
15239
|
const downloadsDir = getDownloadsFolder();
|
|
14788
|
-
const repoName = sanitizeFilename(
|
|
15240
|
+
const repoName = sanitizeFilename(path18.basename(group.repoPath));
|
|
14789
15241
|
const timeRange = options.timeRange;
|
|
14790
15242
|
const rangePart = timeRange?.label ?? "all";
|
|
14791
15243
|
const epochSeconds = Math.floor(Date.now() / 1e3);
|
|
14792
15244
|
const baseName = `logs-${repoName}-${rangePart}-${epochSeconds}`;
|
|
14793
15245
|
const outputPath = getUniqueFilename(downloadsDir, baseName, ".zip");
|
|
14794
|
-
const output =
|
|
15246
|
+
const output = fs14.createWriteStream(outputPath);
|
|
14795
15247
|
const archive = archiver2("zip", { zlib: { level: 6 } });
|
|
14796
15248
|
const done = new Promise((resolve, reject) => {
|
|
14797
15249
|
output.on("close", resolve);
|
|
@@ -14985,15 +15437,15 @@ async function confirmExport(group, output) {
|
|
|
14985
15437
|
}
|
|
14986
15438
|
|
|
14987
15439
|
// src/sources/claude.ts
|
|
14988
|
-
import
|
|
15440
|
+
import fs15 from "fs";
|
|
14989
15441
|
import os9 from "os";
|
|
14990
|
-
import
|
|
15442
|
+
import path19 from "path";
|
|
14991
15443
|
import readline from "readline";
|
|
14992
15444
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["tool-results"]);
|
|
14993
15445
|
async function resolveRepoPath(projectDir) {
|
|
14994
|
-
const indexPath =
|
|
15446
|
+
const indexPath = path19.join(projectDir, "sessions-index.json");
|
|
14995
15447
|
try {
|
|
14996
|
-
const raw = await
|
|
15448
|
+
const raw = await fs15.promises.readFile(indexPath, "utf-8");
|
|
14997
15449
|
const data = JSON.parse(raw);
|
|
14998
15450
|
if (data.originalPath && typeof data.originalPath === "string") {
|
|
14999
15451
|
return data.originalPath;
|
|
@@ -15001,12 +15453,12 @@ async function resolveRepoPath(projectDir) {
|
|
|
15001
15453
|
} catch {
|
|
15002
15454
|
}
|
|
15003
15455
|
const cwdCounts = /* @__PURE__ */ new Map();
|
|
15004
|
-
const entries = await
|
|
15456
|
+
const entries = await fs15.promises.readdir(projectDir, {
|
|
15005
15457
|
withFileTypes: true
|
|
15006
15458
|
});
|
|
15007
15459
|
for (const entry of entries) {
|
|
15008
15460
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
15009
|
-
const cwd = await extractCwdFromJsonl(
|
|
15461
|
+
const cwd = await extractCwdFromJsonl(path19.join(projectDir, entry.name));
|
|
15010
15462
|
if (cwd) {
|
|
15011
15463
|
cwdCounts.set(cwd, (cwdCounts.get(cwd) ?? 0) + 1);
|
|
15012
15464
|
}
|
|
@@ -15025,7 +15477,7 @@ async function resolveRepoPath(projectDir) {
|
|
|
15025
15477
|
return null;
|
|
15026
15478
|
}
|
|
15027
15479
|
async function extractCwdFromJsonl(filePath) {
|
|
15028
|
-
const stream =
|
|
15480
|
+
const stream = fs15.createReadStream(filePath, { encoding: "utf-8" });
|
|
15029
15481
|
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
15030
15482
|
try {
|
|
15031
15483
|
for await (const line of rl) {
|
|
@@ -15047,12 +15499,12 @@ async function extractCwdFromJsonl(filePath) {
|
|
|
15047
15499
|
async function collectFiles(dir, baseDir, sourceName, repoPath, results) {
|
|
15048
15500
|
let entries;
|
|
15049
15501
|
try {
|
|
15050
|
-
entries = await
|
|
15502
|
+
entries = await fs15.promises.readdir(dir, { withFileTypes: true });
|
|
15051
15503
|
} catch {
|
|
15052
15504
|
return;
|
|
15053
15505
|
}
|
|
15054
15506
|
for (const entry of entries) {
|
|
15055
|
-
const fullPath =
|
|
15507
|
+
const fullPath = path19.join(dir, entry.name);
|
|
15056
15508
|
if (entry.isDirectory()) {
|
|
15057
15509
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
15058
15510
|
await collectFiles(fullPath, baseDir, sourceName, repoPath, results);
|
|
@@ -15074,19 +15526,19 @@ function fallbackDecode(encodedName) {
|
|
|
15074
15526
|
var ClaudeSource = class {
|
|
15075
15527
|
name = "claude";
|
|
15076
15528
|
async scan() {
|
|
15077
|
-
const baseDir =
|
|
15529
|
+
const baseDir = path19.join(os9.homedir(), ".claude", "projects");
|
|
15078
15530
|
try {
|
|
15079
|
-
await
|
|
15531
|
+
await fs15.promises.access(baseDir);
|
|
15080
15532
|
} catch {
|
|
15081
15533
|
return [];
|
|
15082
15534
|
}
|
|
15083
|
-
const projectDirs = await
|
|
15535
|
+
const projectDirs = await fs15.promises.readdir(baseDir, {
|
|
15084
15536
|
withFileTypes: true
|
|
15085
15537
|
});
|
|
15086
15538
|
const dirEntries = projectDirs.filter((d) => d.isDirectory());
|
|
15087
15539
|
const resultArrays = await Promise.all(
|
|
15088
15540
|
dirEntries.map(async (dir) => {
|
|
15089
|
-
const projectPath =
|
|
15541
|
+
const projectPath = path19.join(baseDir, dir.name);
|
|
15090
15542
|
const repoPath = await resolveRepoPath(projectPath) ?? fallbackDecode(dir.name);
|
|
15091
15543
|
const files = [];
|
|
15092
15544
|
await collectFiles(
|
|
@@ -15104,12 +15556,12 @@ var ClaudeSource = class {
|
|
|
15104
15556
|
};
|
|
15105
15557
|
|
|
15106
15558
|
// src/sources/codex.ts
|
|
15107
|
-
import
|
|
15559
|
+
import fs16 from "fs";
|
|
15108
15560
|
import os10 from "os";
|
|
15109
|
-
import
|
|
15561
|
+
import path20 from "path";
|
|
15110
15562
|
import readline2 from "readline";
|
|
15111
15563
|
async function parseSessionMeta(filePath) {
|
|
15112
|
-
const stream =
|
|
15564
|
+
const stream = fs16.createReadStream(filePath, { encoding: "utf-8" });
|
|
15113
15565
|
const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
|
|
15114
15566
|
try {
|
|
15115
15567
|
for await (const line of rl) {
|
|
@@ -15134,12 +15586,12 @@ async function findJsonlFiles(dir) {
|
|
|
15134
15586
|
async function walk(d) {
|
|
15135
15587
|
let entries;
|
|
15136
15588
|
try {
|
|
15137
|
-
entries = await
|
|
15589
|
+
entries = await fs16.promises.readdir(d, { withFileTypes: true });
|
|
15138
15590
|
} catch {
|
|
15139
15591
|
return;
|
|
15140
15592
|
}
|
|
15141
15593
|
for (const entry of entries) {
|
|
15142
|
-
const full =
|
|
15594
|
+
const full = path20.join(d, entry.name);
|
|
15143
15595
|
if (entry.isDirectory()) {
|
|
15144
15596
|
await walk(full);
|
|
15145
15597
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -15153,11 +15605,11 @@ async function findJsonlFiles(dir) {
|
|
|
15153
15605
|
async function loadHistory(historyPath) {
|
|
15154
15606
|
const map = /* @__PURE__ */ new Map();
|
|
15155
15607
|
try {
|
|
15156
|
-
await
|
|
15608
|
+
await fs16.promises.access(historyPath);
|
|
15157
15609
|
} catch {
|
|
15158
15610
|
return map;
|
|
15159
15611
|
}
|
|
15160
|
-
const stream =
|
|
15612
|
+
const stream = fs16.createReadStream(historyPath, { encoding: "utf-8" });
|
|
15161
15613
|
const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
|
|
15162
15614
|
try {
|
|
15163
15615
|
for await (const line of rl) {
|
|
@@ -15184,14 +15636,14 @@ async function loadHistory(historyPath) {
|
|
|
15184
15636
|
var CodexSource = class {
|
|
15185
15637
|
name = "codex";
|
|
15186
15638
|
async scan() {
|
|
15187
|
-
const codexDir =
|
|
15188
|
-
const sessionsDir =
|
|
15639
|
+
const codexDir = path20.join(os10.homedir(), ".codex");
|
|
15640
|
+
const sessionsDir = path20.join(codexDir, "sessions");
|
|
15189
15641
|
try {
|
|
15190
|
-
await
|
|
15642
|
+
await fs16.promises.access(sessionsDir);
|
|
15191
15643
|
} catch {
|
|
15192
15644
|
return [];
|
|
15193
15645
|
}
|
|
15194
|
-
const historyPath =
|
|
15646
|
+
const historyPath = path20.join(codexDir, "history.jsonl");
|
|
15195
15647
|
const [jsonlFiles, historyMap] = await Promise.all([
|
|
15196
15648
|
findJsonlFiles(sessionsDir),
|
|
15197
15649
|
loadHistory(historyPath)
|
|
@@ -15214,8 +15666,8 @@ var CodexSource = class {
|
|
|
15214
15666
|
});
|
|
15215
15667
|
const historyLines = historyMap.get(meta.sessionId);
|
|
15216
15668
|
if (historyLines) {
|
|
15217
|
-
const sessionDir =
|
|
15218
|
-
const historyAbsPath =
|
|
15669
|
+
const sessionDir = path20.relative(sessionsDir, path20.dirname(filePath));
|
|
15670
|
+
const historyAbsPath = path20.join(
|
|
15219
15671
|
sessionsDir,
|
|
15220
15672
|
sessionDir,
|
|
15221
15673
|
`history-${meta.sessionId}.jsonl`
|
|
@@ -15235,18 +15687,18 @@ var CodexSource = class {
|
|
|
15235
15687
|
};
|
|
15236
15688
|
|
|
15237
15689
|
// src/sources/copilotChat.ts
|
|
15238
|
-
import
|
|
15690
|
+
import fs17 from "fs";
|
|
15239
15691
|
import os11 from "os";
|
|
15240
|
-
import
|
|
15692
|
+
import path21 from "path";
|
|
15241
15693
|
import { fileURLToPath } from "url";
|
|
15242
15694
|
function vsCodeUserDirs() {
|
|
15243
15695
|
const home = os11.homedir();
|
|
15244
15696
|
const dirs = [
|
|
15245
|
-
|
|
15246
|
-
|
|
15697
|
+
path21.join(home, "Library", "Application Support", "Code", "User"),
|
|
15698
|
+
path21.join(home, ".config", "Code", "User")
|
|
15247
15699
|
];
|
|
15248
15700
|
if (process.env.APPDATA) {
|
|
15249
|
-
dirs.push(
|
|
15701
|
+
dirs.push(path21.join(process.env.APPDATA, "Code", "User"));
|
|
15250
15702
|
}
|
|
15251
15703
|
return dirs;
|
|
15252
15704
|
}
|
|
@@ -15261,7 +15713,7 @@ function uriToFsPath(uri) {
|
|
|
15261
15713
|
async function readWorkspaceFolder(workspaceJsonPath) {
|
|
15262
15714
|
let raw;
|
|
15263
15715
|
try {
|
|
15264
|
-
raw = await
|
|
15716
|
+
raw = await fs17.promises.readFile(workspaceJsonPath, "utf-8");
|
|
15265
15717
|
} catch {
|
|
15266
15718
|
return null;
|
|
15267
15719
|
}
|
|
@@ -15283,10 +15735,10 @@ var CopilotChatSource = class {
|
|
|
15283
15735
|
async scan() {
|
|
15284
15736
|
const results = [];
|
|
15285
15737
|
for (const userDir of vsCodeUserDirs()) {
|
|
15286
|
-
const workspaceStorage =
|
|
15738
|
+
const workspaceStorage = path21.join(userDir, "workspaceStorage");
|
|
15287
15739
|
let hashDirs;
|
|
15288
15740
|
try {
|
|
15289
|
-
hashDirs = await
|
|
15741
|
+
hashDirs = await fs17.promises.readdir(workspaceStorage, {
|
|
15290
15742
|
withFileTypes: true
|
|
15291
15743
|
});
|
|
15292
15744
|
} catch {
|
|
@@ -15294,22 +15746,22 @@ var CopilotChatSource = class {
|
|
|
15294
15746
|
}
|
|
15295
15747
|
for (const hash of hashDirs) {
|
|
15296
15748
|
if (!hash.isDirectory()) continue;
|
|
15297
|
-
const wsRoot =
|
|
15298
|
-
const transcriptsDir =
|
|
15749
|
+
const wsRoot = path21.join(workspaceStorage, hash.name);
|
|
15750
|
+
const transcriptsDir = path21.join(
|
|
15299
15751
|
wsRoot,
|
|
15300
15752
|
"GitHub.copilot-chat",
|
|
15301
15753
|
"transcripts"
|
|
15302
15754
|
);
|
|
15303
15755
|
let transcriptEntries;
|
|
15304
15756
|
try {
|
|
15305
|
-
transcriptEntries = await
|
|
15757
|
+
transcriptEntries = await fs17.promises.readdir(transcriptsDir, {
|
|
15306
15758
|
withFileTypes: true
|
|
15307
15759
|
});
|
|
15308
15760
|
} catch {
|
|
15309
15761
|
continue;
|
|
15310
15762
|
}
|
|
15311
15763
|
const repoPath = await readWorkspaceFolder(
|
|
15312
|
-
|
|
15764
|
+
path21.join(wsRoot, "workspace.json")
|
|
15313
15765
|
);
|
|
15314
15766
|
if (!repoPath) continue;
|
|
15315
15767
|
for (const entry of transcriptEntries) {
|
|
@@ -15317,7 +15769,7 @@ var CopilotChatSource = class {
|
|
|
15317
15769
|
const sessionId = entry.name.slice(0, -".jsonl".length);
|
|
15318
15770
|
results.push({
|
|
15319
15771
|
sourceName: this.name,
|
|
15320
|
-
absolutePath:
|
|
15772
|
+
absolutePath: path21.join(transcriptsDir, entry.name),
|
|
15321
15773
|
repoPath,
|
|
15322
15774
|
metadata: { sessionId }
|
|
15323
15775
|
});
|
|
@@ -15357,7 +15809,7 @@ function reportRedactionStats(noun, stats) {
|
|
|
15357
15809
|
async function filterByTimeRange(group, range) {
|
|
15358
15810
|
const results = await Promise.all(
|
|
15359
15811
|
group.files.map(
|
|
15360
|
-
(f) =>
|
|
15812
|
+
(f) => fs18.promises.stat(f.absolutePath).then((s) => ({ file: f, stat: s })).catch(() => null)
|
|
15361
15813
|
)
|
|
15362
15814
|
);
|
|
15363
15815
|
const filtered = [];
|
|
@@ -15384,10 +15836,10 @@ async function runInteractive() {
|
|
|
15384
15836
|
s.start(`Scanning ${source.name} logs...`);
|
|
15385
15837
|
const allFiles = await source.scan();
|
|
15386
15838
|
const allGroups = await mergeByRepo(allFiles);
|
|
15387
|
-
const repoRoot =
|
|
15839
|
+
const repoRoot = path22.resolve(repo.root);
|
|
15388
15840
|
const matching = allGroups.filter((g) => {
|
|
15389
|
-
const resolved =
|
|
15390
|
-
return resolved === repoRoot || resolved.startsWith(repoRoot +
|
|
15841
|
+
const resolved = path22.resolve(g.repoPath);
|
|
15842
|
+
return resolved === repoRoot || resolved.startsWith(repoRoot + path22.sep);
|
|
15391
15843
|
});
|
|
15392
15844
|
if (matching.length === 0) {
|
|
15393
15845
|
s.stop(`No ${source.name} logs found for ${repo.name}.`);
|
|
@@ -15418,7 +15870,7 @@ async function runInteractive() {
|
|
|
15418
15870
|
}
|
|
15419
15871
|
}
|
|
15420
15872
|
const envFileNames = await discoverEnvFiles(repoRoot);
|
|
15421
|
-
const envFilePaths = envFileNames.map((n) =>
|
|
15873
|
+
const envFilePaths = envFileNames.map((n) => path22.join(repoRoot, n));
|
|
15422
15874
|
const additionalFiles = await promptSecretFiles(envFileNames);
|
|
15423
15875
|
const secretResult = await collectSecrets(
|
|
15424
15876
|
repoRoot,
|