aicp-tracker 1.3.3 → 1.3.4

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/log-parser.js +26 -15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aicp-tracker",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "AI Code Pulse — Claude Code usage tracker for JIRA cost attribution",
5
5
  "main": "src/daemon.js",
6
6
  "bin": {
package/src/log-parser.js CHANGED
@@ -12,6 +12,17 @@ function extractTasksFromBranch(branch) {
12
12
  }
13
13
 
14
14
  function sumUsage(entries) {
15
+ // Claude Code writes multiple JSONL lines per message.id (streaming snapshots).
16
+ // All snapshots for the same message.id have identical token counts, so we count
17
+ // only the LAST occurrence of each message.id to avoid double-counting.
18
+ // The last occurrence also has the most complete content (all tool calls present).
19
+ const lastIdxByMsgId = new Map();
20
+ for (let i = 0; i < entries.length; i++) {
21
+ const msgId = entries[i].message?.id;
22
+ if (msgId && entries[i].message?.usage) lastIdxByMsgId.set(msgId, i);
23
+ }
24
+ const countIdxSet = new Set(lastIdxByMsgId.values());
25
+
15
26
  let input = 0, cc = 0, cr = 0, out = 0;
16
27
  let ephUser = 0, ephAsst = 0, ephTool = 0;
17
28
  let webSearch = 0, webFetch = 0;
@@ -19,8 +30,14 @@ function sumUsage(entries) {
19
30
  const models = new Set();
20
31
  const uuids = [];
21
32
 
22
- for (const e of entries) {
23
- const u = e.message?.usage || {};
33
+ for (let i = 0; i < entries.length; i++) {
34
+ const e = entries[i];
35
+ const u = e.message?.usage;
36
+ if (!u) continue;
37
+ // Skip all but the last snapshot for each message.id
38
+ const msgId = e.message?.id;
39
+ if (msgId && !countIdxSet.has(i)) continue;
40
+
24
41
  input += u.input_tokens || 0;
25
42
  cc += u.cache_creation_input_tokens || 0;
26
43
  cr += u.cache_read_input_tokens || 0;
@@ -227,10 +244,10 @@ function parseNewLines(filePath) {
227
244
  fs.closeSync(fd);
228
245
  state.setOffset(filePath, offset + read);
229
246
 
230
- // Parse entries; deduplicate by message.id only for entries that carry usage data
231
- // (non-usage entries like tool-call lines must all be kept to avoid losing file paths)
247
+ // Keep ALL entries deduplication by message.id happens inside sumUsage.
248
+ // We must retain every line here because later snapshots of the same message.id
249
+ // contain the Edit/Write tool calls that earlier snapshots lack.
232
250
  const entries = [];
233
- const seenUsageMsgIds = new Set();
234
251
 
235
252
  for (const raw of buf.slice(0, read).toString('utf8').split('\n')) {
236
253
  const line = raw.trim();
@@ -238,12 +255,6 @@ function parseNewLines(filePath) {
238
255
  let entry;
239
256
  try { entry = JSON.parse(line); } catch { continue; }
240
257
  if (!entry.uuid) continue;
241
-
242
- const msgId = entry.message?.id;
243
- if (msgId && entry.message?.usage) {
244
- if (seenUsageMsgIds.has(msgId)) continue;
245
- seenUsageMsgIds.add(msgId);
246
- }
247
258
  entries.push(entry);
248
259
  }
249
260
 
@@ -270,11 +281,11 @@ function parseNewLines(filePath) {
270
281
  if (entry.promptId) {
271
282
  currentPromptId = entry.promptId;
272
283
  latestPromptId = entry.promptId;
273
- ensureGroup(currentPromptId, entry);
274
- } else if (currentPromptId) {
275
- ensureGroup(currentPromptId, entry);
276
- groupAllEntries.get(currentPromptId).push(entry);
284
+ } else if (!currentPromptId) {
285
+ continue;
277
286
  }
287
+ ensureGroup(currentPromptId, entry);
288
+ groupAllEntries.get(currentPromptId).push(entry);
278
289
  }
279
290
 
280
291
  if (latestPromptId) state.setLastPromptId(filePath, latestPromptId);