@yishiguji/tokenarena 0.16.5 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4494,57 +4494,314 @@ var RooCodeParser = class {
4494
4494
  registerParser(new RooCodeParser());
4495
4495
 
4496
4496
  // src/parsers/snow.ts
4497
- import { existsSync as existsSync22 } from "fs";
4497
+ import { existsSync as existsSync22, readdirSync as readdirSync12, statSync as statSync6 } from "fs";
4498
4498
  import { homedir as homedir22 } from "os";
4499
- import { join as join23 } from "path";
4500
- var DEFAULT_DATA_DIR6 = join23(homedir22(), ".snow", "usage");
4499
+ import { basename as basename10, join as join23 } from "path";
4500
+ var DEFAULT_SNOW_DIR = join23(homedir22(), ".snow");
4501
+ var USAGE_DIR_NAME = "usage";
4502
+ var SESSIONS_DIR_NAME = "sessions";
4503
+ var NON_TRANSCRIPT_DIRS = /* @__PURE__ */ new Set(["subagent", "compressed"]);
4504
+ var SESSION_MATCH_WINDOW_MS = 5 * 60 * 1e3;
4505
+ var DAMAGED_TRANSCRIPT_SETTLE_MS = 60 * 1e3;
4506
+ var MIN_TIMESTAMP_MS = Date.UTC(2020, 0, 1);
4507
+ var MAX_TIMESTAMP_MS = Date.UTC(2080, 0, 1);
4501
4508
  function toNonNegativeNumber2(value) {
4509
+ if (typeof value !== "number" && typeof value !== "string") return 0;
4502
4510
  const numberValue = Number(value);
4503
4511
  return Number.isFinite(numberValue) && numberValue >= 0 ? numberValue : 0;
4504
4512
  }
4513
+ function toTime(value) {
4514
+ const time = typeof value === "string" ? Date.parse(value) : typeof value === "number" ? value : Number.NaN;
4515
+ return time >= MIN_TIMESTAMP_MS && time < MAX_TIMESTAMP_MS ? time : null;
4516
+ }
4517
+ function isRecord(value) {
4518
+ return value !== null && typeof value === "object" && !Array.isArray(value);
4519
+ }
4520
+ function findSourceFiles(dir, extension, results = []) {
4521
+ let entries;
4522
+ try {
4523
+ entries = readdirSync12(dir, { withFileTypes: true });
4524
+ } catch (error) {
4525
+ const code = error.code;
4526
+ if (code === "ENOENT" || code === "ENOTDIR") return results;
4527
+ throw error;
4528
+ }
4529
+ for (const entry of entries) {
4530
+ const fullPath = join23(dir, entry.name);
4531
+ if (entry.isDirectory()) {
4532
+ if (extension === ".json" && NON_TRANSCRIPT_DIRS.has(entry.name)) {
4533
+ continue;
4534
+ }
4535
+ findSourceFiles(fullPath, extension, results);
4536
+ } else if (entry.name.endsWith(extension)) {
4537
+ results.push(fullPath);
4538
+ }
4539
+ }
4540
+ return results;
4541
+ }
4542
+ function resolveProject(session) {
4543
+ if (typeof session.projectPath === "string" && session.projectPath) {
4544
+ const segments = session.projectPath.split(/[\\/]+/).filter(Boolean);
4545
+ return segments.at(-1) || "unknown";
4546
+ }
4547
+ return "unknown";
4548
+ }
4549
+ function readTranscript(filePath) {
4550
+ const content = readFileSafe(filePath);
4551
+ if (content === null) return { status: "damaged" };
4552
+ let parsed;
4553
+ try {
4554
+ parsed = JSON.parse(content);
4555
+ } catch {
4556
+ return { status: "damaged" };
4557
+ }
4558
+ if (!isRecord(parsed) || !Array.isArray(parsed.messages)) {
4559
+ return { status: "foreign" };
4560
+ }
4561
+ const session = parsed;
4562
+ const compressedFrom = typeof session.compressedFrom === "string" && session.compressedFrom ? session.compressedFrom : void 0;
4563
+ const copiedUntil = compressedFrom !== void 0 || typeof session.branchedFrom === "string" ? Math.max(
4564
+ toTime(session.compressedAt) ?? Number.NEGATIVE_INFINITY,
4565
+ toTime(session.createdAt) ?? Number.NEGATIVE_INFINITY
4566
+ ) : Number.NEGATIVE_INFINITY;
4567
+ const messages = [];
4568
+ let lastMessageAt = 0;
4569
+ for (const message of parsed.messages) {
4570
+ if (!isRecord(message)) continue;
4571
+ if (message.role !== "user" && message.role !== "assistant") continue;
4572
+ const time = toTime(message.timestamp);
4573
+ if (time === null) continue;
4574
+ if (time > lastMessageAt) lastMessageAt = time;
4575
+ if (time <= copiedUntil) continue;
4576
+ messages.push({ role: message.role, time });
4577
+ }
4578
+ return {
4579
+ status: "ok",
4580
+ transcript: {
4581
+ filePath,
4582
+ sessionId: typeof session.id === "string" && session.id ? session.id : basename10(filePath, ".json"),
4583
+ project: resolveProject(session),
4584
+ updatedAt: toTime(session.updatedAt) ?? lastMessageAt,
4585
+ ...compressedFrom !== void 0 ? { compressedFrom } : {},
4586
+ messages
4587
+ }
4588
+ };
4589
+ }
4590
+ function isNewerCopy(candidate, current) {
4591
+ if (candidate.updatedAt !== current.updatedAt) {
4592
+ return candidate.updatedAt > current.updatedAt;
4593
+ }
4594
+ if (candidate.messages.length !== current.messages.length) {
4595
+ return candidate.messages.length > current.messages.length;
4596
+ }
4597
+ return candidate.filePath < current.filePath;
4598
+ }
4599
+ function resolveConversationId(transcript, transcripts) {
4600
+ let current = transcript;
4601
+ const visited = /* @__PURE__ */ new Set([current.sessionId]);
4602
+ while (current.compressedFrom && !visited.has(current.compressedFrom)) {
4603
+ visited.add(current.compressedFrom);
4604
+ const source = transcripts.get(current.compressedFrom);
4605
+ if (!source) return current.compressedFrom;
4606
+ current = source;
4607
+ }
4608
+ return current.sessionId;
4609
+ }
4610
+ function mayStillBeWriting(filePath, now) {
4611
+ try {
4612
+ return now - statSync6(filePath).mtimeMs < DAMAGED_TRANSCRIPT_SETTLE_MS;
4613
+ } catch {
4614
+ return true;
4615
+ }
4616
+ }
4617
+ function distanceToNearest(sortedTimes, time) {
4618
+ let low = 0;
4619
+ let high = sortedTimes.length;
4620
+ while (low < high) {
4621
+ const middle = low + high >>> 1;
4622
+ if (sortedTimes[middle] < time) low = middle + 1;
4623
+ else high = middle;
4624
+ }
4625
+ let distance = Number.POSITIVE_INFINITY;
4626
+ if (low < sortedTimes.length) distance = sortedTimes[low] - time;
4627
+ if (low > 0) distance = Math.min(distance, time - sortedTimes[low - 1]);
4628
+ return distance;
4629
+ }
4630
+ function attributeUsage(entries, windows) {
4631
+ if (windows.length === 0) return;
4632
+ const byStart = [...windows].sort((left, right) => left.start - right.start);
4633
+ const byTime = entries.map((entry) => ({ entry, time: entry.timestamp.getTime() })).sort((left, right) => left.time - right.time);
4634
+ const open = [];
4635
+ let nextWindow = 0;
4636
+ for (const { entry, time } of byTime) {
4637
+ while (nextWindow < byStart.length && byStart[nextWindow].start <= time) {
4638
+ open.push(byStart[nextWindow]);
4639
+ nextWindow++;
4640
+ }
4641
+ let kept = 0;
4642
+ let best;
4643
+ let bestDistance = Number.POSITIVE_INFINITY;
4644
+ for (const window of open) {
4645
+ if (window.end < time) continue;
4646
+ open[kept++] = window;
4647
+ const distance = distanceToNearest(window.assistantTimes, time);
4648
+ if (best === void 0 || distance < bestDistance || distance === bestDistance && window.sessionId < best.sessionId) {
4649
+ best = window;
4650
+ bestDistance = distance;
4651
+ }
4652
+ }
4653
+ open.length = kept;
4654
+ if (best) entry.sessionId = best.sessionId;
4655
+ }
4656
+ }
4505
4657
  var SnowParser = class {
4506
- constructor(dataDir = DEFAULT_DATA_DIR6) {
4507
- this.dataDir = dataDir;
4508
- this.tool = { id: "snow", name: "Snow CLI", dataDir };
4658
+ constructor(snowDir = DEFAULT_SNOW_DIR) {
4659
+ this.snowDir = snowDir;
4660
+ this.tool = {
4661
+ id: "snow",
4662
+ name: "Snow CLI",
4663
+ dataDir: join23(snowDir, USAGE_DIR_NAME)
4664
+ };
4509
4665
  }
4510
- dataDir;
4666
+ snowDir;
4511
4667
  tool;
4512
4668
  async parse() {
4669
+ const usage = this.parseUsage();
4670
+ const sessions = this.parseSessions();
4671
+ attributeUsage(usage.entries, sessions.windows);
4672
+ return {
4673
+ buckets: aggregateToBuckets(usage.entries),
4674
+ sessions: extractSessions(sessions.events, usage.entries),
4675
+ ...usage.incomplete ? { incomplete: true } : {}
4676
+ };
4677
+ }
4678
+ listSourceFiles() {
4679
+ return [
4680
+ ...findSourceFiles(join23(this.snowDir, USAGE_DIR_NAME), ".jsonl"),
4681
+ ...findSourceFiles(join23(this.snowDir, SESSIONS_DIR_NAME), ".json")
4682
+ ];
4683
+ }
4684
+ /**
4685
+ * Build session timing events plus the windows used to attribute usage
4686
+ * records. Message timestamps drive turn boundaries, so active time, session
4687
+ * duration, and message counts all come from real transcripts.
4688
+ *
4689
+ * Transcripts only feed sessions, never buckets, so a damaged one is skipped
4690
+ * rather than holding back the whole source: Snow cannot open it either and
4691
+ * will not rewrite it, so deferring would block Snow uploads for good.
4692
+ */
4693
+ parseSessions() {
4694
+ const transcripts = /* @__PURE__ */ new Map();
4695
+ const damaged = [];
4696
+ const now = Date.now();
4697
+ for (const filePath of findSourceFiles(
4698
+ join23(this.snowDir, SESSIONS_DIR_NAME),
4699
+ ".json"
4700
+ )) {
4701
+ const read = readTranscript(filePath);
4702
+ if (read.status === "damaged") {
4703
+ if (!mayStillBeWriting(filePath, now)) damaged.push(filePath);
4704
+ continue;
4705
+ }
4706
+ if (read.status === "foreign") continue;
4707
+ const current = transcripts.get(read.transcript.sessionId);
4708
+ if (!current || isNewerCopy(read.transcript, current)) {
4709
+ transcripts.set(read.transcript.sessionId, read.transcript);
4710
+ }
4711
+ }
4712
+ if (damaged.length > 0) {
4713
+ const more = damaged.length > 1 ? ` and ${damaged.length - 1} more` : "";
4714
+ logger.warn(
4715
+ `Skipped damaged Snow session file ${damaged[0]}${more}. Token usage is still counted.`
4716
+ );
4717
+ }
4718
+ const events = [];
4719
+ const windows = [];
4720
+ for (const transcript of transcripts.values()) {
4721
+ if (transcript.messages.length === 0) continue;
4722
+ const sessionId = resolveConversationId(transcript, transcripts);
4723
+ const assistantTimes = [];
4724
+ let start = Number.POSITIVE_INFINITY;
4725
+ let end = Number.NEGATIVE_INFINITY;
4726
+ for (const message of transcript.messages) {
4727
+ events.push({
4728
+ sessionId,
4729
+ source: "snow",
4730
+ project: transcript.project,
4731
+ timestamp: new Date(message.time),
4732
+ role: message.role
4733
+ });
4734
+ if (message.role === "assistant") assistantTimes.push(message.time);
4735
+ if (message.time < start) start = message.time;
4736
+ if (message.time > end) end = message.time;
4737
+ }
4738
+ windows.push({
4739
+ sessionId,
4740
+ assistantTimes: assistantTimes.sort((left, right) => left - right),
4741
+ start: start - SESSION_MATCH_WINDOW_MS,
4742
+ end: end + SESSION_MATCH_WINDOW_MS
4743
+ });
4744
+ }
4745
+ return { events, windows };
4746
+ }
4747
+ /**
4748
+ * Parse usage JSONL. Session attribution happens afterwards, because it needs
4749
+ * the transcripts.
4750
+ *
4751
+ * The bucket project stays `unknown` on purpose; see the note on
4752
+ * `SESSION_MATCH_WINDOW_MS`.
4753
+ */
4754
+ parseUsage() {
4513
4755
  const entries = [];
4514
- for (const filePath of findJsonlFiles(this.dataDir)) {
4756
+ let incomplete = false;
4757
+ for (const filePath of findSourceFiles(
4758
+ join23(this.snowDir, USAGE_DIR_NAME),
4759
+ ".jsonl"
4760
+ )) {
4515
4761
  const content = readFileSafe(filePath);
4516
- if (!content) continue;
4762
+ if (content === null) {
4763
+ if (existsSync22(filePath)) incomplete = true;
4764
+ continue;
4765
+ }
4517
4766
  for (const line of content.split("\n")) {
4518
4767
  if (!line.trim()) continue;
4768
+ let record;
4519
4769
  try {
4520
- const record = JSON.parse(line);
4521
- if (typeof record.timestamp !== "string") continue;
4522
- const timestamp = new Date(record.timestamp);
4523
- if (Number.isNaN(timestamp.getTime())) continue;
4524
- const inputTokens = toNonNegativeNumber2(record.inputTokens);
4525
- const outputTokens = toNonNegativeNumber2(record.outputTokens);
4526
- const cachedTokens = toNonNegativeNumber2(record.cacheReadInputTokens);
4527
- const reasoningTokens = toNonNegativeNumber2(record.reasoningTokens);
4528
- if (inputTokens + outputTokens + cachedTokens + reasoningTokens === 0)
4529
- continue;
4530
- entries.push({
4531
- source: "snow",
4532
- model: typeof record.model === "string" && record.model ? record.model : "unknown",
4533
- project: "unknown",
4534
- timestamp,
4535
- inputTokens,
4536
- outputTokens,
4537
- reasoningTokens,
4538
- cachedTokens
4539
- });
4770
+ const parsed = JSON.parse(line);
4771
+ if (!isRecord(parsed)) continue;
4772
+ record = parsed;
4540
4773
  } catch {
4774
+ continue;
4541
4775
  }
4776
+ const time = toTime(record.timestamp);
4777
+ if (time === null) continue;
4778
+ const reportedInputTokens = toNonNegativeNumber2(record.inputTokens);
4779
+ const outputTokens = toNonNegativeNumber2(record.outputTokens);
4780
+ const cachedTokens = toNonNegativeNumber2(record.cacheReadInputTokens);
4781
+ const cacheCreationTokens = toNonNegativeNumber2(
4782
+ record.cacheCreationInputTokens
4783
+ );
4784
+ const reasoningTokens = toNonNegativeNumber2(record.reasoningTokens);
4785
+ const inputTokens = record.cacheCreationInputTokens === void 0 && cachedTokens <= reportedInputTokens ? reportedInputTokens - cachedTokens : reportedInputTokens;
4786
+ if (inputTokens + outputTokens + cachedTokens + cacheCreationTokens + reasoningTokens === 0)
4787
+ continue;
4788
+ entries.push({
4789
+ source: "snow",
4790
+ model: typeof record.model === "string" && record.model ? record.model : "unknown",
4791
+ project: "unknown",
4792
+ timestamp: new Date(time),
4793
+ inputTokens,
4794
+ outputTokens,
4795
+ reasoningTokens,
4796
+ cachedTokens,
4797
+ cacheCreationTokens
4798
+ });
4542
4799
  }
4543
4800
  }
4544
- return { buckets: aggregateToBuckets(entries), sessions: [] };
4801
+ return { entries, incomplete };
4545
4802
  }
4546
4803
  isInstalled() {
4547
- return existsSync22(this.dataDir);
4804
+ return existsSync22(join23(this.snowDir, USAGE_DIR_NAME));
4548
4805
  }
4549
4806
  };
4550
4807
  registerParser(new SnowParser());
@@ -5174,9 +5431,9 @@ var ZCodeParser = class {
5174
5431
  registerParser(new ZCodeParser());
5175
5432
 
5176
5433
  // src/parsers/qodercli.ts
5177
- import { existsSync as existsSync25, readdirSync as readdirSync12 } from "fs";
5434
+ import { existsSync as existsSync25, readdirSync as readdirSync13 } from "fs";
5178
5435
  import { homedir as homedir25 } from "os";
5179
- import { basename as basename10, join as join26 } from "path";
5436
+ import { basename as basename11, join as join26 } from "path";
5180
5437
  var TOOL_ID16 = "qodercli";
5181
5438
  var TOOL_NAME15 = "Qoder CLI";
5182
5439
  var DEFAULT_PROJECTS_DIR = join26(homedir25(), ".qoder", "projects");
@@ -5241,7 +5498,7 @@ function projectFromEncodedSlug(slug) {
5241
5498
  }
5242
5499
  function projectFromCwd(cwd) {
5243
5500
  if (!cwd) return "unknown";
5244
- const leaf = basename10(cwd.replace(/[\\/]+$/, ""));
5501
+ const leaf = basename11(cwd.replace(/[\\/]+$/, ""));
5245
5502
  return leaf || "unknown";
5246
5503
  }
5247
5504
  function totalCreditsUsed(quota) {
@@ -5344,7 +5601,7 @@ var QoderCliParser = class {
5344
5601
  parseProjectSessions(sessionEvents) {
5345
5602
  if (!existsSync25(this.projectsDir)) return;
5346
5603
  for (const filePath of findJsonlFiles(this.projectsDir)) {
5347
- if (basename10(filePath).startsWith("verified-")) continue;
5604
+ if (basename11(filePath).startsWith("verified-")) continue;
5348
5605
  const content = readFileSafe(filePath);
5349
5606
  if (!content) continue;
5350
5607
  if (classifyQoderEntrypoint(content) !== CLI_ENTRYPOINT) continue;
@@ -5382,7 +5639,7 @@ var QoderCliParser = class {
5382
5639
  if (!existsSync25(this.runsDir)) return [];
5383
5640
  let runDirs;
5384
5641
  try {
5385
- runDirs = readdirSync12(this.runsDir, { withFileTypes: true }).filter(
5642
+ runDirs = readdirSync13(this.runsDir, { withFileTypes: true }).filter(
5386
5643
  (d) => d.isDirectory()
5387
5644
  );
5388
5645
  } catch {
@@ -5416,12 +5673,12 @@ var QoderCliParser = class {
5416
5673
  registerParser(new QoderCliParser());
5417
5674
 
5418
5675
  // src/parsers/grok-build.ts
5419
- import { existsSync as existsSync26, readdirSync as readdirSync13 } from "fs";
5676
+ import { existsSync as existsSync26, readdirSync as readdirSync14 } from "fs";
5420
5677
  import { homedir as homedir26 } from "os";
5421
- import { basename as basename11, join as join27 } from "path";
5678
+ import { basename as basename12, join as join27 } from "path";
5422
5679
  var TOOL_ID17 = "grok-build";
5423
5680
  var TOOL_NAME16 = "Grok Build";
5424
- var DEFAULT_DATA_DIR7 = join27(homedir26(), ".grok", "sessions");
5681
+ var DEFAULT_DATA_DIR6 = join27(homedir26(), ".grok", "sessions");
5425
5682
  var DEFAULT_ARCHIVE_DIR2 = join27(homedir26(), ".grok", "archived_sessions");
5426
5683
  function createToolDefinition12(dataDir) {
5427
5684
  return {
@@ -5437,10 +5694,10 @@ function toSafeNumber12(value) {
5437
5694
  function projectFromEncodedCwd(encoded) {
5438
5695
  try {
5439
5696
  const decoded = decodeURIComponent(encoded);
5440
- const leaf = basename11(decoded.replace(/[\\/]+$/, ""));
5697
+ const leaf = basename12(decoded.replace(/[\\/]+$/, ""));
5441
5698
  return leaf || "unknown";
5442
5699
  } catch {
5443
- const leaf = basename11(encoded);
5700
+ const leaf = basename12(encoded);
5444
5701
  return leaf || "unknown";
5445
5702
  }
5446
5703
  }
@@ -5494,7 +5751,7 @@ function findSessionDirs(dataDir) {
5494
5751
  if (!existsSync26(dataDir)) return results;
5495
5752
  let projectDirs;
5496
5753
  try {
5497
- projectDirs = readdirSync13(dataDir, { withFileTypes: true });
5754
+ projectDirs = readdirSync14(dataDir, { withFileTypes: true });
5498
5755
  } catch {
5499
5756
  return results;
5500
5757
  }
@@ -5505,7 +5762,7 @@ function findSessionDirs(dataDir) {
5505
5762
  const project = projectFromEncodedCwd(projectEntry.name);
5506
5763
  let sessionEntries;
5507
5764
  try {
5508
- sessionEntries = readdirSync13(projectDir, { withFileTypes: true });
5765
+ sessionEntries = readdirSync14(projectDir, { withFileTypes: true });
5509
5766
  } catch {
5510
5767
  continue;
5511
5768
  }
@@ -5534,7 +5791,7 @@ function readFallbackModel(sessionDir) {
5534
5791
  }
5535
5792
  }
5536
5793
  var GrokBuildParser = class {
5537
- constructor(dataDir = DEFAULT_DATA_DIR7, archiveDir = dataDir === DEFAULT_DATA_DIR7 ? DEFAULT_ARCHIVE_DIR2 : void 0) {
5794
+ constructor(dataDir = DEFAULT_DATA_DIR6, archiveDir = dataDir === DEFAULT_DATA_DIR6 ? DEFAULT_ARCHIVE_DIR2 : void 0) {
5538
5795
  this.dataDir = dataDir;
5539
5796
  this.archiveDir = archiveDir;
5540
5797
  this.tool = createToolDefinition12(dataDir);
@@ -5635,7 +5892,7 @@ registerParser(new GrokBuildParser());
5635
5892
  // src/parsers/atomcode.ts
5636
5893
  import { existsSync as existsSync27 } from "fs";
5637
5894
  import { homedir as homedir27 } from "os";
5638
- import { basename as basename12, join as join28 } from "path";
5895
+ import { basename as basename13, join as join28 } from "path";
5639
5896
  var TOOL_ID18 = "atomcode";
5640
5897
  var TOOL_NAME17 = "AtomCode";
5641
5898
  var DEFAULT_SESSIONS_DIR6 = join28(homedir27(), ".atomcode", "sessions");
@@ -5690,7 +5947,7 @@ function getMetaModel(meta) {
5690
5947
  }
5691
5948
  function getMetaProject(meta) {
5692
5949
  if (typeof meta?.working_dir === "string" && meta.working_dir) {
5693
- return basename12(meta.working_dir) || "unknown";
5950
+ return basename13(meta.working_dir) || "unknown";
5694
5951
  }
5695
5952
  return "unknown";
5696
5953
  }
@@ -5781,9 +6038,9 @@ var AtomCodeParser = class {
5781
6038
  registerParser(new AtomCodeParser());
5782
6039
 
5783
6040
  // src/parsers/dsh.ts
5784
- import { existsSync as existsSync28, readdirSync as readdirSync14, readFileSync as readFileSync9 } from "fs";
6041
+ import { existsSync as existsSync28, readdirSync as readdirSync15, readFileSync as readFileSync9 } from "fs";
5785
6042
  import { homedir as homedir28 } from "os";
5786
- import { basename as basename13, join as join29 } from "path";
6043
+ import { basename as basename14, join as join29 } from "path";
5787
6044
  import * as zlib from "zlib";
5788
6045
  var TOOL_ID19 = "dsh";
5789
6046
  var TOOL_NAME18 = "DeepSeek Harness";
@@ -5883,7 +6140,7 @@ function findSessionLogs(dir) {
5883
6140
  const results = [];
5884
6141
  if (!existsSync28(dir)) return results;
5885
6142
  try {
5886
- for (const entry of readdirSync14(dir, { withFileTypes: true })) {
6143
+ for (const entry of readdirSync15(dir, { withFileTypes: true })) {
5887
6144
  const fullPath = join29(dir, entry.name);
5888
6145
  if (entry.isDirectory()) {
5889
6146
  results.push(...findSessionLogs(fullPath));
@@ -5933,7 +6190,7 @@ var DshParser = class {
5933
6190
  const relativeParts = filePath.slice(sessionsDir.length + 1).split(/[\\/]/);
5934
6191
  const header = rows[0]?.type === "session" ? rows[0] : void 0;
5935
6192
  const sessionId = (header?.id ?? decodeSegment(relativeParts[1] ?? "")) || "unknown";
5936
- const project = typeof header?.cwd === "string" && header.cwd ? basename13(header.cwd) || "unknown" : projectFromDirName(relativeParts[0] ?? "");
6193
+ const project = typeof header?.cwd === "string" && header.cwd ? basename14(header.cwd) || "unknown" : projectFromDirName(relativeParts[0] ?? "");
5937
6194
  let currentModel = "unknown";
5938
6195
  for (const row of rows) {
5939
6196
  if (row.type === "request/context") {
@@ -7311,7 +7568,7 @@ function saveUploadManifest(manifest) {
7311
7568
 
7312
7569
  // src/services/parse-cache.ts
7313
7570
  import { createHash as createHash7 } from "crypto";
7314
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync14, statSync as statSync6, writeFileSync as writeFileSync5 } from "fs";
7571
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync14, statSync as statSync7, writeFileSync as writeFileSync5 } from "fs";
7315
7572
  import { hostname as hostname6 } from "os";
7316
7573
  import { join as join35 } from "path";
7317
7574
 
@@ -7367,7 +7624,7 @@ function computeScanFingerprint(files, now = Date.now()) {
7367
7624
  for (const file of [...files].sort()) {
7368
7625
  let stats;
7369
7626
  try {
7370
- stats = statSync6(file);
7627
+ stats = statSync7(file);
7371
7628
  } catch {
7372
7629
  return null;
7373
7630
  }
@@ -7575,12 +7832,12 @@ function createProjectIdentityResolver(settings) {
7575
7832
  return identity;
7576
7833
  };
7577
7834
  }
7578
- function toUploadBuckets(buckets, settings, device, resolveProject = createProjectIdentityResolver(
7835
+ function toUploadBuckets(buckets, settings, device, resolveProject2 = createProjectIdentityResolver(
7579
7836
  settings
7580
7837
  )) {
7581
7838
  const aggregated = /* @__PURE__ */ new Map();
7582
7839
  for (const bucket of buckets) {
7583
- const project = resolveProject(bucket.project);
7840
+ const project = resolveProject2(bucket.project);
7584
7841
  const key = [
7585
7842
  bucket.source,
7586
7843
  bucket.model,
@@ -7616,11 +7873,11 @@ function toUploadBuckets(buckets, settings, device, resolveProject = createProje
7616
7873
  }
7617
7874
  return Array.from(aggregated.values());
7618
7875
  }
7619
- function toUploadSessions(sessions, settings, device, resolveProject = createProjectIdentityResolver(
7876
+ function toUploadSessions(sessions, settings, device, resolveProject2 = createProjectIdentityResolver(
7620
7877
  settings
7621
7878
  )) {
7622
7879
  return sessions.map((session) => {
7623
- const project = resolveProject(session.project);
7880
+ const project = resolveProject2(session.project);
7624
7881
  return {
7625
7882
  source: session.source,
7626
7883
  projectKey: project.projectKey,
@@ -7799,18 +8056,18 @@ async function runSync(config, opts = {}) {
7799
8056
  deviceId: device.deviceId,
7800
8057
  settings
7801
8058
  });
7802
- const resolveProject = createProjectIdentityResolver(settings);
8059
+ const resolveProject2 = createProjectIdentityResolver(settings);
7803
8060
  const uploadBuckets = toUploadBuckets(
7804
8061
  allBuckets,
7805
8062
  settings,
7806
8063
  device,
7807
- resolveProject
8064
+ resolveProject2
7808
8065
  );
7809
8066
  const uploadSessions = toUploadSessions(
7810
8067
  allSessions,
7811
8068
  settings,
7812
8069
  device,
7813
- resolveProject
8070
+ resolveProject2
7814
8071
  );
7815
8072
  const previousManifest = loadUploadManifest();
7816
8073
  const uploadDiff = diffUploadManifest({