hillclimb 0.1.8 → 0.2.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/README.md +2 -2
- package/dist/cli.js +1176 -216
- 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 fs17 from "fs";
|
|
5
|
+
import path20 from "path";
|
|
6
6
|
import * as p6 from "@clack/prompts";
|
|
7
7
|
|
|
8
8
|
// src/commands/init.ts
|
|
@@ -125,16 +125,65 @@ function detectRepoRoot() {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
// src/
|
|
128
|
+
// src/gitignore.ts
|
|
129
129
|
import fs2 from "fs";
|
|
130
130
|
import path3 from "path";
|
|
131
|
-
|
|
131
|
+
function toGitignorePattern(repoRoot, file) {
|
|
132
|
+
const rel = path3.relative(repoRoot, file);
|
|
133
|
+
if (!rel || rel.startsWith("..") || path3.isAbsolute(rel)) return null;
|
|
134
|
+
return `/${rel.split(path3.sep).join("/")}`;
|
|
135
|
+
}
|
|
136
|
+
function normalizePattern(pattern) {
|
|
137
|
+
return pattern.trim().replace(/\\/g, "/").replace(/^\/+/, "");
|
|
138
|
+
}
|
|
139
|
+
async function ensureGitignored(repoRoot, files) {
|
|
140
|
+
const gitignorePath = path3.join(repoRoot, ".gitignore");
|
|
141
|
+
const patterns = [
|
|
142
|
+
...new Set(
|
|
143
|
+
files.map((file) => toGitignorePattern(repoRoot, file)).filter((pattern) => pattern !== null)
|
|
144
|
+
)
|
|
145
|
+
];
|
|
146
|
+
let content = "";
|
|
147
|
+
let created = false;
|
|
148
|
+
try {
|
|
149
|
+
content = await fs2.promises.readFile(gitignorePath, "utf-8");
|
|
150
|
+
} catch (err) {
|
|
151
|
+
if (err.code !== "ENOENT") throw err;
|
|
152
|
+
created = true;
|
|
153
|
+
}
|
|
154
|
+
const existing = new Set(
|
|
155
|
+
content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map(normalizePattern)
|
|
156
|
+
);
|
|
157
|
+
const added = patterns.filter(
|
|
158
|
+
(pattern) => !existing.has(normalizePattern(pattern))
|
|
159
|
+
);
|
|
160
|
+
if (added.length === 0) {
|
|
161
|
+
return { path: gitignorePath, added: [], created: false, changed: false };
|
|
162
|
+
}
|
|
163
|
+
let next = content;
|
|
164
|
+
if (next && !next.endsWith("\n")) next += "\n";
|
|
165
|
+
if (next) next += "\n";
|
|
166
|
+
next += ["# Hillclimb hook files", ...added].join("\n");
|
|
167
|
+
next += "\n";
|
|
168
|
+
await fs2.promises.writeFile(gitignorePath, next);
|
|
169
|
+
return {
|
|
170
|
+
path: gitignorePath,
|
|
171
|
+
added,
|
|
172
|
+
created,
|
|
173
|
+
changed: true
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/identity.ts
|
|
178
|
+
import fs3 from "fs";
|
|
179
|
+
import path4 from "path";
|
|
180
|
+
var IDENTITY_PATH = path4.join(configDir(), "identity.json");
|
|
132
181
|
function normalizeUrl(apiBaseUrl) {
|
|
133
182
|
return apiBaseUrl.replace(/\/$/, "");
|
|
134
183
|
}
|
|
135
184
|
async function loadAllIdentities() {
|
|
136
185
|
try {
|
|
137
|
-
const raw = await
|
|
186
|
+
const raw = await fs3.promises.readFile(IDENTITY_PATH, "utf-8");
|
|
138
187
|
const parsed = JSON.parse(raw);
|
|
139
188
|
if (!parsed.identities || typeof parsed.identities !== "object") {
|
|
140
189
|
return { identities: {} };
|
|
@@ -149,12 +198,12 @@ async function loadIdentity(apiBaseUrl) {
|
|
|
149
198
|
return file.identities[normalizeUrl(apiBaseUrl)] ?? null;
|
|
150
199
|
}
|
|
151
200
|
async function writeIdentityFile(file) {
|
|
152
|
-
await
|
|
201
|
+
await fs3.promises.mkdir(configDir(), { recursive: true, mode: 448 });
|
|
153
202
|
const tmp = `${IDENTITY_PATH}.tmp`;
|
|
154
|
-
await
|
|
203
|
+
await fs3.promises.writeFile(tmp, JSON.stringify(file, null, 2), {
|
|
155
204
|
mode: 384
|
|
156
205
|
});
|
|
157
|
-
await
|
|
206
|
+
await fs3.promises.rename(tmp, IDENTITY_PATH);
|
|
158
207
|
}
|
|
159
208
|
async function saveIdentity(identity) {
|
|
160
209
|
const file = await loadAllIdentities();
|
|
@@ -192,8 +241,8 @@ function formatError(err) {
|
|
|
192
241
|
}
|
|
193
242
|
|
|
194
243
|
// src/platform/log.ts
|
|
195
|
-
import
|
|
196
|
-
import
|
|
244
|
+
import fs4 from "fs";
|
|
245
|
+
import path5 from "path";
|
|
197
246
|
var PT_TIME_ZONE = "America/Los_Angeles";
|
|
198
247
|
function pacificParts(date) {
|
|
199
248
|
const parts = new Intl.DateTimeFormat("en-US", {
|
|
@@ -244,10 +293,10 @@ function pacificDateString(date) {
|
|
|
244
293
|
return `${p7.year}-${p7.month}-${p7.day}`;
|
|
245
294
|
}
|
|
246
295
|
function logsDir() {
|
|
247
|
-
return
|
|
296
|
+
return path5.join(configDir(), "logs");
|
|
248
297
|
}
|
|
249
298
|
function todayLogPath() {
|
|
250
|
-
return
|
|
299
|
+
return path5.join(logsDir(), `${pacificDateString(/* @__PURE__ */ new Date())}.log`);
|
|
251
300
|
}
|
|
252
301
|
var logPrefix = "";
|
|
253
302
|
function setLogPrefix(prefix) {
|
|
@@ -258,8 +307,8 @@ function appendLog(level, message) {
|
|
|
258
307
|
const line = `[${pacificTimestamp(/* @__PURE__ */ new Date())}] [${level}]${tag} ${message}
|
|
259
308
|
`;
|
|
260
309
|
try {
|
|
261
|
-
|
|
262
|
-
|
|
310
|
+
fs4.mkdirSync(logsDir(), { recursive: true, mode: 448 });
|
|
311
|
+
fs4.appendFileSync(todayLogPath(), line);
|
|
263
312
|
} catch {
|
|
264
313
|
process.stderr.write(`hillclimb:${tag} ${level}: ${message}
|
|
265
314
|
`);
|
|
@@ -547,9 +596,9 @@ var PlatformClient = class {
|
|
|
547
596
|
};
|
|
548
597
|
|
|
549
598
|
// src/platform/hooks.ts
|
|
550
|
-
import
|
|
599
|
+
import fs5 from "fs";
|
|
551
600
|
import os2 from "os";
|
|
552
|
-
import
|
|
601
|
+
import path6 from "path";
|
|
553
602
|
var HOOK_CMD = (sub) => `npx hillclimb@latest ${sub}`;
|
|
554
603
|
var GIT_TRACES_CMD = (tool) => `npx hillclimb@latest git-traces --tool=${tool}`;
|
|
555
604
|
var CLAUDE_SESSIONEND_UPLOAD_TIMEOUT_SECONDS = 30;
|
|
@@ -569,7 +618,7 @@ var TOOLS = [
|
|
|
569
618
|
{ eventName: "Stop", command: GIT_TRACES_CMD("claude") },
|
|
570
619
|
{ eventName: "SessionEnd", command: GIT_TRACES_CMD("claude") }
|
|
571
620
|
],
|
|
572
|
-
detect: () => isDir(
|
|
621
|
+
detect: () => isDir(path6.join(os2.homedir(), ".claude"))
|
|
573
622
|
},
|
|
574
623
|
{
|
|
575
624
|
tool: "cursor",
|
|
@@ -582,7 +631,7 @@ var TOOLS = [
|
|
|
582
631
|
{ eventName: "stop", command: GIT_TRACES_CMD("cursor") },
|
|
583
632
|
{ eventName: "sessionEnd", command: GIT_TRACES_CMD("cursor") }
|
|
584
633
|
],
|
|
585
|
-
detect: () => isDir(
|
|
634
|
+
detect: () => isDir(path6.join(os2.homedir(), ".cursor"))
|
|
586
635
|
},
|
|
587
636
|
{
|
|
588
637
|
// opencode has no settings-file hook system (the `experimental.hook` block
|
|
@@ -597,7 +646,7 @@ var TOOLS = [
|
|
|
597
646
|
format: "opencode",
|
|
598
647
|
events: [],
|
|
599
648
|
// Events are wired inside the plugin itself; see OPENCODE_PLUGIN_CONTENT.
|
|
600
|
-
detect: () => isDir(
|
|
649
|
+
detect: () => isDir(path6.join(os2.homedir(), ".local", "share", "opencode")) || isDir(path6.join(os2.homedir(), ".config", "opencode"))
|
|
601
650
|
},
|
|
602
651
|
{
|
|
603
652
|
// VS Code Copilot Chat. Same "no SessionEnd" constraint as Codex (the
|
|
@@ -637,23 +686,23 @@ var TOOLS = [
|
|
|
637
686
|
{ eventName: "SessionStart", command: GIT_TRACES_CMD("codex") },
|
|
638
687
|
{ eventName: "Stop", command: GIT_TRACES_CMD("codex") }
|
|
639
688
|
],
|
|
640
|
-
detect: () => isDir(
|
|
689
|
+
detect: () => isDir(path6.join(os2.homedir(), ".codex"))
|
|
641
690
|
}
|
|
642
691
|
];
|
|
643
692
|
function isDir(p7) {
|
|
644
693
|
try {
|
|
645
|
-
return
|
|
694
|
+
return fs5.statSync(p7).isDirectory();
|
|
646
695
|
} catch {
|
|
647
696
|
return false;
|
|
648
697
|
}
|
|
649
698
|
}
|
|
650
699
|
function copilotChatDetect() {
|
|
651
700
|
const home = os2.homedir();
|
|
652
|
-
const suffix =
|
|
701
|
+
const suffix = path6.join("User", "globalStorage", "github.copilot-chat");
|
|
653
702
|
const candidates = [
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
process.env.APPDATA ?
|
|
703
|
+
path6.join(home, "Library", "Application Support", "Code", suffix),
|
|
704
|
+
path6.join(home, ".config", "Code", suffix),
|
|
705
|
+
process.env.APPDATA ? path6.join(process.env.APPDATA, "Code", suffix) : null
|
|
657
706
|
].filter((p7) => p7 !== null);
|
|
658
707
|
return candidates.some(isDir);
|
|
659
708
|
}
|
|
@@ -662,11 +711,11 @@ function ensureHooks(settings) {
|
|
|
662
711
|
return settings.hooks;
|
|
663
712
|
}
|
|
664
713
|
function settingsPath(repoRoot, def) {
|
|
665
|
-
return
|
|
714
|
+
return path6.join(repoRoot, def.settingsFile);
|
|
666
715
|
}
|
|
667
716
|
async function readJson(file) {
|
|
668
717
|
try {
|
|
669
|
-
const raw = await
|
|
718
|
+
const raw = await fs5.promises.readFile(file, "utf-8");
|
|
670
719
|
const parsed = JSON.parse(raw);
|
|
671
720
|
if (parsed && typeof parsed === "object") return parsed;
|
|
672
721
|
return {};
|
|
@@ -676,8 +725,8 @@ async function readJson(file) {
|
|
|
676
725
|
}
|
|
677
726
|
}
|
|
678
727
|
async function writeJson(file, obj) {
|
|
679
|
-
await
|
|
680
|
-
await
|
|
728
|
+
await fs5.promises.mkdir(path6.dirname(file), { recursive: true });
|
|
729
|
+
await fs5.promises.writeFile(file, `${JSON.stringify(obj, null, 2)}
|
|
681
730
|
`);
|
|
682
731
|
}
|
|
683
732
|
function claudeHookPresent(matchers, command, options = {}) {
|
|
@@ -801,7 +850,7 @@ function copilotUninstall(settings, eventName, command) {
|
|
|
801
850
|
}
|
|
802
851
|
return true;
|
|
803
852
|
}
|
|
804
|
-
var OPENCODE_PLUGIN_VERSION =
|
|
853
|
+
var OPENCODE_PLUGIN_VERSION = 4;
|
|
805
854
|
var OPENCODE_PLUGIN_MARKER = `// HILLCLIMB_OPENCODE_PLUGIN_VERSION=${OPENCODE_PLUGIN_VERSION}`;
|
|
806
855
|
var OPENCODE_PLUGIN_CONTENT = `${OPENCODE_PLUGIN_MARKER}
|
|
807
856
|
// Auto-installed by \`npx hillclimb\`. Do not edit manually \u2014 re-running
|
|
@@ -815,20 +864,54 @@ import path from "node:path";
|
|
|
815
864
|
const TOOL = "opencode";
|
|
816
865
|
const TRANSCRIPT_DIR = path.join(os.homedir(), ".hillclimb", "opencode-transcripts");
|
|
817
866
|
|
|
818
|
-
// sessionID -> Map<messageID,
|
|
819
|
-
// message.updated
|
|
820
|
-
//
|
|
867
|
+
// sessionID -> Map<messageID, { info, parts }>. Using a Map-of-Maps so late
|
|
868
|
+
// message.updated / message.part.updated events overwrite earlier versions
|
|
869
|
+
// instead of appending duplicates.
|
|
821
870
|
const sessionMessages = new Map();
|
|
822
871
|
|
|
823
|
-
function
|
|
824
|
-
if (!sessionID || !
|
|
872
|
+
function ensureRecord(sessionID, messageID, info) {
|
|
873
|
+
if (!sessionID || !messageID) return null;
|
|
825
874
|
let bucket = sessionMessages.get(sessionID);
|
|
826
875
|
if (!bucket) {
|
|
827
876
|
bucket = new Map();
|
|
828
877
|
sessionMessages.set(sessionID, bucket);
|
|
829
878
|
}
|
|
830
|
-
|
|
831
|
-
|
|
879
|
+
let record = bucket.get(messageID);
|
|
880
|
+
if (!record) {
|
|
881
|
+
record = {
|
|
882
|
+
info: info || { id: messageID, sessionID },
|
|
883
|
+
parts: new Map(),
|
|
884
|
+
};
|
|
885
|
+
bucket.set(messageID, record);
|
|
886
|
+
} else if (info) {
|
|
887
|
+
record.info = Object.assign({}, record.info || {}, info);
|
|
888
|
+
}
|
|
889
|
+
return record;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
function ingestMessage(sessionID, info) {
|
|
893
|
+
if (!sessionID || !info) return;
|
|
894
|
+
const id = info.id || ((info.role || "unknown") + "-" + Date.now());
|
|
895
|
+
ensureRecord(sessionID, id, info);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function ingestPart(sessionID, part) {
|
|
899
|
+
if (!part) return;
|
|
900
|
+
const sid = sessionID || part.sessionID;
|
|
901
|
+
const messageID = part.messageID;
|
|
902
|
+
if (!sid || !messageID) return;
|
|
903
|
+
const record = ensureRecord(sid, messageID, { id: messageID, sessionID: sid });
|
|
904
|
+
if (!record) return;
|
|
905
|
+
const partID = part.id || ((part.type || "part") + "-" + record.parts.size);
|
|
906
|
+
record.parts.set(partID, part);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function serializeRecord(record) {
|
|
910
|
+
const info = record.info || {};
|
|
911
|
+
const parts = Array.from(record.parts.values());
|
|
912
|
+
if (parts.length === 0) return info;
|
|
913
|
+
// Preserve the old top-level message-info shape while adding parts.
|
|
914
|
+
return Object.assign({}, info, { parts });
|
|
832
915
|
}
|
|
833
916
|
|
|
834
917
|
function writeTranscript(sessionID) {
|
|
@@ -838,7 +921,7 @@ function writeTranscript(sessionID) {
|
|
|
838
921
|
fs.mkdirSync(TRANSCRIPT_DIR, { recursive: true });
|
|
839
922
|
const file = path.join(TRANSCRIPT_DIR, sessionID + ".jsonl");
|
|
840
923
|
const lines = [];
|
|
841
|
-
for (const
|
|
924
|
+
for (const record of bucket.values()) lines.push(JSON.stringify(serializeRecord(record)));
|
|
842
925
|
fs.writeFileSync(file, lines.join("\\n") + "\\n");
|
|
843
926
|
return file;
|
|
844
927
|
} catch {
|
|
@@ -868,17 +951,24 @@ export const HillclimbPlugin = async ({ directory }) => ({
|
|
|
868
951
|
event: async ({ event }) => {
|
|
869
952
|
const type = event && event.type;
|
|
870
953
|
const props = (event && event.properties) || {};
|
|
871
|
-
const
|
|
954
|
+
const info = props.info || {};
|
|
955
|
+
const sessionID = props.sessionID || info.sessionID || info.id;
|
|
956
|
+
const cwd = directory || props.directory || info.directory;
|
|
872
957
|
|
|
873
958
|
if (type === "message.updated") {
|
|
874
|
-
ingestMessage(sessionID,
|
|
959
|
+
ingestMessage(sessionID, info);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (type === "message.part.updated") {
|
|
964
|
+
ingestPart(sessionID, props.part);
|
|
875
965
|
return;
|
|
876
966
|
}
|
|
877
967
|
|
|
878
968
|
if (type === "session.created" && sessionID) {
|
|
879
969
|
spawnHillclimb("git-traces --tool=opencode", {
|
|
880
970
|
session_id: sessionID,
|
|
881
|
-
cwd
|
|
971
|
+
cwd,
|
|
882
972
|
hook_event_name: "session.created",
|
|
883
973
|
tool: TOOL,
|
|
884
974
|
});
|
|
@@ -891,7 +981,7 @@ export const HillclimbPlugin = async ({ directory }) => ({
|
|
|
891
981
|
// so a single session produces one contribution, not one per turn.
|
|
892
982
|
spawnHillclimb("git-traces --tool=opencode", {
|
|
893
983
|
session_id: sessionID,
|
|
894
|
-
cwd
|
|
984
|
+
cwd,
|
|
895
985
|
hook_event_name: "session.idle",
|
|
896
986
|
tool: TOOL,
|
|
897
987
|
});
|
|
@@ -909,7 +999,7 @@ export const HillclimbPlugin = async ({ directory }) => ({
|
|
|
909
999
|
if (transcriptPath) {
|
|
910
1000
|
spawnHillclimb("upload", {
|
|
911
1001
|
session_id: sessionID,
|
|
912
|
-
cwd
|
|
1002
|
+
cwd,
|
|
913
1003
|
transcript_path: transcriptPath,
|
|
914
1004
|
hook_event_name: "session.deleted",
|
|
915
1005
|
tool: TOOL,
|
|
@@ -929,7 +1019,7 @@ export const HillclimbPlugin = async ({ directory }) => ({
|
|
|
929
1019
|
if (transcriptPath) {
|
|
930
1020
|
spawnHillclimb("upload", {
|
|
931
1021
|
session_id: sid,
|
|
932
|
-
cwd
|
|
1022
|
+
cwd,
|
|
933
1023
|
transcript_path: transcriptPath,
|
|
934
1024
|
hook_event_name: "server.instance.disposed",
|
|
935
1025
|
tool: TOOL,
|
|
@@ -938,7 +1028,7 @@ export const HillclimbPlugin = async ({ directory }) => ({
|
|
|
938
1028
|
}
|
|
939
1029
|
sessionMessages.clear();
|
|
940
1030
|
spawnHillclimb("git-traces --tool=opencode", {
|
|
941
|
-
cwd
|
|
1031
|
+
cwd,
|
|
942
1032
|
hook_event_name: "server.instance.disposed",
|
|
943
1033
|
tool: TOOL,
|
|
944
1034
|
});
|
|
@@ -951,19 +1041,19 @@ export default HillclimbPlugin;
|
|
|
951
1041
|
`;
|
|
952
1042
|
async function opencodeInstall(file) {
|
|
953
1043
|
try {
|
|
954
|
-
const existing = await
|
|
1044
|
+
const existing = await fs5.promises.readFile(file, "utf-8");
|
|
955
1045
|
if (existing === OPENCODE_PLUGIN_CONTENT) {
|
|
956
1046
|
return { installed: 0, alreadyPresent: 1 };
|
|
957
1047
|
}
|
|
958
1048
|
} catch {
|
|
959
1049
|
}
|
|
960
|
-
await
|
|
961
|
-
await
|
|
1050
|
+
await fs5.promises.mkdir(path6.dirname(file), { recursive: true });
|
|
1051
|
+
await fs5.promises.writeFile(file, OPENCODE_PLUGIN_CONTENT);
|
|
962
1052
|
return { installed: 1, alreadyPresent: 0 };
|
|
963
1053
|
}
|
|
964
1054
|
async function opencodeCheck(file) {
|
|
965
1055
|
try {
|
|
966
|
-
const content = await
|
|
1056
|
+
const content = await fs5.promises.readFile(file, "utf-8");
|
|
967
1057
|
return content.includes(OPENCODE_PLUGIN_MARKER);
|
|
968
1058
|
} catch {
|
|
969
1059
|
return false;
|
|
@@ -1021,7 +1111,7 @@ async function installHooksForTool(repoRoot, def) {
|
|
|
1021
1111
|
const file = settingsPath(repoRoot, def);
|
|
1022
1112
|
if (def.format === "opencode") {
|
|
1023
1113
|
const r = await opencodeInstall(file);
|
|
1024
|
-
return { settingsFile: file, ...r };
|
|
1114
|
+
return { settingsFile: file, ...r, changed: r.installed > 0 };
|
|
1025
1115
|
}
|
|
1026
1116
|
const settings = await readJson(file);
|
|
1027
1117
|
let installed = 0;
|
|
@@ -1044,7 +1134,7 @@ async function installHooksForTool(repoRoot, def) {
|
|
|
1044
1134
|
}
|
|
1045
1135
|
}
|
|
1046
1136
|
if (mutated) await writeJson(file, settings);
|
|
1047
|
-
return { settingsFile: file, installed, alreadyPresent };
|
|
1137
|
+
return { settingsFile: file, installed, alreadyPresent, changed: mutated };
|
|
1048
1138
|
}
|
|
1049
1139
|
async function checkHooksForTool(repoRoot, def) {
|
|
1050
1140
|
const file = settingsPath(repoRoot, def);
|
|
@@ -1084,6 +1174,29 @@ async function installDetectedHooks(repoRoot) {
|
|
|
1084
1174
|
}
|
|
1085
1175
|
return results;
|
|
1086
1176
|
}
|
|
1177
|
+
async function healHookForTool(repoRoot, tool) {
|
|
1178
|
+
const def = TOOLS.find((candidate) => candidate.tool === tool);
|
|
1179
|
+
if (!def) {
|
|
1180
|
+
return {
|
|
1181
|
+
tool,
|
|
1182
|
+
changed: false,
|
|
1183
|
+
alreadyCurrent: false,
|
|
1184
|
+
installed: 0,
|
|
1185
|
+
alreadyPresent: 0,
|
|
1186
|
+
skipped: "unsupported-tool"
|
|
1187
|
+
};
|
|
1188
|
+
}
|
|
1189
|
+
const r = await installHooksForTool(repoRoot, def);
|
|
1190
|
+
return {
|
|
1191
|
+
tool: def.tool,
|
|
1192
|
+
label: def.label,
|
|
1193
|
+
settingsFile: r.settingsFile,
|
|
1194
|
+
changed: r.changed,
|
|
1195
|
+
alreadyCurrent: !r.changed,
|
|
1196
|
+
installed: r.installed,
|
|
1197
|
+
alreadyPresent: r.alreadyPresent
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1087
1200
|
async function checkAllHooks(repoRoot) {
|
|
1088
1201
|
const results = [];
|
|
1089
1202
|
for (const def of TOOLS) {
|
|
@@ -1097,17 +1210,17 @@ async function checkAllHooks(repoRoot) {
|
|
|
1097
1210
|
}
|
|
1098
1211
|
return results;
|
|
1099
1212
|
}
|
|
1100
|
-
var CODEX_CONFIG_PATH =
|
|
1213
|
+
var CODEX_CONFIG_PATH = path6.join(os2.homedir(), ".codex", "config.toml");
|
|
1101
1214
|
async function ensureCodexHooksEnabled() {
|
|
1102
1215
|
let content;
|
|
1103
1216
|
try {
|
|
1104
|
-
content = await
|
|
1217
|
+
content = await fs5.promises.readFile(CODEX_CONFIG_PATH, "utf-8");
|
|
1105
1218
|
} catch (err) {
|
|
1106
1219
|
if (err.code === "ENOENT") {
|
|
1107
|
-
await
|
|
1220
|
+
await fs5.promises.mkdir(path6.dirname(CODEX_CONFIG_PATH), {
|
|
1108
1221
|
recursive: true
|
|
1109
1222
|
});
|
|
1110
|
-
await
|
|
1223
|
+
await fs5.promises.writeFile(
|
|
1111
1224
|
CODEX_CONFIG_PATH,
|
|
1112
1225
|
"[features]\nhooks = true\n"
|
|
1113
1226
|
);
|
|
@@ -1148,7 +1261,7 @@ hooks = true
|
|
|
1148
1261
|
`;
|
|
1149
1262
|
}
|
|
1150
1263
|
if (content === original) return false;
|
|
1151
|
-
await
|
|
1264
|
+
await fs5.promises.writeFile(CODEX_CONFIG_PATH, content);
|
|
1152
1265
|
return true;
|
|
1153
1266
|
}
|
|
1154
1267
|
var CLAUDE_DEF = TOOLS[0];
|
|
@@ -1571,6 +1684,29 @@ async function runInit(args = []) {
|
|
|
1571
1684
|
p3.log.success(`Installed ${r.label} hook`);
|
|
1572
1685
|
}
|
|
1573
1686
|
}
|
|
1687
|
+
try {
|
|
1688
|
+
const ignored = await ensureGitignored(
|
|
1689
|
+
repoRoot,
|
|
1690
|
+
hookResults.map((r) => r.settingsFile)
|
|
1691
|
+
);
|
|
1692
|
+
if (ignored.changed) {
|
|
1693
|
+
appendLog(
|
|
1694
|
+
"info",
|
|
1695
|
+
`init: gitignored hook files (${ignored.added.join(",")})`
|
|
1696
|
+
);
|
|
1697
|
+
p3.log.success(
|
|
1698
|
+
`${ignored.created ? "Created" : "Updated"} .gitignore for hook files`
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1701
|
+
} catch (err) {
|
|
1702
|
+
appendLog(
|
|
1703
|
+
"warn",
|
|
1704
|
+
`init: could not update .gitignore for hook files: ${formatError(err)}`
|
|
1705
|
+
);
|
|
1706
|
+
p3.log.warn(
|
|
1707
|
+
`Could not update .gitignore for hook files: ${err instanceof Error ? err.message : String(err)}`
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1574
1710
|
if (hookResults.some((r) => r.tool === "codex")) {
|
|
1575
1711
|
try {
|
|
1576
1712
|
const changed = await ensureCodexHooksEnabled();
|
|
@@ -1821,7 +1957,7 @@ async function runLogout(args = []) {
|
|
|
1821
1957
|
}
|
|
1822
1958
|
|
|
1823
1959
|
// src/commands/status.ts
|
|
1824
|
-
import
|
|
1960
|
+
import path7 from "path";
|
|
1825
1961
|
async function runStatus(args = []) {
|
|
1826
1962
|
const debug = args.includes("--debug");
|
|
1827
1963
|
header("status");
|
|
@@ -1839,7 +1975,7 @@ async function runStatus(args = []) {
|
|
|
1839
1975
|
return;
|
|
1840
1976
|
}
|
|
1841
1977
|
const { repoRoot, config } = match;
|
|
1842
|
-
const repoName = repo?.name ??
|
|
1978
|
+
const repoName = repo?.name ?? path7.basename(repoRoot);
|
|
1843
1979
|
if (IS_TTY) {
|
|
1844
1980
|
process.stdout.write(` ${dim("Checking login...")}`);
|
|
1845
1981
|
}
|
|
@@ -1933,7 +2069,7 @@ async function runStatus(args = []) {
|
|
|
1933
2069
|
try {
|
|
1934
2070
|
const hookStatuses = await checkAllHooks(repoRoot);
|
|
1935
2071
|
for (const h of hookStatuses) {
|
|
1936
|
-
const rel =
|
|
2072
|
+
const rel = path7.relative(repoRoot, h.settingsFile) || h.settingsFile;
|
|
1937
2073
|
debugRow(
|
|
1938
2074
|
`${h.label} hook:`,
|
|
1939
2075
|
`${rel} ${dim(`(${h.installed ? "installed" : "missing"})`)}`
|
|
@@ -1950,20 +2086,20 @@ async function runStatus(args = []) {
|
|
|
1950
2086
|
|
|
1951
2087
|
// src/commands/upload.ts
|
|
1952
2088
|
import { spawn as spawn2 } from "child_process";
|
|
1953
|
-
import
|
|
2089
|
+
import fs9 from "fs";
|
|
1954
2090
|
import os5 from "os";
|
|
1955
|
-
import
|
|
2091
|
+
import path12 from "path";
|
|
1956
2092
|
|
|
1957
2093
|
// src/middleware/pattern-redact.ts
|
|
1958
2094
|
import os3 from "os";
|
|
1959
2095
|
import { Worker } from "worker_threads";
|
|
1960
2096
|
|
|
1961
2097
|
// src/middleware/file-utils.ts
|
|
1962
|
-
import
|
|
2098
|
+
import fs6 from "fs";
|
|
1963
2099
|
async function checkBinary(filePath) {
|
|
1964
2100
|
let handle = null;
|
|
1965
2101
|
try {
|
|
1966
|
-
handle = await
|
|
2102
|
+
handle = await fs6.promises.open(filePath, "r");
|
|
1967
2103
|
const buf = Buffer.alloc(8192);
|
|
1968
2104
|
const { bytesRead } = await handle.read(buf, 0, 8192, 0);
|
|
1969
2105
|
for (let i = 0; i < bytesRead; i++) {
|
|
@@ -1988,7 +2124,7 @@ async function readFileContent(file) {
|
|
|
1988
2124
|
return { kind: "binary" };
|
|
1989
2125
|
}
|
|
1990
2126
|
try {
|
|
1991
|
-
const content = await
|
|
2127
|
+
const content = await fs6.promises.readFile(file.absolutePath, "utf-8");
|
|
1992
2128
|
return { kind: "text", content };
|
|
1993
2129
|
} catch {
|
|
1994
2130
|
return { kind: "error" };
|
|
@@ -10440,8 +10576,8 @@ var RedactMiddleware = class {
|
|
|
10440
10576
|
var middleware = [];
|
|
10441
10577
|
|
|
10442
10578
|
// src/middleware/secrets.ts
|
|
10443
|
-
import
|
|
10444
|
-
import
|
|
10579
|
+
import fs7 from "fs";
|
|
10580
|
+
import path8 from "path";
|
|
10445
10581
|
var KNOWN_NON_SECRETS = /* @__PURE__ */ new Set([
|
|
10446
10582
|
"true",
|
|
10447
10583
|
"false",
|
|
@@ -10521,7 +10657,7 @@ async function parseEnvFile(filePath) {
|
|
|
10521
10657
|
const values = [];
|
|
10522
10658
|
let content;
|
|
10523
10659
|
try {
|
|
10524
|
-
content = await
|
|
10660
|
+
content = await fs7.promises.readFile(filePath, "utf-8");
|
|
10525
10661
|
} catch {
|
|
10526
10662
|
return values;
|
|
10527
10663
|
}
|
|
@@ -10589,16 +10725,16 @@ function addWithVariants(set, value) {
|
|
|
10589
10725
|
async function discoverEnvFiles(repoRoot) {
|
|
10590
10726
|
let entries;
|
|
10591
10727
|
try {
|
|
10592
|
-
entries = await
|
|
10728
|
+
entries = await fs7.promises.readdir(repoRoot);
|
|
10593
10729
|
} catch {
|
|
10594
10730
|
return [];
|
|
10595
10731
|
}
|
|
10596
10732
|
const envFiles = [];
|
|
10597
10733
|
for (const name of entries) {
|
|
10598
10734
|
if (!name.startsWith(".env")) continue;
|
|
10599
|
-
const filePath =
|
|
10735
|
+
const filePath = path8.join(repoRoot, name);
|
|
10600
10736
|
try {
|
|
10601
|
-
const stat = await
|
|
10737
|
+
const stat = await fs7.promises.stat(filePath);
|
|
10602
10738
|
if (stat.isFile()) envFiles.push(name);
|
|
10603
10739
|
} catch {
|
|
10604
10740
|
}
|
|
@@ -10621,7 +10757,7 @@ async function collectSecrets(repoRoot, envFiles, additionalFiles) {
|
|
|
10621
10757
|
}
|
|
10622
10758
|
}
|
|
10623
10759
|
for (const filePath of additionalFiles) {
|
|
10624
|
-
const resolved =
|
|
10760
|
+
const resolved = path8.resolve(repoRoot, filePath);
|
|
10625
10761
|
sourceFiles.push(resolved);
|
|
10626
10762
|
for (const value of await parseEnvFile(resolved)) {
|
|
10627
10763
|
if (isUsableValue(value)) {
|
|
@@ -10646,7 +10782,7 @@ async function collectSecrets(repoRoot, envFiles, additionalFiles) {
|
|
|
10646
10782
|
}
|
|
10647
10783
|
|
|
10648
10784
|
// src/normalizer/index.ts
|
|
10649
|
-
import
|
|
10785
|
+
import path9 from "path";
|
|
10650
10786
|
|
|
10651
10787
|
// src/normalizer/claude.ts
|
|
10652
10788
|
function stringify(value) {
|
|
@@ -11416,6 +11552,288 @@ function convertEventToStep2(event, stepId, defaultModelName) {
|
|
|
11416
11552
|
return null;
|
|
11417
11553
|
}
|
|
11418
11554
|
|
|
11555
|
+
// src/normalizer/types.ts
|
|
11556
|
+
var ATIF_VERSION = "ATIF-v1.6";
|
|
11557
|
+
function excludeNone(obj) {
|
|
11558
|
+
const result = {};
|
|
11559
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
11560
|
+
if (value === void 0 || value === null) continue;
|
|
11561
|
+
if (Array.isArray(value)) {
|
|
11562
|
+
result[key] = value.map(
|
|
11563
|
+
(item) => typeof item === "object" && item !== null && !Array.isArray(item) ? excludeNone(item) : item
|
|
11564
|
+
);
|
|
11565
|
+
} else if (typeof value === "object" && !Array.isArray(value)) {
|
|
11566
|
+
result[key] = excludeNone(value);
|
|
11567
|
+
} else {
|
|
11568
|
+
result[key] = value;
|
|
11569
|
+
}
|
|
11570
|
+
}
|
|
11571
|
+
return result;
|
|
11572
|
+
}
|
|
11573
|
+
|
|
11574
|
+
// src/normalizer/copilotChat.ts
|
|
11575
|
+
function asObject(value) {
|
|
11576
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
11577
|
+
}
|
|
11578
|
+
function asNumber(value) {
|
|
11579
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
11580
|
+
}
|
|
11581
|
+
function compactExtra(extra) {
|
|
11582
|
+
const result = {};
|
|
11583
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
11584
|
+
if (value !== void 0 && value !== null) result[key] = value;
|
|
11585
|
+
}
|
|
11586
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
11587
|
+
}
|
|
11588
|
+
function parseJsonLines(content) {
|
|
11589
|
+
const entries = [];
|
|
11590
|
+
for (const line of content.split("\n")) {
|
|
11591
|
+
const trimmed = line.trim();
|
|
11592
|
+
if (!trimmed) continue;
|
|
11593
|
+
try {
|
|
11594
|
+
const parsed = asObject(JSON.parse(trimmed));
|
|
11595
|
+
if (!parsed) continue;
|
|
11596
|
+
entries.push({
|
|
11597
|
+
type: typeof parsed.type === "string" ? parsed.type : void 0,
|
|
11598
|
+
data: asObject(parsed.data),
|
|
11599
|
+
id: typeof parsed.id === "string" ? parsed.id : void 0,
|
|
11600
|
+
timestamp: typeof parsed.timestamp === "string" ? parsed.timestamp : void 0
|
|
11601
|
+
});
|
|
11602
|
+
} catch {
|
|
11603
|
+
}
|
|
11604
|
+
}
|
|
11605
|
+
return entries;
|
|
11606
|
+
}
|
|
11607
|
+
function parseArguments(value) {
|
|
11608
|
+
const obj = asObject(value);
|
|
11609
|
+
if (obj) return obj;
|
|
11610
|
+
if (typeof value === "string") {
|
|
11611
|
+
try {
|
|
11612
|
+
const parsed = asObject(JSON.parse(value));
|
|
11613
|
+
if (parsed) return parsed;
|
|
11614
|
+
} catch {
|
|
11615
|
+
return value ? { input: value } : {};
|
|
11616
|
+
}
|
|
11617
|
+
}
|
|
11618
|
+
return value === void 0 || value === null ? {} : { value };
|
|
11619
|
+
}
|
|
11620
|
+
function buildMetrics2(data) {
|
|
11621
|
+
const inputTokens = asNumber(data.inputTokens) ?? 0;
|
|
11622
|
+
const outputTokens = asNumber(data.outputTokens) ?? 0;
|
|
11623
|
+
const cacheReadTokens = asNumber(data.cacheReadTokens) ?? 0;
|
|
11624
|
+
const cacheWriteTokens = asNumber(data.cacheWriteTokens) ?? 0;
|
|
11625
|
+
const cost = asNumber(data.cost);
|
|
11626
|
+
if (!inputTokens && !outputTokens && !cacheReadTokens && !cacheWriteTokens && !cost) {
|
|
11627
|
+
return void 0;
|
|
11628
|
+
}
|
|
11629
|
+
return {
|
|
11630
|
+
prompt_tokens: inputTokens + cacheReadTokens || void 0,
|
|
11631
|
+
completion_tokens: outputTokens || void 0,
|
|
11632
|
+
cached_tokens: cacheReadTokens || void 0,
|
|
11633
|
+
cost_usd: cost || void 0,
|
|
11634
|
+
extra: compactExtra({
|
|
11635
|
+
cache_write_tokens: cacheWriteTokens || void 0,
|
|
11636
|
+
duration_ms: data.duration,
|
|
11637
|
+
initiator: data.initiator,
|
|
11638
|
+
api_call_id: data.apiCallId,
|
|
11639
|
+
provider_call_id: data.providerCallId,
|
|
11640
|
+
parent_tool_call_id: data.parentToolCallId,
|
|
11641
|
+
quota_snapshots: data.quotaSnapshots,
|
|
11642
|
+
copilot_usage: data.copilotUsage
|
|
11643
|
+
})
|
|
11644
|
+
};
|
|
11645
|
+
}
|
|
11646
|
+
function finalMetricsFromSteps(steps) {
|
|
11647
|
+
let prompt = 0;
|
|
11648
|
+
let completion = 0;
|
|
11649
|
+
let cached = 0;
|
|
11650
|
+
let cost = 0;
|
|
11651
|
+
for (const step of steps) {
|
|
11652
|
+
prompt += step.metrics?.prompt_tokens ?? 0;
|
|
11653
|
+
completion += step.metrics?.completion_tokens ?? 0;
|
|
11654
|
+
cached += step.metrics?.cached_tokens ?? 0;
|
|
11655
|
+
cost += step.metrics?.cost_usd ?? 0;
|
|
11656
|
+
}
|
|
11657
|
+
return {
|
|
11658
|
+
total_prompt_tokens: prompt || void 0,
|
|
11659
|
+
total_completion_tokens: completion || void 0,
|
|
11660
|
+
total_cached_tokens: cached || void 0,
|
|
11661
|
+
total_cost_usd: cost || void 0,
|
|
11662
|
+
total_steps: steps.length
|
|
11663
|
+
};
|
|
11664
|
+
}
|
|
11665
|
+
function makeToolCall(request) {
|
|
11666
|
+
const req = asObject(request);
|
|
11667
|
+
if (!req) return null;
|
|
11668
|
+
const callId = typeof req.toolCallId === "string" && req.toolCallId || typeof req.id === "string" && req.id || "";
|
|
11669
|
+
const name = typeof req.name === "string" && req.name || typeof req.toolName === "string" && req.toolName || "tool";
|
|
11670
|
+
return {
|
|
11671
|
+
tool_call_id: callId,
|
|
11672
|
+
function_name: name,
|
|
11673
|
+
arguments: parseArguments(req.arguments)
|
|
11674
|
+
};
|
|
11675
|
+
}
|
|
11676
|
+
function toolCallFromExecutionStart(data) {
|
|
11677
|
+
const callId = typeof data.toolCallId === "string" && data.toolCallId || typeof data.id === "string" && data.id || "";
|
|
11678
|
+
const name = typeof data.toolName === "string" && data.toolName || typeof data.name === "string" && data.name || "tool";
|
|
11679
|
+
return {
|
|
11680
|
+
tool_call_id: callId,
|
|
11681
|
+
function_name: name,
|
|
11682
|
+
arguments: parseArguments(data.arguments)
|
|
11683
|
+
};
|
|
11684
|
+
}
|
|
11685
|
+
function contentFromToolResult(data) {
|
|
11686
|
+
const result = asObject(data.result);
|
|
11687
|
+
if (typeof result?.content === "string") return result.content;
|
|
11688
|
+
if (typeof data.content === "string") return data.content;
|
|
11689
|
+
return void 0;
|
|
11690
|
+
}
|
|
11691
|
+
function convertCopilotChatToTrajectory(jsonlContent, sessionId) {
|
|
11692
|
+
const entries = parseJsonLines(jsonlContent);
|
|
11693
|
+
if (entries.length === 0) return null;
|
|
11694
|
+
let sid = sessionId ?? "unknown";
|
|
11695
|
+
let copilotVersion = "unknown";
|
|
11696
|
+
let vscodeVersion;
|
|
11697
|
+
let cwd;
|
|
11698
|
+
let defaultModelName;
|
|
11699
|
+
const steps = [];
|
|
11700
|
+
const pendingReasoning = [];
|
|
11701
|
+
const pendingToolSteps = /* @__PURE__ */ new Map();
|
|
11702
|
+
let lastAgentStep;
|
|
11703
|
+
for (const entry of entries) {
|
|
11704
|
+
const data = entry.data ?? {};
|
|
11705
|
+
if (entry.type === "session.start") {
|
|
11706
|
+
if (!sessionId && typeof data.sessionId === "string")
|
|
11707
|
+
sid = data.sessionId;
|
|
11708
|
+
if (typeof data.copilotVersion === "string")
|
|
11709
|
+
copilotVersion = data.copilotVersion;
|
|
11710
|
+
if (typeof data.vscodeVersion === "string")
|
|
11711
|
+
vscodeVersion = data.vscodeVersion;
|
|
11712
|
+
const context = asObject(data.context);
|
|
11713
|
+
if (typeof context?.cwd === "string") cwd = context.cwd;
|
|
11714
|
+
continue;
|
|
11715
|
+
}
|
|
11716
|
+
if (entry.type === "user.message" || entry.type === "system.message") {
|
|
11717
|
+
const content = typeof data.content === "string" && data.content || typeof data.transformedContent === "string" && data.transformedContent || "";
|
|
11718
|
+
if (!content.trim()) continue;
|
|
11719
|
+
const source = entry.type === "system.message" ? "system" : "user";
|
|
11720
|
+
const step = {
|
|
11721
|
+
step_id: steps.length + 1,
|
|
11722
|
+
timestamp: entry.timestamp,
|
|
11723
|
+
source,
|
|
11724
|
+
message: content,
|
|
11725
|
+
extra: compactExtra({
|
|
11726
|
+
attachments: data.attachments,
|
|
11727
|
+
source: data.source,
|
|
11728
|
+
agent_mode: data.agentMode,
|
|
11729
|
+
interaction_id: data.interactionId
|
|
11730
|
+
})
|
|
11731
|
+
};
|
|
11732
|
+
steps.push(step);
|
|
11733
|
+
continue;
|
|
11734
|
+
}
|
|
11735
|
+
if (entry.type === "assistant.reasoning") {
|
|
11736
|
+
if (typeof data.content === "string" && data.content.trim()) {
|
|
11737
|
+
pendingReasoning.push(data.content.trim());
|
|
11738
|
+
}
|
|
11739
|
+
continue;
|
|
11740
|
+
}
|
|
11741
|
+
if (entry.type === "assistant.message") {
|
|
11742
|
+
const toolCalls = (Array.isArray(data.toolRequests) ? data.toolRequests : []).flatMap((request) => {
|
|
11743
|
+
const call = makeToolCall(request);
|
|
11744
|
+
return call ? [call] : [];
|
|
11745
|
+
});
|
|
11746
|
+
const content = typeof data.content === "string" && data.content.trim() ? data.content.trim() : "";
|
|
11747
|
+
const reasoning = typeof data.reasoningText === "string" && data.reasoningText.trim() || pendingReasoning.join("\n\n") || void 0;
|
|
11748
|
+
pendingReasoning.length = 0;
|
|
11749
|
+
const step = {
|
|
11750
|
+
step_id: steps.length + 1,
|
|
11751
|
+
timestamp: entry.timestamp,
|
|
11752
|
+
source: "agent",
|
|
11753
|
+
message: content || (toolCalls.length > 0 ? "(tool use)" : ""),
|
|
11754
|
+
model_name: defaultModelName,
|
|
11755
|
+
extra: compactExtra({
|
|
11756
|
+
message_id: data.messageId,
|
|
11757
|
+
phase: data.phase,
|
|
11758
|
+
output_tokens: data.outputTokens
|
|
11759
|
+
})
|
|
11760
|
+
};
|
|
11761
|
+
if (reasoning) step.reasoning_content = reasoning;
|
|
11762
|
+
if (toolCalls.length > 0) step.tool_calls = toolCalls;
|
|
11763
|
+
if (step.message || step.tool_calls?.length || step.reasoning_content) {
|
|
11764
|
+
steps.push(step);
|
|
11765
|
+
lastAgentStep = step;
|
|
11766
|
+
for (const call of toolCalls) {
|
|
11767
|
+
if (call.tool_call_id) pendingToolSteps.set(call.tool_call_id, step);
|
|
11768
|
+
}
|
|
11769
|
+
}
|
|
11770
|
+
continue;
|
|
11771
|
+
}
|
|
11772
|
+
if (entry.type === "tool.execution_start") {
|
|
11773
|
+
const toolCall = toolCallFromExecutionStart(data);
|
|
11774
|
+
const step = {
|
|
11775
|
+
step_id: steps.length + 1,
|
|
11776
|
+
timestamp: entry.timestamp,
|
|
11777
|
+
source: "agent",
|
|
11778
|
+
message: `Executed ${toolCall.function_name} ${toolCall.tool_call_id}`.trim(),
|
|
11779
|
+
model_name: defaultModelName,
|
|
11780
|
+
tool_calls: [toolCall]
|
|
11781
|
+
};
|
|
11782
|
+
steps.push(step);
|
|
11783
|
+
lastAgentStep = step;
|
|
11784
|
+
if (toolCall.tool_call_id)
|
|
11785
|
+
pendingToolSteps.set(toolCall.tool_call_id, step);
|
|
11786
|
+
continue;
|
|
11787
|
+
}
|
|
11788
|
+
if (entry.type === "tool.execution_complete") {
|
|
11789
|
+
const callId = typeof data.toolCallId === "string" && data.toolCallId || typeof data.id === "string" && data.id || "";
|
|
11790
|
+
const target = pendingToolSteps.get(callId);
|
|
11791
|
+
const content = contentFromToolResult(data);
|
|
11792
|
+
if (target && content !== void 0) {
|
|
11793
|
+
const observation = target.observation ?? { results: [] };
|
|
11794
|
+
observation.results.push({
|
|
11795
|
+
source_call_id: callId || void 0,
|
|
11796
|
+
content
|
|
11797
|
+
});
|
|
11798
|
+
target.observation = observation;
|
|
11799
|
+
const extra = { ...target.extra ?? {} };
|
|
11800
|
+
extra.tool_success = data.success;
|
|
11801
|
+
target.extra = compactExtra(extra);
|
|
11802
|
+
}
|
|
11803
|
+
if (callId) pendingToolSteps.delete(callId);
|
|
11804
|
+
continue;
|
|
11805
|
+
}
|
|
11806
|
+
if (entry.type === "assistant.usage") {
|
|
11807
|
+
if (typeof data.model === "string" && !defaultModelName) {
|
|
11808
|
+
defaultModelName = data.model;
|
|
11809
|
+
if (lastAgentStep && !lastAgentStep.model_name) {
|
|
11810
|
+
lastAgentStep.model_name = defaultModelName;
|
|
11811
|
+
}
|
|
11812
|
+
}
|
|
11813
|
+
const metrics = buildMetrics2(data);
|
|
11814
|
+
if (metrics && lastAgentStep && !lastAgentStep.metrics) {
|
|
11815
|
+
lastAgentStep.metrics = metrics;
|
|
11816
|
+
}
|
|
11817
|
+
}
|
|
11818
|
+
}
|
|
11819
|
+
if (steps.length === 0) return null;
|
|
11820
|
+
return {
|
|
11821
|
+
schema_version: ATIF_VERSION,
|
|
11822
|
+
session_id: sid,
|
|
11823
|
+
agent: {
|
|
11824
|
+
name: "github-copilot-chat",
|
|
11825
|
+
version: copilotVersion,
|
|
11826
|
+
model_name: defaultModelName,
|
|
11827
|
+
extra: compactExtra({
|
|
11828
|
+
vscode_version: vscodeVersion,
|
|
11829
|
+
cwd
|
|
11830
|
+
})
|
|
11831
|
+
},
|
|
11832
|
+
steps,
|
|
11833
|
+
final_metrics: finalMetricsFromSteps(steps)
|
|
11834
|
+
};
|
|
11835
|
+
}
|
|
11836
|
+
|
|
11419
11837
|
// src/normalizer/cursor.ts
|
|
11420
11838
|
function convertCursorToTrajectory(jsonlContent, sessionId) {
|
|
11421
11839
|
const lines = [];
|
|
@@ -11473,22 +11891,501 @@ function convertCursorToTrajectory(jsonlContent, sessionId) {
|
|
|
11473
11891
|
};
|
|
11474
11892
|
}
|
|
11475
11893
|
|
|
11476
|
-
// src/normalizer/
|
|
11477
|
-
function
|
|
11894
|
+
// src/normalizer/opencode.ts
|
|
11895
|
+
function asObject2(value) {
|
|
11896
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
11897
|
+
}
|
|
11898
|
+
function asArray(value) {
|
|
11899
|
+
return Array.isArray(value) ? value : [];
|
|
11900
|
+
}
|
|
11901
|
+
function asNumber2(value) {
|
|
11902
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
11903
|
+
}
|
|
11904
|
+
function compactExtra2(extra) {
|
|
11478
11905
|
const result = {};
|
|
11479
|
-
for (const [key, value] of Object.entries(
|
|
11480
|
-
if (value
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
11906
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
11907
|
+
if (value !== void 0 && value !== null) result[key] = value;
|
|
11908
|
+
}
|
|
11909
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
11910
|
+
}
|
|
11911
|
+
function parseJsonLines2(content) {
|
|
11912
|
+
const events = [];
|
|
11913
|
+
for (const line of content.split("\n")) {
|
|
11914
|
+
const trimmed = line.trim();
|
|
11915
|
+
if (!trimmed) continue;
|
|
11916
|
+
try {
|
|
11917
|
+
const parsed = JSON.parse(trimmed);
|
|
11918
|
+
const obj = asObject2(parsed);
|
|
11919
|
+
if (obj) events.push(obj);
|
|
11920
|
+
} catch {
|
|
11921
|
+
}
|
|
11922
|
+
}
|
|
11923
|
+
return events;
|
|
11924
|
+
}
|
|
11925
|
+
function timestampToIso(value) {
|
|
11926
|
+
const numeric = asNumber2(value);
|
|
11927
|
+
if (numeric === void 0) return void 0;
|
|
11928
|
+
const millis = numeric < 1e12 ? numeric * 1e3 : numeric;
|
|
11929
|
+
const date = new Date(millis);
|
|
11930
|
+
return Number.isNaN(date.getTime()) ? void 0 : date.toISOString();
|
|
11931
|
+
}
|
|
11932
|
+
function timeFromObject(value) {
|
|
11933
|
+
const obj = asObject2(value);
|
|
11934
|
+
if (!obj) return void 0;
|
|
11935
|
+
return timestampToIso(obj.start ?? obj.created ?? obj.completed ?? obj.end);
|
|
11936
|
+
}
|
|
11937
|
+
function stringify2(value) {
|
|
11938
|
+
if (typeof value === "string") return value;
|
|
11939
|
+
try {
|
|
11940
|
+
return JSON.stringify(value);
|
|
11941
|
+
} catch {
|
|
11942
|
+
return String(value);
|
|
11943
|
+
}
|
|
11944
|
+
}
|
|
11945
|
+
function argsFromUnknown(value) {
|
|
11946
|
+
const obj = asObject2(value);
|
|
11947
|
+
if (obj) return obj;
|
|
11948
|
+
if (typeof value === "string") {
|
|
11949
|
+
try {
|
|
11950
|
+
const parsed = JSON.parse(value);
|
|
11951
|
+
const parsedObj = asObject2(parsed);
|
|
11952
|
+
if (parsedObj) return parsedObj;
|
|
11953
|
+
} catch {
|
|
11954
|
+
return value ? { input: value } : {};
|
|
11955
|
+
}
|
|
11956
|
+
}
|
|
11957
|
+
return value === void 0 || value === null ? {} : { value };
|
|
11958
|
+
}
|
|
11959
|
+
function modelNameFromInfo(info) {
|
|
11960
|
+
const model = asObject2(info.model);
|
|
11961
|
+
const modelID = typeof info.modelID === "string" && info.modelID || typeof model?.modelID === "string" && model.modelID || void 0;
|
|
11962
|
+
const providerID = typeof info.providerID === "string" && info.providerID || typeof model?.providerID === "string" && model.providerID || void 0;
|
|
11963
|
+
if (providerID && modelID) return `${providerID}/${modelID}`;
|
|
11964
|
+
return modelID;
|
|
11965
|
+
}
|
|
11966
|
+
function metricsFromTokens(tokens, cost) {
|
|
11967
|
+
const t = asObject2(tokens);
|
|
11968
|
+
if (!t) return void 0;
|
|
11969
|
+
const cache = asObject2(t.cache);
|
|
11970
|
+
const input = asNumber2(t.input) ?? 0;
|
|
11971
|
+
const output = asNumber2(t.output) ?? 0;
|
|
11972
|
+
const reasoning = asNumber2(t.reasoning) ?? 0;
|
|
11973
|
+
const cacheRead = asNumber2(cache?.read) ?? 0;
|
|
11974
|
+
const cacheWrite = asNumber2(cache?.write) ?? 0;
|
|
11975
|
+
const costUsd = asNumber2(cost);
|
|
11976
|
+
if (!input && !output && !cacheRead && !cacheWrite && !costUsd) {
|
|
11977
|
+
return void 0;
|
|
11978
|
+
}
|
|
11979
|
+
const extra = compactExtra2({
|
|
11980
|
+
reasoning_tokens: reasoning || void 0,
|
|
11981
|
+
cache_write_tokens: cacheWrite || void 0
|
|
11982
|
+
});
|
|
11983
|
+
return {
|
|
11984
|
+
prompt_tokens: input + cacheRead || void 0,
|
|
11985
|
+
completion_tokens: output || void 0,
|
|
11986
|
+
cached_tokens: cacheRead || void 0,
|
|
11987
|
+
cost_usd: costUsd || void 0,
|
|
11988
|
+
extra
|
|
11989
|
+
};
|
|
11990
|
+
}
|
|
11991
|
+
function metricsFromInfo(info) {
|
|
11992
|
+
return metricsFromTokens(info.tokens, info.cost);
|
|
11993
|
+
}
|
|
11994
|
+
function addFinalMetricTotals(totals, metrics) {
|
|
11995
|
+
if (!metrics) return;
|
|
11996
|
+
totals.total_prompt_tokens += metrics.prompt_tokens ?? 0;
|
|
11997
|
+
totals.total_completion_tokens += metrics.completion_tokens ?? 0;
|
|
11998
|
+
totals.total_cached_tokens += metrics.cached_tokens ?? 0;
|
|
11999
|
+
totals.total_cost_usd += metrics.cost_usd ?? 0;
|
|
12000
|
+
}
|
|
12001
|
+
function finalMetricsFromSteps2(steps) {
|
|
12002
|
+
const totals = {
|
|
12003
|
+
total_prompt_tokens: 0,
|
|
12004
|
+
total_completion_tokens: 0,
|
|
12005
|
+
total_cached_tokens: 0,
|
|
12006
|
+
total_cost_usd: 0,
|
|
12007
|
+
total_steps: steps.length,
|
|
12008
|
+
extra: {}
|
|
12009
|
+
};
|
|
12010
|
+
for (const step of steps) addFinalMetricTotals(totals, step.metrics);
|
|
12011
|
+
return {
|
|
12012
|
+
total_prompt_tokens: totals.total_prompt_tokens || void 0,
|
|
12013
|
+
total_completion_tokens: totals.total_completion_tokens || void 0,
|
|
12014
|
+
total_cached_tokens: totals.total_cached_tokens || void 0,
|
|
12015
|
+
total_cost_usd: totals.total_cost_usd || void 0,
|
|
12016
|
+
total_steps: steps.length
|
|
12017
|
+
};
|
|
12018
|
+
}
|
|
12019
|
+
function entryFromLine(line) {
|
|
12020
|
+
const info = asObject2(line.info);
|
|
12021
|
+
if (info) {
|
|
12022
|
+
return {
|
|
12023
|
+
info,
|
|
12024
|
+
parts: asArray(line.parts).flatMap((part) => {
|
|
12025
|
+
const obj = asObject2(part);
|
|
12026
|
+
return obj ? [obj] : [];
|
|
12027
|
+
})
|
|
12028
|
+
};
|
|
12029
|
+
}
|
|
12030
|
+
if (typeof line.role === "string" || typeof line.sessionID === "string") {
|
|
12031
|
+
return {
|
|
12032
|
+
info: line,
|
|
12033
|
+
parts: asArray(line.parts).flatMap((part) => {
|
|
12034
|
+
const obj = asObject2(part);
|
|
12035
|
+
return obj ? [obj] : [];
|
|
12036
|
+
})
|
|
12037
|
+
};
|
|
12038
|
+
}
|
|
12039
|
+
return null;
|
|
12040
|
+
}
|
|
12041
|
+
function entriesFromEventWrappers(lines) {
|
|
12042
|
+
const byMessage = /* @__PURE__ */ new Map();
|
|
12043
|
+
function getEntry(messageID, sessionID) {
|
|
12044
|
+
let entry = byMessage.get(messageID);
|
|
12045
|
+
if (!entry) {
|
|
12046
|
+
entry = { info: { id: messageID, sessionID }, parts: [] };
|
|
12047
|
+
byMessage.set(messageID, entry);
|
|
12048
|
+
}
|
|
12049
|
+
return entry;
|
|
12050
|
+
}
|
|
12051
|
+
for (const line of lines) {
|
|
12052
|
+
const type = line.type;
|
|
12053
|
+
const props = asObject2(line.properties) ?? line;
|
|
12054
|
+
if (type === "message.updated") {
|
|
12055
|
+
const info = asObject2(props.info);
|
|
12056
|
+
const id = typeof info?.id === "string" ? info.id : void 0;
|
|
12057
|
+
if (!info || !id) continue;
|
|
12058
|
+
const entry = getEntry(id, info.sessionID);
|
|
12059
|
+
entry.info = info;
|
|
12060
|
+
continue;
|
|
12061
|
+
}
|
|
12062
|
+
if (type === "message.part.updated") {
|
|
12063
|
+
const part = asObject2(props.part);
|
|
12064
|
+
const messageID = typeof part?.messageID === "string" && part.messageID || void 0;
|
|
12065
|
+
if (!part || !messageID) continue;
|
|
12066
|
+
const entry = getEntry(messageID, part.sessionID);
|
|
12067
|
+
const partID = typeof part.id === "string" ? part.id : void 0;
|
|
12068
|
+
const existingIndex = partID ? entry.parts.findIndex((p7) => p7.id === partID) : -1;
|
|
12069
|
+
if (existingIndex >= 0) entry.parts[existingIndex] = part;
|
|
12070
|
+
else entry.parts.push(part);
|
|
12071
|
+
}
|
|
12072
|
+
}
|
|
12073
|
+
return [...byMessage.values()];
|
|
12074
|
+
}
|
|
12075
|
+
function getSessionId(sessionId, exportInfo, entries) {
|
|
12076
|
+
if (sessionId) return sessionId;
|
|
12077
|
+
if (typeof exportInfo?.id === "string") return exportInfo.id;
|
|
12078
|
+
for (const entry of entries) {
|
|
12079
|
+
if (typeof entry.info.sessionID === "string") return entry.info.sessionID;
|
|
12080
|
+
}
|
|
12081
|
+
return "unknown";
|
|
12082
|
+
}
|
|
12083
|
+
function getAgentVersion(exportInfo) {
|
|
12084
|
+
return typeof exportInfo?.version === "string" && exportInfo.version || "unknown";
|
|
12085
|
+
}
|
|
12086
|
+
function sortEntries(entries) {
|
|
12087
|
+
return [...entries].sort((a, b) => {
|
|
12088
|
+
const at = asNumber2(asObject2(a.info.time)?.created) ?? 0;
|
|
12089
|
+
const bt = asNumber2(asObject2(b.info.time)?.created) ?? 0;
|
|
12090
|
+
return at - bt;
|
|
12091
|
+
});
|
|
12092
|
+
}
|
|
12093
|
+
function splitAssistantParts(parts) {
|
|
12094
|
+
let sawBoundary = false;
|
|
12095
|
+
const groups = [];
|
|
12096
|
+
let current = [];
|
|
12097
|
+
for (const part of parts) {
|
|
12098
|
+
const type = part.type;
|
|
12099
|
+
if (type === "step-start") {
|
|
12100
|
+
sawBoundary = true;
|
|
12101
|
+
if (current.length > 0) groups.push(current);
|
|
12102
|
+
current = [part];
|
|
12103
|
+
continue;
|
|
12104
|
+
}
|
|
12105
|
+
current.push(part);
|
|
12106
|
+
if (type === "step-finish") {
|
|
12107
|
+
sawBoundary = true;
|
|
12108
|
+
groups.push(current);
|
|
12109
|
+
current = [];
|
|
12110
|
+
}
|
|
12111
|
+
}
|
|
12112
|
+
if (current.length > 0) groups.push(current);
|
|
12113
|
+
return sawBoundary ? groups : [parts];
|
|
12114
|
+
}
|
|
12115
|
+
function textFromFilePart(part) {
|
|
12116
|
+
const filename = typeof part.filename === "string" ? part.filename : void 0;
|
|
12117
|
+
const url = typeof part.url === "string" ? part.url : void 0;
|
|
12118
|
+
const mime = typeof part.mime === "string" ? part.mime : void 0;
|
|
12119
|
+
const label = filename ?? url;
|
|
12120
|
+
if (!label) return void 0;
|
|
12121
|
+
return mime ? `[file:${mime}] ${label}` : `[file] ${label}`;
|
|
12122
|
+
}
|
|
12123
|
+
function buildUserStep(entry, stepId, defaultModelName) {
|
|
12124
|
+
const textParts = [];
|
|
12125
|
+
const extra = {};
|
|
12126
|
+
for (const part of entry.parts) {
|
|
12127
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
12128
|
+
if (part.text.trim()) textParts.push(part.text.trim());
|
|
12129
|
+
continue;
|
|
12130
|
+
}
|
|
12131
|
+
if (part.type === "file") {
|
|
12132
|
+
const fileText = textFromFilePart(part);
|
|
12133
|
+
if (fileText) textParts.push(fileText);
|
|
12134
|
+
}
|
|
12135
|
+
}
|
|
12136
|
+
const message = textParts.join("\n\n").trim();
|
|
12137
|
+
if (!message) return null;
|
|
12138
|
+
if (entry.info.agent) extra.agent = entry.info.agent;
|
|
12139
|
+
if (entry.info.tools) extra.tools = entry.info.tools;
|
|
12140
|
+
return {
|
|
12141
|
+
step_id: stepId,
|
|
12142
|
+
timestamp: timeFromObject(entry.info.time),
|
|
12143
|
+
source: "user",
|
|
12144
|
+
message,
|
|
12145
|
+
model_name: defaultModelName,
|
|
12146
|
+
extra: compactExtra2(extra)
|
|
12147
|
+
};
|
|
12148
|
+
}
|
|
12149
|
+
function buildAgentStep(parts, info, stepId, defaultModelName, fallbackMetrics) {
|
|
12150
|
+
const textParts = [];
|
|
12151
|
+
const reasoningParts = [];
|
|
12152
|
+
const toolCalls = [];
|
|
12153
|
+
const observationResults = [];
|
|
12154
|
+
const extra = {};
|
|
12155
|
+
let metrics;
|
|
12156
|
+
let timestamp = timeFromObject(info.time);
|
|
12157
|
+
for (const part of parts) {
|
|
12158
|
+
if (!timestamp) timestamp = timeFromObject(part.time);
|
|
12159
|
+
switch (part.type) {
|
|
12160
|
+
case "text":
|
|
12161
|
+
if (typeof part.text === "string" && part.text.trim()) {
|
|
12162
|
+
textParts.push(part.text.trim());
|
|
12163
|
+
}
|
|
12164
|
+
break;
|
|
12165
|
+
case "reasoning":
|
|
12166
|
+
if (typeof part.text === "string" && part.text.trim()) {
|
|
12167
|
+
reasoningParts.push(part.text.trim());
|
|
12168
|
+
}
|
|
12169
|
+
break;
|
|
12170
|
+
case "file": {
|
|
12171
|
+
const fileText = textFromFilePart(part);
|
|
12172
|
+
if (fileText) textParts.push(fileText);
|
|
12173
|
+
break;
|
|
12174
|
+
}
|
|
12175
|
+
case "tool": {
|
|
12176
|
+
const state = asObject2(part.state) ?? {};
|
|
12177
|
+
const callID = typeof part.callID === "string" && part.callID || typeof part.id === "string" && part.id || "";
|
|
12178
|
+
const toolName = typeof part.tool === "string" && part.tool || "tool";
|
|
12179
|
+
const input = argsFromUnknown(state.input);
|
|
12180
|
+
toolCalls.push({
|
|
12181
|
+
tool_call_id: callID,
|
|
12182
|
+
function_name: toolName,
|
|
12183
|
+
arguments: input
|
|
12184
|
+
});
|
|
12185
|
+
const output = state.output ?? state.error;
|
|
12186
|
+
if (output !== void 0 && output !== null) {
|
|
12187
|
+
observationResults.push({
|
|
12188
|
+
source_call_id: callID || void 0,
|
|
12189
|
+
content: stringify2(output)
|
|
12190
|
+
});
|
|
12191
|
+
}
|
|
12192
|
+
if (state.status) extra.status = state.status;
|
|
12193
|
+
if (state.metadata) extra.tool_metadata = state.metadata;
|
|
12194
|
+
if (part.metadata) extra.part_metadata = part.metadata;
|
|
12195
|
+
break;
|
|
12196
|
+
}
|
|
12197
|
+
case "step-finish":
|
|
12198
|
+
metrics = metricsFromTokens(part.tokens, part.cost) ?? metrics;
|
|
12199
|
+
if (part.reason) extra.finish_reason = part.reason;
|
|
12200
|
+
if (part.snapshot) extra.snapshot = part.snapshot;
|
|
12201
|
+
break;
|
|
12202
|
+
case "patch":
|
|
12203
|
+
extra.patches = [...asArray(extra.patches), part];
|
|
12204
|
+
break;
|
|
12205
|
+
case "agent":
|
|
12206
|
+
if (part.name) extra.agent = part.name;
|
|
12207
|
+
break;
|
|
12208
|
+
case "retry":
|
|
12209
|
+
extra.retry = part;
|
|
12210
|
+
break;
|
|
12211
|
+
}
|
|
12212
|
+
}
|
|
12213
|
+
metrics = metrics ?? fallbackMetrics;
|
|
12214
|
+
if (textParts.length === 0 && reasoningParts.length === 0 && toolCalls.length === 0 && !metrics) {
|
|
12215
|
+
return null;
|
|
12216
|
+
}
|
|
12217
|
+
const observation = observationResults.length > 0 ? { results: observationResults } : void 0;
|
|
12218
|
+
const step = {
|
|
12219
|
+
step_id: stepId,
|
|
12220
|
+
timestamp,
|
|
12221
|
+
source: "agent",
|
|
12222
|
+
message: textParts.length > 0 ? textParts.join("\n\n") : "(tool use)",
|
|
12223
|
+
model_name: modelNameFromInfo(info) ?? defaultModelName
|
|
12224
|
+
};
|
|
12225
|
+
if (reasoningParts.length > 0)
|
|
12226
|
+
step.reasoning_content = reasoningParts.join("\n\n");
|
|
12227
|
+
if (toolCalls.length > 0) step.tool_calls = toolCalls;
|
|
12228
|
+
if (observation) step.observation = observation;
|
|
12229
|
+
if (metrics) step.metrics = metrics;
|
|
12230
|
+
const compactedExtra = compactExtra2(extra);
|
|
12231
|
+
if (compactedExtra) step.extra = compactedExtra;
|
|
12232
|
+
return step;
|
|
12233
|
+
}
|
|
12234
|
+
function buildUnavailableAgentStep(info, stepId, defaultModelName) {
|
|
12235
|
+
const metrics = metricsFromInfo(info);
|
|
12236
|
+
if (!metrics && !modelNameFromInfo(info) && !info.error && !info.finish) {
|
|
12237
|
+
return null;
|
|
12238
|
+
}
|
|
12239
|
+
return {
|
|
12240
|
+
step_id: stepId,
|
|
12241
|
+
timestamp: timeFromObject(info.time),
|
|
12242
|
+
source: "agent",
|
|
12243
|
+
message: "(message unavailable)",
|
|
12244
|
+
model_name: modelNameFromInfo(info) ?? defaultModelName,
|
|
12245
|
+
metrics,
|
|
12246
|
+
extra: compactExtra2({
|
|
12247
|
+
content_unavailable: true,
|
|
12248
|
+
finish_reason: info.finish,
|
|
12249
|
+
error: info.error
|
|
12250
|
+
})
|
|
12251
|
+
};
|
|
12252
|
+
}
|
|
12253
|
+
function convertMessageEntriesToTrajectory(entries, sessionId, exportInfo) {
|
|
12254
|
+
if (entries.length === 0) return null;
|
|
12255
|
+
const orderedEntries = sortEntries(entries);
|
|
12256
|
+
const defaultModelName = orderedEntries.map((entry) => modelNameFromInfo(entry.info)).find((model) => typeof model === "string");
|
|
12257
|
+
const steps = [];
|
|
12258
|
+
for (const entry of orderedEntries) {
|
|
12259
|
+
const role = entry.info.role;
|
|
12260
|
+
if (role === "user") {
|
|
12261
|
+
const step = buildUserStep(entry, steps.length + 1, defaultModelName);
|
|
12262
|
+
if (step) steps.push(step);
|
|
12263
|
+
continue;
|
|
12264
|
+
}
|
|
12265
|
+
if (role !== "assistant") continue;
|
|
12266
|
+
if (entry.parts.length === 0) {
|
|
12267
|
+
const step = buildUnavailableAgentStep(
|
|
12268
|
+
entry.info,
|
|
12269
|
+
steps.length + 1,
|
|
12270
|
+
defaultModelName
|
|
11484
12271
|
);
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
}
|
|
11488
|
-
|
|
12272
|
+
if (step) steps.push(step);
|
|
12273
|
+
continue;
|
|
12274
|
+
}
|
|
12275
|
+
const groups = splitAssistantParts(entry.parts);
|
|
12276
|
+
const hasPartMetrics = groups.some(
|
|
12277
|
+
(group) => group.some((part) => part.type === "step-finish")
|
|
12278
|
+
);
|
|
12279
|
+
const fallbackMetrics = hasPartMetrics ? void 0 : metricsFromInfo(entry.info);
|
|
12280
|
+
for (let i = 0; i < groups.length; i++) {
|
|
12281
|
+
const step = buildAgentStep(
|
|
12282
|
+
groups[i],
|
|
12283
|
+
entry.info,
|
|
12284
|
+
steps.length + 1,
|
|
12285
|
+
defaultModelName,
|
|
12286
|
+
i === 0 ? fallbackMetrics : void 0
|
|
12287
|
+
);
|
|
12288
|
+
if (step) steps.push(step);
|
|
11489
12289
|
}
|
|
11490
12290
|
}
|
|
11491
|
-
return
|
|
12291
|
+
if (steps.length === 0) return null;
|
|
12292
|
+
return {
|
|
12293
|
+
schema_version: ATIF_VERSION,
|
|
12294
|
+
session_id: getSessionId(sessionId, exportInfo, orderedEntries),
|
|
12295
|
+
agent: {
|
|
12296
|
+
name: "opencode",
|
|
12297
|
+
version: getAgentVersion(exportInfo),
|
|
12298
|
+
model_name: defaultModelName
|
|
12299
|
+
},
|
|
12300
|
+
steps,
|
|
12301
|
+
final_metrics: finalMetricsFromSteps2(steps)
|
|
12302
|
+
};
|
|
12303
|
+
}
|
|
12304
|
+
function convertRunEventsToTrajectory(events, sessionId) {
|
|
12305
|
+
const session = sessionId ?? (events.map((event) => event.sessionID).find((sid) => typeof sid === "string") || "unknown");
|
|
12306
|
+
const turns = [];
|
|
12307
|
+
let current = null;
|
|
12308
|
+
for (const event of events) {
|
|
12309
|
+
const type = event.type;
|
|
12310
|
+
if (type === "step_start") {
|
|
12311
|
+
current = { parts: [], timestamp: event.timestamp };
|
|
12312
|
+
continue;
|
|
12313
|
+
}
|
|
12314
|
+
if (type === "step_finish") {
|
|
12315
|
+
if (current) {
|
|
12316
|
+
current.finish = asObject2(event.part) ?? {};
|
|
12317
|
+
turns.push(current);
|
|
12318
|
+
current = null;
|
|
12319
|
+
}
|
|
12320
|
+
continue;
|
|
12321
|
+
}
|
|
12322
|
+
if (current && (type === "text" || type === "reasoning" || type === "tool_use")) {
|
|
12323
|
+
const part = asObject2(event.part);
|
|
12324
|
+
if (part) current.parts.push(part);
|
|
12325
|
+
}
|
|
12326
|
+
}
|
|
12327
|
+
const steps = [];
|
|
12328
|
+
for (const turn of turns) {
|
|
12329
|
+
const parts = [...turn.parts];
|
|
12330
|
+
if (turn.finish) {
|
|
12331
|
+
parts.push({ ...turn.finish, type: "step-finish" });
|
|
12332
|
+
}
|
|
12333
|
+
const step = buildAgentStep(
|
|
12334
|
+
parts,
|
|
12335
|
+
{ time: { created: turn.timestamp } },
|
|
12336
|
+
steps.length + 1,
|
|
12337
|
+
void 0
|
|
12338
|
+
);
|
|
12339
|
+
if (step) steps.push(step);
|
|
12340
|
+
}
|
|
12341
|
+
if (steps.length === 0) return null;
|
|
12342
|
+
return {
|
|
12343
|
+
schema_version: ATIF_VERSION,
|
|
12344
|
+
session_id: session,
|
|
12345
|
+
agent: {
|
|
12346
|
+
name: "opencode",
|
|
12347
|
+
version: "unknown"
|
|
12348
|
+
},
|
|
12349
|
+
steps,
|
|
12350
|
+
final_metrics: finalMetricsFromSteps2(steps)
|
|
12351
|
+
};
|
|
12352
|
+
}
|
|
12353
|
+
function isRunEvent(lines) {
|
|
12354
|
+
return lines.some(
|
|
12355
|
+
(line) => ["step_start", "step_finish", "text", "reasoning", "tool_use"].includes(
|
|
12356
|
+
String(line.type ?? "")
|
|
12357
|
+
)
|
|
12358
|
+
);
|
|
12359
|
+
}
|
|
12360
|
+
function convertOpenCodeToTrajectory(content, sessionId) {
|
|
12361
|
+
const trimmed = content.trim();
|
|
12362
|
+
if (!trimmed) return null;
|
|
12363
|
+
try {
|
|
12364
|
+
const parsed = JSON.parse(trimmed);
|
|
12365
|
+
const parsedObj = asObject2(parsed);
|
|
12366
|
+
const messages = asArray(parsedObj?.messages);
|
|
12367
|
+
if (parsedObj && messages.length > 0) {
|
|
12368
|
+
const entries2 = messages.flatMap((message) => {
|
|
12369
|
+
const entry = entryFromLine(asObject2(message) ?? {});
|
|
12370
|
+
return entry ? [entry] : [];
|
|
12371
|
+
});
|
|
12372
|
+
return convertMessageEntriesToTrajectory(
|
|
12373
|
+
entries2,
|
|
12374
|
+
sessionId,
|
|
12375
|
+
asObject2(parsedObj.info)
|
|
12376
|
+
);
|
|
12377
|
+
}
|
|
12378
|
+
} catch {
|
|
12379
|
+
}
|
|
12380
|
+
const lines = parseJsonLines2(content);
|
|
12381
|
+
if (lines.length === 0) return null;
|
|
12382
|
+
if (isRunEvent(lines)) return convertRunEventsToTrajectory(lines, sessionId);
|
|
12383
|
+
const wrappedEntries = entriesFromEventWrappers(lines);
|
|
12384
|
+
const entries = wrappedEntries.length > 0 ? wrappedEntries : lines.flatMap((line) => {
|
|
12385
|
+
const entry = entryFromLine(line);
|
|
12386
|
+
return entry ? [entry] : [];
|
|
12387
|
+
});
|
|
12388
|
+
return convertMessageEntriesToTrajectory(entries, sessionId);
|
|
11492
12389
|
}
|
|
11493
12390
|
|
|
11494
12391
|
// src/normalizer/index.ts
|
|
@@ -11500,6 +12397,10 @@ function normalizeContent(sourceName, content, sessionId) {
|
|
|
11500
12397
|
return convertCodexToTrajectory(content, sessionId);
|
|
11501
12398
|
case "cursor":
|
|
11502
12399
|
return convertCursorToTrajectory(content, sessionId);
|
|
12400
|
+
case "opencode":
|
|
12401
|
+
return convertOpenCodeToTrajectory(content, sessionId);
|
|
12402
|
+
case "copilot-chat":
|
|
12403
|
+
return convertCopilotChatToTrajectory(content, sessionId);
|
|
11503
12404
|
default:
|
|
11504
12405
|
return null;
|
|
11505
12406
|
}
|
|
@@ -11511,10 +12412,13 @@ var NormalizeMiddleware = class {
|
|
|
11511
12412
|
for (const file of group.files) {
|
|
11512
12413
|
newFiles.push(file);
|
|
11513
12414
|
if (!file.absolutePath.endsWith(".jsonl")) continue;
|
|
11514
|
-
if (!["claude", "codex", "cursor"].includes(
|
|
12415
|
+
if (!["claude", "codex", "cursor", "opencode", "copilot-chat"].includes(
|
|
12416
|
+
file.sourceName
|
|
12417
|
+
))
|
|
12418
|
+
continue;
|
|
11515
12419
|
const content = file.content ? file.content.toString("utf-8") : null;
|
|
11516
12420
|
if (!content) continue;
|
|
11517
|
-
const sessionId = file.metadata?.sessionId ??
|
|
12421
|
+
const sessionId = file.metadata?.sessionId ?? path9.basename(file.absolutePath, ".jsonl");
|
|
11518
12422
|
try {
|
|
11519
12423
|
const trajectory = normalizeContent(
|
|
11520
12424
|
file.sourceName,
|
|
@@ -11548,14 +12452,14 @@ import archiver from "archiver";
|
|
|
11548
12452
|
|
|
11549
12453
|
// src/outputs/archive.ts
|
|
11550
12454
|
import os4 from "os";
|
|
11551
|
-
import
|
|
12455
|
+
import path10 from "path";
|
|
11552
12456
|
function getSourceBaseDir(sourceName) {
|
|
11553
12457
|
const home = os4.homedir();
|
|
11554
12458
|
switch (sourceName) {
|
|
11555
12459
|
case "claude":
|
|
11556
|
-
return
|
|
12460
|
+
return path10.join(home, ".claude", "projects");
|
|
11557
12461
|
case "codex":
|
|
11558
|
-
return
|
|
12462
|
+
return path10.join(home, ".codex", "sessions");
|
|
11559
12463
|
default:
|
|
11560
12464
|
return home;
|
|
11561
12465
|
}
|
|
@@ -11564,8 +12468,8 @@ function addGroupToArchive(archive, group, selectedSources) {
|
|
|
11564
12468
|
for (const file of group.files) {
|
|
11565
12469
|
if (!selectedSources.has(file.sourceName)) continue;
|
|
11566
12470
|
const baseDir = getSourceBaseDir(file.sourceName);
|
|
11567
|
-
const relativePath = file.absolutePath.startsWith(baseDir) ?
|
|
11568
|
-
const archivePath =
|
|
12471
|
+
const relativePath = file.absolutePath.startsWith(baseDir) ? path10.relative(baseDir, file.absolutePath) : path10.basename(file.absolutePath);
|
|
12472
|
+
const archivePath = path10.join(file.sourceName, relativePath);
|
|
11569
12473
|
if (file.content) {
|
|
11570
12474
|
archive.append(file.content, { name: archivePath });
|
|
11571
12475
|
} else {
|
|
@@ -11639,21 +12543,21 @@ var PlatformUploadOutput = class {
|
|
|
11639
12543
|
};
|
|
11640
12544
|
|
|
11641
12545
|
// src/pipeline.ts
|
|
11642
|
-
import
|
|
11643
|
-
import
|
|
12546
|
+
import fs8 from "fs";
|
|
12547
|
+
import path11 from "path";
|
|
11644
12548
|
function canonicalizePath(p7) {
|
|
11645
|
-
let resolved =
|
|
11646
|
-
if (resolved.endsWith(
|
|
12549
|
+
let resolved = path11.resolve(p7);
|
|
12550
|
+
if (resolved.endsWith(path11.sep) && resolved !== path11.sep) {
|
|
11647
12551
|
resolved = resolved.slice(0, -1);
|
|
11648
12552
|
}
|
|
11649
12553
|
return resolved;
|
|
11650
12554
|
}
|
|
11651
12555
|
function computeLabel(repoPath, allPaths) {
|
|
11652
|
-
const segments = repoPath.split(
|
|
12556
|
+
const segments = repoPath.split(path11.sep).filter(Boolean);
|
|
11653
12557
|
for (let depth = 1; depth <= segments.length; depth++) {
|
|
11654
12558
|
const label = segments.slice(-depth).join("/");
|
|
11655
12559
|
const matches = allPaths.filter((p7) => {
|
|
11656
|
-
const s = p7.split(
|
|
12560
|
+
const s = p7.split(path11.sep).filter(Boolean);
|
|
11657
12561
|
return s.slice(-depth).join("/") === label;
|
|
11658
12562
|
});
|
|
11659
12563
|
if (matches.length === 1) return label;
|
|
@@ -11675,7 +12579,7 @@ async function mergeByRepo(files) {
|
|
|
11675
12579
|
const groups = [];
|
|
11676
12580
|
for (const [repoPath, groupFiles] of grouped) {
|
|
11677
12581
|
const stats = await Promise.all(
|
|
11678
|
-
groupFiles.map((f) =>
|
|
12582
|
+
groupFiles.map((f) => fs8.promises.stat(f.absolutePath).catch(() => null))
|
|
11679
12583
|
);
|
|
11680
12584
|
let lastModified = /* @__PURE__ */ new Date(0);
|
|
11681
12585
|
for (const stat of stats) {
|
|
@@ -11698,7 +12602,7 @@ async function preloadFiles(group) {
|
|
|
11698
12602
|
group.files.map(async (file) => {
|
|
11699
12603
|
if (file.content) return file;
|
|
11700
12604
|
try {
|
|
11701
|
-
const buf = await
|
|
12605
|
+
const buf = await fs8.promises.readFile(file.absolutePath);
|
|
11702
12606
|
const checkLen = Math.min(buf.length, 8192);
|
|
11703
12607
|
for (let i = 0; i < checkLen; i++) {
|
|
11704
12608
|
if (buf[i] === 0) {
|
|
@@ -11743,7 +12647,7 @@ function lineHasAssistant(line) {
|
|
|
11743
12647
|
return line.includes('"type":"assistant"') || line.includes('"role":"assistant"') || line.includes('"type":"assistant.');
|
|
11744
12648
|
}
|
|
11745
12649
|
async function hasAssistantMessage(transcriptPath) {
|
|
11746
|
-
const stream =
|
|
12650
|
+
const stream = fs9.createReadStream(transcriptPath, { encoding: "utf-8" });
|
|
11747
12651
|
let buffer = "";
|
|
11748
12652
|
try {
|
|
11749
12653
|
for await (const chunk of stream) {
|
|
@@ -11771,12 +12675,29 @@ function resolveSourceTool(payload) {
|
|
|
11771
12675
|
if (payload.hook_event_name === "Stop") return "codex";
|
|
11772
12676
|
return "claude";
|
|
11773
12677
|
}
|
|
12678
|
+
async function selfHealHook(repoRoot, tool) {
|
|
12679
|
+
try {
|
|
12680
|
+
const result = await healHookForTool(repoRoot, tool);
|
|
12681
|
+
if (result.skipped) {
|
|
12682
|
+
appendLog("warn", `self-heal: skipped ${tool} hook (${result.skipped})`);
|
|
12683
|
+
return;
|
|
12684
|
+
}
|
|
12685
|
+
if (result.changed) {
|
|
12686
|
+
appendLog("info", `self-heal: updated ${tool} hook`);
|
|
12687
|
+
}
|
|
12688
|
+
} catch (err) {
|
|
12689
|
+
appendLog(
|
|
12690
|
+
"warn",
|
|
12691
|
+
`self-heal: failed to repair ${tool} hook: ${err instanceof Error ? err.message : String(err)}`
|
|
12692
|
+
);
|
|
12693
|
+
}
|
|
12694
|
+
}
|
|
11774
12695
|
function resolveCursorTranscriptPath(payload) {
|
|
11775
12696
|
const id = payload.conversation_id ?? payload.session_id;
|
|
11776
12697
|
const workspace = payload.workspace_roots?.[0];
|
|
11777
12698
|
if (!id || !workspace) return void 0;
|
|
11778
12699
|
const encoded = workspace.replace(/^\//, "").replace(/\//g, "-");
|
|
11779
|
-
return
|
|
12700
|
+
return path12.join(
|
|
11780
12701
|
os5.homedir(),
|
|
11781
12702
|
".cursor",
|
|
11782
12703
|
"projects",
|
|
@@ -11802,9 +12723,19 @@ async function runUploadInner(payload) {
|
|
|
11802
12723
|
);
|
|
11803
12724
|
return;
|
|
11804
12725
|
}
|
|
11805
|
-
const
|
|
12726
|
+
const match = await findProjectForCwd(cwd);
|
|
12727
|
+
if (!match) {
|
|
12728
|
+
appendLog(
|
|
12729
|
+
"warn",
|
|
12730
|
+
`Skipping session ${sessionId}: no hillclimb config for cwd ${cwd}. Run \`npx hillclimb\` in the repo.`
|
|
12731
|
+
);
|
|
12732
|
+
return;
|
|
12733
|
+
}
|
|
12734
|
+
const { repoRoot, config } = match;
|
|
12735
|
+
await selfHealHook(repoRoot, sourceTool);
|
|
12736
|
+
const transcriptResolved = path12.resolve(transcriptPath);
|
|
11806
12737
|
try {
|
|
11807
|
-
const stat = await
|
|
12738
|
+
const stat = await fs9.promises.stat(transcriptResolved);
|
|
11808
12739
|
if (!stat.isFile()) {
|
|
11809
12740
|
appendLog(
|
|
11810
12741
|
"warn",
|
|
@@ -11819,14 +12750,6 @@ async function runUploadInner(payload) {
|
|
|
11819
12750
|
);
|
|
11820
12751
|
return;
|
|
11821
12752
|
}
|
|
11822
|
-
const match = await findProjectForCwd(cwd);
|
|
11823
|
-
if (!match) {
|
|
11824
|
-
appendLog(
|
|
11825
|
-
"warn",
|
|
11826
|
-
`Skipping session ${sessionId}: no hillclimb config for cwd ${cwd}. Run \`npx hillclimb\` in the repo.`
|
|
11827
|
-
);
|
|
11828
|
-
return;
|
|
11829
|
-
}
|
|
11830
12753
|
if (!await hasAssistantMessage(transcriptResolved)) {
|
|
11831
12754
|
appendLog(
|
|
11832
12755
|
"info",
|
|
@@ -11834,7 +12757,6 @@ async function runUploadInner(payload) {
|
|
|
11834
12757
|
);
|
|
11835
12758
|
return;
|
|
11836
12759
|
}
|
|
11837
|
-
const { repoRoot, config } = match;
|
|
11838
12760
|
await uploadSession({
|
|
11839
12761
|
sessionId,
|
|
11840
12762
|
transcriptPath: transcriptResolved,
|
|
@@ -11853,13 +12775,13 @@ async function uploadSession(args) {
|
|
|
11853
12775
|
};
|
|
11854
12776
|
const group = {
|
|
11855
12777
|
repoPath: repoRoot,
|
|
11856
|
-
label:
|
|
12778
|
+
label: path12.basename(repoRoot),
|
|
11857
12779
|
files: [sourceFile],
|
|
11858
12780
|
sourceNames: [sourceTool],
|
|
11859
12781
|
lastModified: now
|
|
11860
12782
|
};
|
|
11861
12783
|
const envFileNames = await discoverEnvFiles(repoRoot);
|
|
11862
|
-
const envFilePaths = envFileNames.map((n) =>
|
|
12784
|
+
const envFilePaths = envFileNames.map((n) => path12.join(repoRoot, n));
|
|
11863
12785
|
const secretResult = await collectSecrets(repoRoot, envFilePaths, []);
|
|
11864
12786
|
const mwChain = [];
|
|
11865
12787
|
if (secretResult.values.size > 0) {
|
|
@@ -12039,9 +12961,9 @@ import { execFileSync as execFileSync2 } from "child_process";
|
|
|
12039
12961
|
|
|
12040
12962
|
// src/git-traces/git-ops.ts
|
|
12041
12963
|
import { execFileSync } from "child_process";
|
|
12042
|
-
import
|
|
12964
|
+
import fs10 from "fs";
|
|
12043
12965
|
import os6 from "os";
|
|
12044
|
-
import
|
|
12966
|
+
import path13 from "path";
|
|
12045
12967
|
import { gzipSync } from "zlib";
|
|
12046
12968
|
var GIT_COMMAND_TIMEOUT_MS = 12e4;
|
|
12047
12969
|
var EXEC_OPTS = {
|
|
@@ -12165,7 +13087,7 @@ function buildUntrackedTree(repoRoot) {
|
|
|
12165
13087
|
for (const relPath of list.split("\0")) {
|
|
12166
13088
|
if (!relPath) continue;
|
|
12167
13089
|
try {
|
|
12168
|
-
const stat =
|
|
13090
|
+
const stat = fs10.lstatSync(path13.join(repoRoot, relPath));
|
|
12169
13091
|
if (stat.size > MAX_UNTRACKED_FILE_BYTES) {
|
|
12170
13092
|
appendLog(
|
|
12171
13093
|
"warn",
|
|
@@ -12178,7 +13100,7 @@ function buildUntrackedTree(repoRoot) {
|
|
|
12178
13100
|
}
|
|
12179
13101
|
}
|
|
12180
13102
|
if (kept.length === 0) return null;
|
|
12181
|
-
const tmpIndex =
|
|
13103
|
+
const tmpIndex = path13.join(
|
|
12182
13104
|
os6.tmpdir(),
|
|
12183
13105
|
`hillclimb-untracked-${Date.now()}-${process.pid}`
|
|
12184
13106
|
);
|
|
@@ -12191,7 +13113,7 @@ function buildUntrackedTree(repoRoot) {
|
|
|
12191
13113
|
return gitWithEnv(repoRoot, ["write-tree"], env);
|
|
12192
13114
|
} finally {
|
|
12193
13115
|
try {
|
|
12194
|
-
|
|
13116
|
+
fs10.unlinkSync(tmpIndex);
|
|
12195
13117
|
} catch {
|
|
12196
13118
|
}
|
|
12197
13119
|
}
|
|
@@ -12202,7 +13124,7 @@ function buildSnapshotTree(repoRoot, stashSha) {
|
|
|
12202
13124
|
if (!untrackedTree || untrackedTree === EMPTY_TREE_SHA) {
|
|
12203
13125
|
return trackedTree;
|
|
12204
13126
|
}
|
|
12205
|
-
const tmpIndex =
|
|
13127
|
+
const tmpIndex = path13.join(
|
|
12206
13128
|
os6.tmpdir(),
|
|
12207
13129
|
`hillclimb-index-${Date.now()}-${process.pid}`
|
|
12208
13130
|
);
|
|
@@ -12230,7 +13152,7 @@ function buildSnapshotTree(repoRoot, stashSha) {
|
|
|
12230
13152
|
return gitWithEnv(repoRoot, ["write-tree"], env);
|
|
12231
13153
|
} finally {
|
|
12232
13154
|
try {
|
|
12233
|
-
|
|
13155
|
+
fs10.unlinkSync(tmpIndex);
|
|
12234
13156
|
} catch {
|
|
12235
13157
|
}
|
|
12236
13158
|
}
|
|
@@ -12244,16 +13166,16 @@ function createBundleFromTree(repoRoot, treeSha, sessionId, label) {
|
|
|
12244
13166
|
]);
|
|
12245
13167
|
const orphanRef = `refs/hillclimb/bundle/${sessionId}`;
|
|
12246
13168
|
pinRef(repoRoot, orphanRef, orphanCommit);
|
|
12247
|
-
const tmpFile =
|
|
13169
|
+
const tmpFile = path13.join(
|
|
12248
13170
|
os6.tmpdir(),
|
|
12249
13171
|
`hillclimb-bundle-${Date.now()}.bundle`
|
|
12250
13172
|
);
|
|
12251
13173
|
try {
|
|
12252
13174
|
git(repoRoot, ["bundle", "create", tmpFile, orphanRef]);
|
|
12253
|
-
return
|
|
13175
|
+
return fs10.readFileSync(tmpFile);
|
|
12254
13176
|
} finally {
|
|
12255
13177
|
try {
|
|
12256
|
-
|
|
13178
|
+
fs10.unlinkSync(tmpFile);
|
|
12257
13179
|
} catch {
|
|
12258
13180
|
}
|
|
12259
13181
|
deleteRef(repoRoot, orphanRef);
|
|
@@ -12406,9 +13328,9 @@ function parseCommitFiles(repoRoot, sha) {
|
|
|
12406
13328
|
oldPath
|
|
12407
13329
|
});
|
|
12408
13330
|
} else {
|
|
12409
|
-
const
|
|
12410
|
-
indexByPath.set(
|
|
12411
|
-
files.push({ path:
|
|
13331
|
+
const path21 = parts[parts.length - 1];
|
|
13332
|
+
indexByPath.set(path21, files.length);
|
|
13333
|
+
files.push({ path: path21, status, additions: 0, deletions: 0 });
|
|
12412
13334
|
}
|
|
12413
13335
|
}
|
|
12414
13336
|
for (const line of numstat.split("\n")) {
|
|
@@ -12462,21 +13384,21 @@ function cleanupSessionRefs(repoRoot, sessionId) {
|
|
|
12462
13384
|
|
|
12463
13385
|
// src/git-traces/session-state.ts
|
|
12464
13386
|
import crypto from "crypto";
|
|
12465
|
-
import
|
|
13387
|
+
import fs11 from "fs";
|
|
12466
13388
|
import os7 from "os";
|
|
12467
|
-
import
|
|
13389
|
+
import path14 from "path";
|
|
12468
13390
|
var CURRENT_SCHEMA_VERSION = 3;
|
|
12469
|
-
var STATE_DIR =
|
|
13391
|
+
var STATE_DIR = path14.join(os7.homedir(), ".hillclimb", "git-traces");
|
|
12470
13392
|
function stateFileForRepo(repoRoot, tool) {
|
|
12471
|
-
const hash = crypto.createHash("sha256").update(`${
|
|
12472
|
-
return
|
|
13393
|
+
const hash = crypto.createHash("sha256").update(`${path14.resolve(repoRoot)}\0${tool}`).digest("hex").slice(0, 16);
|
|
13394
|
+
return path14.join(STATE_DIR, `${hash}.json`);
|
|
12473
13395
|
}
|
|
12474
13396
|
function lockFileForRepo(repoRoot, tool) {
|
|
12475
13397
|
return `${stateFileForRepo(repoRoot, tool)}.lock`;
|
|
12476
13398
|
}
|
|
12477
13399
|
async function readSessionState(repoRoot, tool) {
|
|
12478
13400
|
try {
|
|
12479
|
-
const raw = await
|
|
13401
|
+
const raw = await fs11.promises.readFile(
|
|
12480
13402
|
stateFileForRepo(repoRoot, tool),
|
|
12481
13403
|
"utf-8"
|
|
12482
13404
|
);
|
|
@@ -12491,27 +13413,27 @@ async function readSessionState(repoRoot, tool) {
|
|
|
12491
13413
|
}
|
|
12492
13414
|
async function writeSessionState(state, tool) {
|
|
12493
13415
|
const file = stateFileForRepo(state.repoRoot, tool);
|
|
12494
|
-
await
|
|
13416
|
+
await fs11.promises.mkdir(STATE_DIR, { recursive: true, mode: 448 });
|
|
12495
13417
|
const tmp = `${file}.tmp`;
|
|
12496
|
-
await
|
|
13418
|
+
await fs11.promises.writeFile(tmp, JSON.stringify(state, null, 2), {
|
|
12497
13419
|
mode: 384
|
|
12498
13420
|
});
|
|
12499
|
-
await
|
|
13421
|
+
await fs11.promises.rename(tmp, file);
|
|
12500
13422
|
}
|
|
12501
13423
|
async function deleteSessionState(repoRoot, tool) {
|
|
12502
13424
|
try {
|
|
12503
|
-
await
|
|
13425
|
+
await fs11.promises.unlink(stateFileForRepo(repoRoot, tool));
|
|
12504
13426
|
} catch {
|
|
12505
13427
|
}
|
|
12506
13428
|
}
|
|
12507
13429
|
async function acquireLock(repoRoot, tool, retries = 3, delayMs = 200) {
|
|
12508
13430
|
const lockPath = lockFileForRepo(repoRoot, tool);
|
|
12509
|
-
await
|
|
13431
|
+
await fs11.promises.mkdir(STATE_DIR, { recursive: true, mode: 448 });
|
|
12510
13432
|
for (let i = 0; i < retries; i++) {
|
|
12511
13433
|
try {
|
|
12512
|
-
const fd = await
|
|
13434
|
+
const fd = await fs11.promises.open(
|
|
12513
13435
|
lockPath,
|
|
12514
|
-
|
|
13436
|
+
fs11.constants.O_CREAT | fs11.constants.O_EXCL | fs11.constants.O_WRONLY
|
|
12515
13437
|
);
|
|
12516
13438
|
await fd.write(String(process.pid));
|
|
12517
13439
|
await fd.close();
|
|
@@ -12528,13 +13450,13 @@ async function acquireLock(repoRoot, tool, retries = 3, delayMs = 200) {
|
|
|
12528
13450
|
}
|
|
12529
13451
|
async function releaseLock(repoRoot, tool) {
|
|
12530
13452
|
try {
|
|
12531
|
-
await
|
|
13453
|
+
await fs11.promises.unlink(lockFileForRepo(repoRoot, tool));
|
|
12532
13454
|
} catch {
|
|
12533
13455
|
}
|
|
12534
13456
|
}
|
|
12535
13457
|
|
|
12536
13458
|
// src/git-traces/handlers.ts
|
|
12537
|
-
var CLI_VERSION = "0.
|
|
13459
|
+
var CLI_VERSION = "0.2.0";
|
|
12538
13460
|
var GIT_TRACES_SLUG = "git-traces";
|
|
12539
13461
|
var MAX_UPLOAD_BYTES = 500 * 1024 * 1024;
|
|
12540
13462
|
function formatEpochSeconds2(date) {
|
|
@@ -12574,14 +13496,16 @@ function execGit(cwd, args) {
|
|
|
12574
13496
|
maxBuffer: 50 * 1024 * 1024
|
|
12575
13497
|
}).toString("utf-8").trim();
|
|
12576
13498
|
}
|
|
13499
|
+
function canUploadFile(filename, buffer) {
|
|
13500
|
+
if (buffer.byteLength <= MAX_UPLOAD_BYTES) return true;
|
|
13501
|
+
appendLog(
|
|
13502
|
+
"warn",
|
|
13503
|
+
`git-traces: skipping ${filename} (${buffer.byteLength} bytes > ${MAX_UPLOAD_BYTES} limit)`
|
|
13504
|
+
);
|
|
13505
|
+
return false;
|
|
13506
|
+
}
|
|
12577
13507
|
async function uploadFile(client, contributionId, filename, mimeType, buffer) {
|
|
12578
|
-
if (buffer
|
|
12579
|
-
appendLog(
|
|
12580
|
-
"warn",
|
|
12581
|
-
`git-traces: skipping ${filename} (${buffer.byteLength} bytes > ${MAX_UPLOAD_BYTES} limit)`
|
|
12582
|
-
);
|
|
12583
|
-
return;
|
|
12584
|
-
}
|
|
13508
|
+
if (!canUploadFile(filename, buffer)) return false;
|
|
12585
13509
|
const presigned = await client.createUpload(contributionId, {
|
|
12586
13510
|
originalFilename: filename,
|
|
12587
13511
|
mimeType,
|
|
@@ -12596,6 +13520,11 @@ async function uploadFile(client, contributionId, filename, mimeType, buffer) {
|
|
|
12596
13520
|
"info",
|
|
12597
13521
|
`git-traces: uploaded ${filename} (${buffer.byteLength} bytes)`
|
|
12598
13522
|
);
|
|
13523
|
+
return true;
|
|
13524
|
+
}
|
|
13525
|
+
function canUploadEpochBaselineArtifacts(epoch, artifacts) {
|
|
13526
|
+
const prefix = epochPrefix(epoch);
|
|
13527
|
+
return canUploadFile(`${prefix}-baseline.bundle`, artifacts.bundleBuffer) && canUploadFile(`${prefix}-baseline.json`, artifacts.metadataBuffer);
|
|
12599
13528
|
}
|
|
12600
13529
|
function pinEpochBaseline(repoRoot, sessionId, epoch) {
|
|
12601
13530
|
const baselineSha = captureBaselineSha(repoRoot);
|
|
@@ -12650,30 +13579,34 @@ function buildEpochBaselineArtifacts(params) {
|
|
|
12650
13579
|
async function uploadEpochBaselineArtifacts(params) {
|
|
12651
13580
|
const { client, contributionId, epoch, artifacts } = params;
|
|
12652
13581
|
const prefix = epochPrefix(epoch);
|
|
12653
|
-
|
|
12654
|
-
|
|
12655
|
-
contributionId,
|
|
12656
|
-
`${prefix}-baseline.json`,
|
|
12657
|
-
"application/json",
|
|
12658
|
-
artifacts.metadataBuffer
|
|
12659
|
-
);
|
|
12660
|
-
await uploadFile(
|
|
13582
|
+
if (!canUploadEpochBaselineArtifacts(epoch, artifacts)) return false;
|
|
13583
|
+
const bundleUploaded = await uploadFile(
|
|
12661
13584
|
client,
|
|
12662
13585
|
contributionId,
|
|
12663
13586
|
`${prefix}-baseline.bundle`,
|
|
12664
13587
|
"application/x-git-bundle",
|
|
12665
13588
|
artifacts.bundleBuffer
|
|
12666
13589
|
);
|
|
13590
|
+
if (!bundleUploaded) return false;
|
|
13591
|
+
return uploadFile(
|
|
13592
|
+
client,
|
|
13593
|
+
contributionId,
|
|
13594
|
+
`${prefix}-baseline.json`,
|
|
13595
|
+
"application/json",
|
|
13596
|
+
artifacts.metadataBuffer
|
|
13597
|
+
);
|
|
12667
13598
|
}
|
|
12668
13599
|
async function uploadEpochBaseline(params) {
|
|
12669
13600
|
const artifacts = buildEpochBaselineArtifacts(params);
|
|
12670
13601
|
if (!artifacts) return null;
|
|
12671
|
-
|
|
13602
|
+
if (!canUploadEpochBaselineArtifacts(params.epoch, artifacts)) return null;
|
|
13603
|
+
const uploaded = await uploadEpochBaselineArtifacts({
|
|
12672
13604
|
client: params.client,
|
|
12673
13605
|
contributionId: params.contributionId,
|
|
12674
13606
|
epoch: params.epoch,
|
|
12675
13607
|
artifacts
|
|
12676
13608
|
});
|
|
13609
|
+
if (!uploaded) return null;
|
|
12677
13610
|
return { baselineTreeSha: artifacts.baselineTreeSha };
|
|
12678
13611
|
}
|
|
12679
13612
|
async function openEpoch(params) {
|
|
@@ -12783,6 +13716,7 @@ async function handleStop(payload, tool) {
|
|
|
12783
13716
|
startedAt: state.startedAt
|
|
12784
13717
|
});
|
|
12785
13718
|
if (!artifacts) return;
|
|
13719
|
+
if (!canUploadEpochBaselineArtifacts(1, artifacts)) return;
|
|
12786
13720
|
const toolLabel = TOOL_LABELS[tool] ?? "Claude";
|
|
12787
13721
|
const now = /* @__PURE__ */ new Date();
|
|
12788
13722
|
const epochSeconds = formatEpochSeconds2(now);
|
|
@@ -12798,12 +13732,13 @@ Repo: ${cwd}
|
|
|
12798
13732
|
Uploaded: ${now.toISOString()}`
|
|
12799
13733
|
}
|
|
12800
13734
|
);
|
|
12801
|
-
await uploadEpochBaselineArtifacts({
|
|
13735
|
+
const uploaded = await uploadEpochBaselineArtifacts({
|
|
12802
13736
|
client,
|
|
12803
13737
|
contributionId: contribution.id,
|
|
12804
13738
|
epoch: 1,
|
|
12805
13739
|
artifacts
|
|
12806
13740
|
});
|
|
13741
|
+
if (!uploaded) return;
|
|
12807
13742
|
await client.submitContribution(contribution.id);
|
|
12808
13743
|
state.contributionId = contribution.id;
|
|
12809
13744
|
state.baselineTreeSha = artifacts.baselineTreeSha;
|
|
@@ -12949,6 +13884,30 @@ async function readStdin2() {
|
|
|
12949
13884
|
}
|
|
12950
13885
|
return Buffer.concat(chunks).toString("utf-8");
|
|
12951
13886
|
}
|
|
13887
|
+
function resolveCwd2(payload) {
|
|
13888
|
+
return payload.cwd ?? payload.workspace_roots?.[0] ?? null;
|
|
13889
|
+
}
|
|
13890
|
+
async function selfHealHook2(payload, tool) {
|
|
13891
|
+
const cwd = resolveCwd2(payload);
|
|
13892
|
+
if (!cwd) return;
|
|
13893
|
+
try {
|
|
13894
|
+
const project = await findProjectForCwd(cwd);
|
|
13895
|
+
if (!project) return;
|
|
13896
|
+
const result = await healHookForTool(project.repoRoot, tool);
|
|
13897
|
+
if (result.skipped) {
|
|
13898
|
+
appendLog("warn", `self-heal: skipped ${tool} hook (${result.skipped})`);
|
|
13899
|
+
return;
|
|
13900
|
+
}
|
|
13901
|
+
if (result.changed) {
|
|
13902
|
+
appendLog("info", `self-heal: updated ${tool} hook`);
|
|
13903
|
+
}
|
|
13904
|
+
} catch (err) {
|
|
13905
|
+
appendLog(
|
|
13906
|
+
"warn",
|
|
13907
|
+
`self-heal: failed to repair ${tool} hook: ${formatError(err)}`
|
|
13908
|
+
);
|
|
13909
|
+
}
|
|
13910
|
+
}
|
|
12952
13911
|
async function runGitTraces() {
|
|
12953
13912
|
if (process.env[WORKER_ENV_FLAG2] === "1") {
|
|
12954
13913
|
await runGitTracesWorker();
|
|
@@ -13063,6 +14022,7 @@ async function runGitTracesWorker() {
|
|
|
13063
14022
|
);
|
|
13064
14023
|
return;
|
|
13065
14024
|
}
|
|
14025
|
+
await selfHealHook2(payload, tool);
|
|
13066
14026
|
try {
|
|
13067
14027
|
switch (event) {
|
|
13068
14028
|
case "SessionStart":
|
|
@@ -13100,15 +14060,15 @@ ${stack}` : ""}`
|
|
|
13100
14060
|
}
|
|
13101
14061
|
|
|
13102
14062
|
// src/outputs/zip.ts
|
|
13103
|
-
import
|
|
13104
|
-
import
|
|
14063
|
+
import fs13 from "fs";
|
|
14064
|
+
import path16 from "path";
|
|
13105
14065
|
import archiver2 from "archiver";
|
|
13106
14066
|
|
|
13107
14067
|
// src/outputs/downloads.ts
|
|
13108
14068
|
import { execSync as execSync2 } from "child_process";
|
|
13109
|
-
import
|
|
14069
|
+
import fs12 from "fs";
|
|
13110
14070
|
import os8 from "os";
|
|
13111
|
-
import
|
|
14071
|
+
import path15 from "path";
|
|
13112
14072
|
function getDownloadsFolder() {
|
|
13113
14073
|
const home = os8.homedir();
|
|
13114
14074
|
if (process.platform === "linux") {
|
|
@@ -13117,12 +14077,12 @@ function getDownloadsFolder() {
|
|
|
13117
14077
|
encoding: "utf-8",
|
|
13118
14078
|
timeout: 3e3
|
|
13119
14079
|
}).trim();
|
|
13120
|
-
if (xdgDir &&
|
|
14080
|
+
if (xdgDir && fs12.existsSync(xdgDir)) return xdgDir;
|
|
13121
14081
|
} catch {
|
|
13122
14082
|
}
|
|
13123
14083
|
}
|
|
13124
|
-
const downloads =
|
|
13125
|
-
if (
|
|
14084
|
+
const downloads = path15.join(home, "Downloads");
|
|
14085
|
+
if (fs12.existsSync(downloads)) return downloads;
|
|
13126
14086
|
return home;
|
|
13127
14087
|
}
|
|
13128
14088
|
|
|
@@ -13131,11 +14091,11 @@ function sanitizeFilename(name) {
|
|
|
13131
14091
|
return name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
13132
14092
|
}
|
|
13133
14093
|
function getUniqueFilename(dir, base, ext) {
|
|
13134
|
-
let candidate =
|
|
13135
|
-
if (!
|
|
14094
|
+
let candidate = path16.join(dir, `${base}${ext}`);
|
|
14095
|
+
if (!fs13.existsSync(candidate)) return candidate;
|
|
13136
14096
|
let i = 1;
|
|
13137
|
-
while (
|
|
13138
|
-
candidate =
|
|
14097
|
+
while (fs13.existsSync(candidate)) {
|
|
14098
|
+
candidate = path16.join(dir, `${base}-${i}${ext}`);
|
|
13139
14099
|
i++;
|
|
13140
14100
|
}
|
|
13141
14101
|
return candidate;
|
|
@@ -13145,13 +14105,13 @@ var ZipOutput = class {
|
|
|
13145
14105
|
label = "Save as .zip to Downloads";
|
|
13146
14106
|
async emit(group, options) {
|
|
13147
14107
|
const downloadsDir = getDownloadsFolder();
|
|
13148
|
-
const repoName = sanitizeFilename(
|
|
14108
|
+
const repoName = sanitizeFilename(path16.basename(group.repoPath));
|
|
13149
14109
|
const timeRange = options.timeRange;
|
|
13150
14110
|
const rangePart = timeRange?.label ?? "all";
|
|
13151
14111
|
const epochSeconds = Math.floor(Date.now() / 1e3);
|
|
13152
14112
|
const baseName = `logs-${repoName}-${rangePart}-${epochSeconds}`;
|
|
13153
14113
|
const outputPath = getUniqueFilename(downloadsDir, baseName, ".zip");
|
|
13154
|
-
const output =
|
|
14114
|
+
const output = fs13.createWriteStream(outputPath);
|
|
13155
14115
|
const archive = archiver2("zip", { zlib: { level: 6 } });
|
|
13156
14116
|
const done = new Promise((resolve, reject) => {
|
|
13157
14117
|
output.on("close", resolve);
|
|
@@ -13345,15 +14305,15 @@ async function confirmExport(group, output) {
|
|
|
13345
14305
|
}
|
|
13346
14306
|
|
|
13347
14307
|
// src/sources/claude.ts
|
|
13348
|
-
import
|
|
14308
|
+
import fs14 from "fs";
|
|
13349
14309
|
import os9 from "os";
|
|
13350
|
-
import
|
|
14310
|
+
import path17 from "path";
|
|
13351
14311
|
import readline from "readline";
|
|
13352
14312
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["tool-results"]);
|
|
13353
14313
|
async function resolveRepoPath(projectDir) {
|
|
13354
|
-
const indexPath =
|
|
14314
|
+
const indexPath = path17.join(projectDir, "sessions-index.json");
|
|
13355
14315
|
try {
|
|
13356
|
-
const raw = await
|
|
14316
|
+
const raw = await fs14.promises.readFile(indexPath, "utf-8");
|
|
13357
14317
|
const data = JSON.parse(raw);
|
|
13358
14318
|
if (data.originalPath && typeof data.originalPath === "string") {
|
|
13359
14319
|
return data.originalPath;
|
|
@@ -13361,12 +14321,12 @@ async function resolveRepoPath(projectDir) {
|
|
|
13361
14321
|
} catch {
|
|
13362
14322
|
}
|
|
13363
14323
|
const cwdCounts = /* @__PURE__ */ new Map();
|
|
13364
|
-
const entries = await
|
|
14324
|
+
const entries = await fs14.promises.readdir(projectDir, {
|
|
13365
14325
|
withFileTypes: true
|
|
13366
14326
|
});
|
|
13367
14327
|
for (const entry of entries) {
|
|
13368
14328
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
13369
|
-
const cwd = await extractCwdFromJsonl(
|
|
14329
|
+
const cwd = await extractCwdFromJsonl(path17.join(projectDir, entry.name));
|
|
13370
14330
|
if (cwd) {
|
|
13371
14331
|
cwdCounts.set(cwd, (cwdCounts.get(cwd) ?? 0) + 1);
|
|
13372
14332
|
}
|
|
@@ -13385,7 +14345,7 @@ async function resolveRepoPath(projectDir) {
|
|
|
13385
14345
|
return null;
|
|
13386
14346
|
}
|
|
13387
14347
|
async function extractCwdFromJsonl(filePath) {
|
|
13388
|
-
const stream =
|
|
14348
|
+
const stream = fs14.createReadStream(filePath, { encoding: "utf-8" });
|
|
13389
14349
|
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
13390
14350
|
try {
|
|
13391
14351
|
for await (const line of rl) {
|
|
@@ -13407,12 +14367,12 @@ async function extractCwdFromJsonl(filePath) {
|
|
|
13407
14367
|
async function collectFiles(dir, baseDir, sourceName, repoPath, results) {
|
|
13408
14368
|
let entries;
|
|
13409
14369
|
try {
|
|
13410
|
-
entries = await
|
|
14370
|
+
entries = await fs14.promises.readdir(dir, { withFileTypes: true });
|
|
13411
14371
|
} catch {
|
|
13412
14372
|
return;
|
|
13413
14373
|
}
|
|
13414
14374
|
for (const entry of entries) {
|
|
13415
|
-
const fullPath =
|
|
14375
|
+
const fullPath = path17.join(dir, entry.name);
|
|
13416
14376
|
if (entry.isDirectory()) {
|
|
13417
14377
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
13418
14378
|
await collectFiles(fullPath, baseDir, sourceName, repoPath, results);
|
|
@@ -13434,19 +14394,19 @@ function fallbackDecode(encodedName) {
|
|
|
13434
14394
|
var ClaudeSource = class {
|
|
13435
14395
|
name = "claude";
|
|
13436
14396
|
async scan() {
|
|
13437
|
-
const baseDir =
|
|
14397
|
+
const baseDir = path17.join(os9.homedir(), ".claude", "projects");
|
|
13438
14398
|
try {
|
|
13439
|
-
await
|
|
14399
|
+
await fs14.promises.access(baseDir);
|
|
13440
14400
|
} catch {
|
|
13441
14401
|
return [];
|
|
13442
14402
|
}
|
|
13443
|
-
const projectDirs = await
|
|
14403
|
+
const projectDirs = await fs14.promises.readdir(baseDir, {
|
|
13444
14404
|
withFileTypes: true
|
|
13445
14405
|
});
|
|
13446
14406
|
const dirEntries = projectDirs.filter((d) => d.isDirectory());
|
|
13447
14407
|
const resultArrays = await Promise.all(
|
|
13448
14408
|
dirEntries.map(async (dir) => {
|
|
13449
|
-
const projectPath =
|
|
14409
|
+
const projectPath = path17.join(baseDir, dir.name);
|
|
13450
14410
|
const repoPath = await resolveRepoPath(projectPath) ?? fallbackDecode(dir.name);
|
|
13451
14411
|
const files = [];
|
|
13452
14412
|
await collectFiles(
|
|
@@ -13464,12 +14424,12 @@ var ClaudeSource = class {
|
|
|
13464
14424
|
};
|
|
13465
14425
|
|
|
13466
14426
|
// src/sources/codex.ts
|
|
13467
|
-
import
|
|
14427
|
+
import fs15 from "fs";
|
|
13468
14428
|
import os10 from "os";
|
|
13469
|
-
import
|
|
14429
|
+
import path18 from "path";
|
|
13470
14430
|
import readline2 from "readline";
|
|
13471
14431
|
async function parseSessionMeta(filePath) {
|
|
13472
|
-
const stream =
|
|
14432
|
+
const stream = fs15.createReadStream(filePath, { encoding: "utf-8" });
|
|
13473
14433
|
const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
|
|
13474
14434
|
try {
|
|
13475
14435
|
for await (const line of rl) {
|
|
@@ -13494,12 +14454,12 @@ async function findJsonlFiles(dir) {
|
|
|
13494
14454
|
async function walk(d) {
|
|
13495
14455
|
let entries;
|
|
13496
14456
|
try {
|
|
13497
|
-
entries = await
|
|
14457
|
+
entries = await fs15.promises.readdir(d, { withFileTypes: true });
|
|
13498
14458
|
} catch {
|
|
13499
14459
|
return;
|
|
13500
14460
|
}
|
|
13501
14461
|
for (const entry of entries) {
|
|
13502
|
-
const full =
|
|
14462
|
+
const full = path18.join(d, entry.name);
|
|
13503
14463
|
if (entry.isDirectory()) {
|
|
13504
14464
|
await walk(full);
|
|
13505
14465
|
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
@@ -13513,11 +14473,11 @@ async function findJsonlFiles(dir) {
|
|
|
13513
14473
|
async function loadHistory(historyPath) {
|
|
13514
14474
|
const map = /* @__PURE__ */ new Map();
|
|
13515
14475
|
try {
|
|
13516
|
-
await
|
|
14476
|
+
await fs15.promises.access(historyPath);
|
|
13517
14477
|
} catch {
|
|
13518
14478
|
return map;
|
|
13519
14479
|
}
|
|
13520
|
-
const stream =
|
|
14480
|
+
const stream = fs15.createReadStream(historyPath, { encoding: "utf-8" });
|
|
13521
14481
|
const rl = readline2.createInterface({ input: stream, crlfDelay: Infinity });
|
|
13522
14482
|
try {
|
|
13523
14483
|
for await (const line of rl) {
|
|
@@ -13544,14 +14504,14 @@ async function loadHistory(historyPath) {
|
|
|
13544
14504
|
var CodexSource = class {
|
|
13545
14505
|
name = "codex";
|
|
13546
14506
|
async scan() {
|
|
13547
|
-
const codexDir =
|
|
13548
|
-
const sessionsDir =
|
|
14507
|
+
const codexDir = path18.join(os10.homedir(), ".codex");
|
|
14508
|
+
const sessionsDir = path18.join(codexDir, "sessions");
|
|
13549
14509
|
try {
|
|
13550
|
-
await
|
|
14510
|
+
await fs15.promises.access(sessionsDir);
|
|
13551
14511
|
} catch {
|
|
13552
14512
|
return [];
|
|
13553
14513
|
}
|
|
13554
|
-
const historyPath =
|
|
14514
|
+
const historyPath = path18.join(codexDir, "history.jsonl");
|
|
13555
14515
|
const [jsonlFiles, historyMap] = await Promise.all([
|
|
13556
14516
|
findJsonlFiles(sessionsDir),
|
|
13557
14517
|
loadHistory(historyPath)
|
|
@@ -13574,8 +14534,8 @@ var CodexSource = class {
|
|
|
13574
14534
|
});
|
|
13575
14535
|
const historyLines = historyMap.get(meta.sessionId);
|
|
13576
14536
|
if (historyLines) {
|
|
13577
|
-
const sessionDir =
|
|
13578
|
-
const historyAbsPath =
|
|
14537
|
+
const sessionDir = path18.relative(sessionsDir, path18.dirname(filePath));
|
|
14538
|
+
const historyAbsPath = path18.join(
|
|
13579
14539
|
sessionsDir,
|
|
13580
14540
|
sessionDir,
|
|
13581
14541
|
`history-${meta.sessionId}.jsonl`
|
|
@@ -13595,18 +14555,18 @@ var CodexSource = class {
|
|
|
13595
14555
|
};
|
|
13596
14556
|
|
|
13597
14557
|
// src/sources/copilotChat.ts
|
|
13598
|
-
import
|
|
14558
|
+
import fs16 from "fs";
|
|
13599
14559
|
import os11 from "os";
|
|
13600
|
-
import
|
|
14560
|
+
import path19 from "path";
|
|
13601
14561
|
import { fileURLToPath } from "url";
|
|
13602
14562
|
function vsCodeUserDirs() {
|
|
13603
14563
|
const home = os11.homedir();
|
|
13604
14564
|
const dirs = [
|
|
13605
|
-
|
|
13606
|
-
|
|
14565
|
+
path19.join(home, "Library", "Application Support", "Code", "User"),
|
|
14566
|
+
path19.join(home, ".config", "Code", "User")
|
|
13607
14567
|
];
|
|
13608
14568
|
if (process.env.APPDATA) {
|
|
13609
|
-
dirs.push(
|
|
14569
|
+
dirs.push(path19.join(process.env.APPDATA, "Code", "User"));
|
|
13610
14570
|
}
|
|
13611
14571
|
return dirs;
|
|
13612
14572
|
}
|
|
@@ -13621,7 +14581,7 @@ function uriToFsPath(uri) {
|
|
|
13621
14581
|
async function readWorkspaceFolder(workspaceJsonPath) {
|
|
13622
14582
|
let raw;
|
|
13623
14583
|
try {
|
|
13624
|
-
raw = await
|
|
14584
|
+
raw = await fs16.promises.readFile(workspaceJsonPath, "utf-8");
|
|
13625
14585
|
} catch {
|
|
13626
14586
|
return null;
|
|
13627
14587
|
}
|
|
@@ -13643,10 +14603,10 @@ var CopilotChatSource = class {
|
|
|
13643
14603
|
async scan() {
|
|
13644
14604
|
const results = [];
|
|
13645
14605
|
for (const userDir of vsCodeUserDirs()) {
|
|
13646
|
-
const workspaceStorage =
|
|
14606
|
+
const workspaceStorage = path19.join(userDir, "workspaceStorage");
|
|
13647
14607
|
let hashDirs;
|
|
13648
14608
|
try {
|
|
13649
|
-
hashDirs = await
|
|
14609
|
+
hashDirs = await fs16.promises.readdir(workspaceStorage, {
|
|
13650
14610
|
withFileTypes: true
|
|
13651
14611
|
});
|
|
13652
14612
|
} catch {
|
|
@@ -13654,22 +14614,22 @@ var CopilotChatSource = class {
|
|
|
13654
14614
|
}
|
|
13655
14615
|
for (const hash of hashDirs) {
|
|
13656
14616
|
if (!hash.isDirectory()) continue;
|
|
13657
|
-
const wsRoot =
|
|
13658
|
-
const transcriptsDir =
|
|
14617
|
+
const wsRoot = path19.join(workspaceStorage, hash.name);
|
|
14618
|
+
const transcriptsDir = path19.join(
|
|
13659
14619
|
wsRoot,
|
|
13660
14620
|
"GitHub.copilot-chat",
|
|
13661
14621
|
"transcripts"
|
|
13662
14622
|
);
|
|
13663
14623
|
let transcriptEntries;
|
|
13664
14624
|
try {
|
|
13665
|
-
transcriptEntries = await
|
|
14625
|
+
transcriptEntries = await fs16.promises.readdir(transcriptsDir, {
|
|
13666
14626
|
withFileTypes: true
|
|
13667
14627
|
});
|
|
13668
14628
|
} catch {
|
|
13669
14629
|
continue;
|
|
13670
14630
|
}
|
|
13671
14631
|
const repoPath = await readWorkspaceFolder(
|
|
13672
|
-
|
|
14632
|
+
path19.join(wsRoot, "workspace.json")
|
|
13673
14633
|
);
|
|
13674
14634
|
if (!repoPath) continue;
|
|
13675
14635
|
for (const entry of transcriptEntries) {
|
|
@@ -13677,7 +14637,7 @@ var CopilotChatSource = class {
|
|
|
13677
14637
|
const sessionId = entry.name.slice(0, -".jsonl".length);
|
|
13678
14638
|
results.push({
|
|
13679
14639
|
sourceName: this.name,
|
|
13680
|
-
absolutePath:
|
|
14640
|
+
absolutePath: path19.join(transcriptsDir, entry.name),
|
|
13681
14641
|
repoPath,
|
|
13682
14642
|
metadata: { sessionId }
|
|
13683
14643
|
});
|
|
@@ -13717,7 +14677,7 @@ function reportRedactionStats(noun, stats) {
|
|
|
13717
14677
|
async function filterByTimeRange(group, range) {
|
|
13718
14678
|
const results = await Promise.all(
|
|
13719
14679
|
group.files.map(
|
|
13720
|
-
(f) =>
|
|
14680
|
+
(f) => fs17.promises.stat(f.absolutePath).then((s) => ({ file: f, stat: s })).catch(() => null)
|
|
13721
14681
|
)
|
|
13722
14682
|
);
|
|
13723
14683
|
const filtered = [];
|
|
@@ -13744,10 +14704,10 @@ async function runInteractive() {
|
|
|
13744
14704
|
s.start(`Scanning ${source.name} logs...`);
|
|
13745
14705
|
const allFiles = await source.scan();
|
|
13746
14706
|
const allGroups = await mergeByRepo(allFiles);
|
|
13747
|
-
const repoRoot =
|
|
14707
|
+
const repoRoot = path20.resolve(repo.root);
|
|
13748
14708
|
const matching = allGroups.filter((g) => {
|
|
13749
|
-
const resolved =
|
|
13750
|
-
return resolved === repoRoot || resolved.startsWith(repoRoot +
|
|
14709
|
+
const resolved = path20.resolve(g.repoPath);
|
|
14710
|
+
return resolved === repoRoot || resolved.startsWith(repoRoot + path20.sep);
|
|
13751
14711
|
});
|
|
13752
14712
|
if (matching.length === 0) {
|
|
13753
14713
|
s.stop(`No ${source.name} logs found for ${repo.name}.`);
|
|
@@ -13778,7 +14738,7 @@ async function runInteractive() {
|
|
|
13778
14738
|
}
|
|
13779
14739
|
}
|
|
13780
14740
|
const envFileNames = await discoverEnvFiles(repoRoot);
|
|
13781
|
-
const envFilePaths = envFileNames.map((n) =>
|
|
14741
|
+
const envFilePaths = envFileNames.map((n) => path20.join(repoRoot, n));
|
|
13782
14742
|
const additionalFiles = await promptSecretFiles(envFileNames);
|
|
13783
14743
|
const secretResult = await collectSecrets(
|
|
13784
14744
|
repoRoot,
|