@cogit/cli 0.23.1 → 0.24.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cogit",
3
3
  "displayName": "cogit",
4
- "version": "0.23.1",
4
+ "version": "0.24.0",
5
5
  "description": "Git remembers what changed. cogit remembers why.",
6
6
  "author": {
7
7
  "name": "Eren Sertkaya"
@@ -43,8 +43,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
43
43
 
44
44
  // src/git-hooks.ts
45
45
  import { realpathSync } from "fs";
46
- import { chmod, mkdir as mkdir5, readFile as readFile5, rm as rm3, stat as stat4 } from "fs/promises";
47
- import { join as join7 } from "path";
46
+ import { chmod, mkdir as mkdir5, readFile as readFile5, rm as rm3, stat as stat6 } from "fs/promises";
47
+ import { join as join8 } from "path";
48
48
 
49
49
  // src/git.ts
50
50
  import { execFile } from "child_process";
@@ -20951,25 +20951,107 @@ async function writeProjectSetup(root) {
20951
20951
  }
20952
20952
 
20953
20953
  // src/agents/claude/transcripts.ts
20954
- import { createReadStream } from "fs";
20955
- import { access, open as open3, readdir as readdir2 } from "fs/promises";
20954
+ import { createReadStream as createReadStream2 } from "fs";
20955
+ import { access, open as open3, readdir as readdir3, stat as stat5 } from "fs/promises";
20956
20956
  import { homedir as homedir3 } from "os";
20957
- import { join as join6 } from "path";
20957
+ import { join as join7 } from "path";
20958
+ import { createInterface as createInterface2 } from "readline";
20959
+
20960
+ // src/agents/claude/fork.ts
20961
+ import { createReadStream } from "fs";
20962
+ import { readdir as readdir2, stat as stat4 } from "fs/promises";
20963
+ import { basename as basename2, dirname as dirname4, join as join6 } from "path";
20958
20964
  import { createInterface } from "readline";
20965
+ var MAX_DEPTH = 8;
20966
+ var USER_LINE = /"type":"user"/;
20967
+ var LINE_UUID = /"uuid":"([^"]+)"/;
20968
+ var firstUuids = /* @__PURE__ */ new Map();
20969
+ async function firstMessageUuid(file2, isMessage) {
20970
+ const known = firstUuids.get(file2);
20971
+ if (known !== void 0) return known;
20972
+ const input2 = createReadStream(file2, "utf8");
20973
+ const lines = createInterface({ input: input2, crlfDelay: Infinity });
20974
+ let found = null;
20975
+ try {
20976
+ for await (const raw of lines) {
20977
+ if (!USER_LINE.test(raw)) continue;
20978
+ const line = parseLine(raw);
20979
+ if (line === null || !isMessage(line)) continue;
20980
+ found = LINE_UUID.exec(raw)?.[1] ?? null;
20981
+ break;
20982
+ }
20983
+ } finally {
20984
+ lines.close();
20985
+ input2.destroy();
20986
+ }
20987
+ if (found !== null) firstUuids.set(file2, found);
20988
+ return found;
20989
+ }
20990
+ function parseLine(raw) {
20991
+ try {
20992
+ return JSON.parse(raw);
20993
+ } catch {
20994
+ return null;
20995
+ }
20996
+ }
20997
+ async function bornAt(file2) {
20998
+ const birth = (await stat4(file2).catch(() => null))?.birthtimeMs ?? 0;
20999
+ return birth > 0 ? birth : null;
21000
+ }
21001
+ function sharedPrefix(a, b) {
21002
+ let count = 0;
21003
+ while (count < a.length && count < b.length) {
21004
+ const uuid3 = a[count]?.uuid;
21005
+ if (uuid3 === void 0 || uuid3 !== b[count]?.uuid) break;
21006
+ count++;
21007
+ }
21008
+ return count;
21009
+ }
21010
+ async function forkOrigin(file2, turns, readTurns, isMessage, depth = 0) {
21011
+ const first = turns[0];
21012
+ if (first?.uuid === void 0 || depth > MAX_DEPTH) return null;
21013
+ const born = await bornAt(file2);
21014
+ const dir = dirname4(file2);
21015
+ const names = (await readdir2(dir).catch(() => [])).filter(
21016
+ (name) => name.endsWith(".jsonl")
21017
+ );
21018
+ let best = null;
21019
+ for (const name of names) {
21020
+ const other = join6(dir, name);
21021
+ if (other === file2) continue;
21022
+ if (await firstMessageUuid(other, isMessage).catch(() => null) !== first.uuid) continue;
21023
+ const otherBorn = await bornAt(other) ?? 0;
21024
+ if (born !== null && otherBorn >= born) continue;
21025
+ const shared = sharedPrefix(turns, await readTurns(other).catch(() => []));
21026
+ if (shared > 0 && (best === null || shared > best.shared || shared === best.shared && otherBorn > best.born))
21027
+ best = { file: other, shared, born: otherBorn };
21028
+ }
21029
+ if (best === null) return null;
21030
+ const last = turns[best.shared - 1];
21031
+ if (last?.uuid === void 0) return null;
21032
+ const copy = { uuid: last.uuid, at: last.at, text: last.text };
21033
+ const parentTurns = await readTurns(best.file).catch(() => null);
21034
+ const deeper = parentTurns ? await forkOrigin(best.file, parentTurns, readTurns, isMessage, depth + 1) : null;
21035
+ if (deeper && best.shared <= deeper.copied)
21036
+ return { sessionId: deeper.sessionId, copied: best.shared, last: copy };
21037
+ return { sessionId: basename2(best.file, ".jsonl"), copied: best.shared, last: copy };
21038
+ }
21039
+
21040
+ // src/agents/claude/transcripts.ts
20959
21041
  function claudeConfigDir(env = process.env) {
20960
- return env.CLAUDE_CONFIG_DIR ?? join6(homedir3(), ".claude");
21042
+ return env.CLAUDE_CONFIG_DIR ?? join7(homedir3(), ".claude");
20961
21043
  }
20962
21044
  async function findTranscript(sessionId, env = process.env) {
20963
- const projectsDir = join6(claudeConfigDir(env), "projects");
21045
+ const projectsDir = join7(claudeConfigDir(env), "projects");
20964
21046
  let dirs;
20965
21047
  try {
20966
- dirs = await readdir2(projectsDir, { withFileTypes: true });
21048
+ dirs = await readdir3(projectsDir, { withFileTypes: true });
20967
21049
  } catch {
20968
21050
  return null;
20969
21051
  }
20970
21052
  for (const dir of dirs) {
20971
21053
  if (!dir.isDirectory()) continue;
20972
- const file2 = join6(projectsDir, dir.name, `${sessionId}.jsonl`);
21054
+ const file2 = join7(projectsDir, dir.name, `${sessionId}.jsonl`);
20973
21055
  try {
20974
21056
  await access(file2);
20975
21057
  return file2;
@@ -20980,24 +21062,24 @@ async function findTranscript(sessionId, env = process.env) {
20980
21062
  }
20981
21063
  async function collectTranscripts(env = process.env) {
20982
21064
  const found = /* @__PURE__ */ new Map();
20983
- const projectsDir = join6(claudeConfigDir(env), "projects");
21065
+ const projectsDir = join7(claudeConfigDir(env), "projects");
20984
21066
  let projectDirs;
20985
21067
  try {
20986
- projectDirs = await readdir2(projectsDir, { withFileTypes: true });
21068
+ projectDirs = await readdir3(projectsDir, { withFileTypes: true });
20987
21069
  } catch {
20988
21070
  return found;
20989
21071
  }
20990
21072
  for (const entry of projectDirs) {
20991
21073
  if (!entry.isDirectory()) continue;
20992
- const dir = join6(projectsDir, entry.name);
21074
+ const dir = join7(projectsDir, entry.name);
20993
21075
  let files;
20994
21076
  try {
20995
- files = await readdir2(dir);
21077
+ files = await readdir3(dir);
20996
21078
  } catch {
20997
21079
  continue;
20998
21080
  }
20999
21081
  for (const file2 of files) {
21000
- if (file2.endsWith(".jsonl")) found.set(file2.slice(0, -".jsonl".length), join6(dir, file2));
21082
+ if (file2.endsWith(".jsonl")) found.set(file2.slice(0, -".jsonl".length), join7(dir, file2));
21001
21083
  }
21002
21084
  }
21003
21085
  return found;
@@ -21067,7 +21149,7 @@ function displayTurn(turn, roots) {
21067
21149
  }
21068
21150
  var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
21069
21151
  var CLEAR = /<command-name>\s*\/clear\s*<\/command-name>/;
21070
- var LINE_UUID = /"uuid":"([^"]+)"/;
21152
+ var LINE_UUID2 = /"uuid":"([^"]+)"/;
21071
21153
  var LINE_PARENT = /"parentUuid":"([^"]+)"/;
21072
21154
  var LIST_LIMIT = 40;
21073
21155
  function addOnce(tools, key, text) {
@@ -21103,13 +21185,38 @@ function addAssistant(turn, line) {
21103
21185
  }
21104
21186
  }
21105
21187
  async function readConversation(file2) {
21188
+ const turns = await readAllTurns(file2);
21189
+ const origin = await forkOrigin(file2, turns, readTranscriptTurns, isMessageLine);
21190
+ if (origin === null) return turns;
21191
+ return turns.slice(origin.copied).map((turn, index) => {
21192
+ const { resumed: _, ...rest } = turn;
21193
+ return { ...index === 0 ? rest : turn, id: `m${index + 1}` };
21194
+ });
21195
+ }
21196
+ async function readForkOrigin(file2) {
21197
+ return forkOrigin(file2, await readAllTurns(file2), readTranscriptTurns, isMessageLine);
21198
+ }
21199
+ function isMessageLine(line) {
21200
+ return messageText(line) !== null && typeof line.timestamp === "string";
21201
+ }
21202
+ var turnCache = /* @__PURE__ */ new Map();
21203
+ async function readTranscriptTurns(file2) {
21204
+ const size = (await stat5(file2)).size;
21205
+ const cached2 = turnCache.get(file2);
21206
+ if (cached2 && cached2.size === size) return cached2.turns;
21207
+ const turns = readAllTurns(file2);
21208
+ turnCache.set(file2, { size, turns });
21209
+ turns.catch(() => turnCache.delete(file2));
21210
+ return turns;
21211
+ }
21212
+ async function readAllTurns(file2) {
21106
21213
  const turns = [];
21107
21214
  const seen = /* @__PURE__ */ new Set();
21108
21215
  let pendingResume = false;
21109
- const lines = createInterface({ input: createReadStream(file2, "utf8"), crlfDelay: Infinity });
21216
+ const lines = createInterface2({ input: createReadStream2(file2, "utf8"), crlfDelay: Infinity });
21110
21217
  for await (const raw of lines) {
21111
21218
  if (raw.includes("<command-name>") && CLEAR.test(raw)) pendingResume = true;
21112
- const uuid3 = LINE_UUID.exec(raw)?.[1];
21219
+ const uuid3 = LINE_UUID2.exec(raw)?.[1];
21113
21220
  const parent = LINE_PARENT.exec(raw)?.[1];
21114
21221
  if (seen.size > 0 && parent !== void 0 && !seen.has(parent) && !raw.includes('"isSidechain":true'))
21115
21222
  pendingResume = true;
@@ -21223,7 +21330,7 @@ async function readLastTurn(file2) {
21223
21330
  }
21224
21331
  }
21225
21332
  async function readsHandoff(file2) {
21226
- const lines = createInterface({ input: createReadStream(file2, "utf8"), crlfDelay: Infinity });
21333
+ const lines = createInterface2({ input: createReadStream2(file2, "utf8"), crlfDelay: Infinity });
21227
21334
  for await (const raw of lines) {
21228
21335
  if (!raw.includes("handoff")) continue;
21229
21336
  if (/"name":"(?:Read|Bash)"/.test(raw) && /"file_path":"[^"]*handoff[^"]*"/i.test(raw))
@@ -21234,7 +21341,7 @@ async function readsHandoff(file2) {
21234
21341
  }
21235
21342
  async function countUserLines(file2) {
21236
21343
  let count = 0;
21237
- const lines = createInterface({ input: createReadStream(file2, "utf8"), crlfDelay: Infinity });
21344
+ const lines = createInterface2({ input: createReadStream2(file2, "utf8"), crlfDelay: Infinity });
21238
21345
  for await (const raw of lines) {
21239
21346
  if (!raw.includes('"type":"user"')) continue;
21240
21347
  try {
@@ -21277,6 +21384,7 @@ var claude = {
21277
21384
  collectTranscripts,
21278
21385
  collectTranscriptIds,
21279
21386
  readConversation,
21387
+ readForkOrigin,
21280
21388
  readsHandoff,
21281
21389
  readLastTurn,
21282
21390
  countUserLines,
@@ -21307,7 +21415,7 @@ import { createHash } from "crypto";
21307
21415
 
21308
21416
  // src/store/conversations.ts
21309
21417
  import { appendFile, mkdir as mkdir4, readFile as readFile4 } from "fs/promises";
21310
- import { dirname as dirname4 } from "path";
21418
+ import { dirname as dirname5 } from "path";
21311
21419
  import { constants as constants2, gunzipSync, gzipSync } from "zlib";
21312
21420
 
21313
21421
  // src/redact.ts
@@ -21421,7 +21529,7 @@ async function appendConversationTurns(root, sessionId, turns) {
21421
21529
  const chunk = `${value.map((turn) => JSON.stringify(turn)).join("\n")}
21422
21530
  `;
21423
21531
  const file2 = projectPaths(root).conversation(sessionId);
21424
- await mkdir4(dirname4(file2), { recursive: true });
21532
+ await mkdir4(dirname5(file2), { recursive: true });
21425
21533
  await appendFile(file2, gzipSync(Buffer.from(chunk, "utf8")));
21426
21534
  return fresh.length;
21427
21535
  }
@@ -21433,7 +21541,7 @@ async function appendConversationHashes(root, sessionId, hashes) {
21433
21541
  const chunk = `${fresh.map((turn) => JSON.stringify(turn)).join("\n")}
21434
21542
  `;
21435
21543
  const file2 = projectPaths(root).conversation(sessionId);
21436
- await mkdir4(dirname4(file2), { recursive: true });
21544
+ await mkdir4(dirname5(file2), { recursive: true });
21437
21545
  await appendFile(file2, gzipSync(Buffer.from(chunk, "utf8")));
21438
21546
  return fresh.length;
21439
21547
  }
@@ -21741,8 +21849,8 @@ async function installGitHook(root, event) {
21741
21849
  const dir = await gitHooksDir(gitRoot);
21742
21850
  if (dir === void 0) return false;
21743
21851
  await mkdir5(dir, { recursive: true });
21744
- const file2 = join7(dir, event);
21745
- const exists = await stat4(file2).then(
21852
+ const file2 = join8(dir, event);
21853
+ const exists = await stat6(file2).then(
21746
21854
  () => true,
21747
21855
  () => false
21748
21856
  );
@@ -21775,7 +21883,7 @@ async function refreshGitHooks(root) {
21775
21883
  if (dir === void 0) return [];
21776
21884
  const refreshed = [];
21777
21885
  for (const event of GIT_HOOK_EVENTS) {
21778
- const current = await readFile5(join7(dir, event), "utf8").catch(() => void 0);
21886
+ const current = await readFile5(join8(dir, event), "utf8").catch(() => void 0);
21779
21887
  if (current === void 0 || !current.includes(BLOCK_START)) continue;
21780
21888
  if (await installGitHook(root, event)) refreshed.push(event);
21781
21889
  }
@@ -21788,7 +21896,7 @@ async function uninstallGitHooks(root) {
21788
21896
  if (dir === void 0) return [];
21789
21897
  const removed = [];
21790
21898
  for (const event of GIT_HOOK_EVENTS) {
21791
- const file2 = join7(dir, event);
21899
+ const file2 = join8(dir, event);
21792
21900
  const current = await readFile5(file2, "utf8").catch(() => void 0);
21793
21901
  if (current === void 0 || !current.includes(BLOCK_START)) continue;
21794
21902
  const next = withoutHookBlock(current);
@@ -21838,6 +21946,7 @@ export {
21838
21946
  appendConversationTurns,
21839
21947
  appendConversationHashes,
21840
21948
  MESSAGE_HASH,
21949
+ messageHash,
21841
21950
  hashTurns,
21842
21951
  readSessionMessages,
21843
21952
  storedMessages,