@ascenda-one/history-import 0.1.25 → 0.1.26
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 +89 -7
- package/dist/cli.js +189 -17
- package/dist/daySlice.js +15 -2
- package/dist/daySlice.js.map +1 -1
- package/dist/extractors/claudeCode.js +85 -20
- package/dist/extractors/claudeCode.js.map +1 -1
- package/dist/interruptedRuns.js +11 -0
- package/dist/interruptedRuns.js.map +1 -1
- package/dist/localHandoff.js +4 -0
- package/dist/localHandoff.js.map +1 -1
- package/dist/promptLedger.js +297 -7
- package/dist/promptLedger.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -152,6 +152,47 @@ line that closes the turn. The rule is in
|
|
|
152
152
|
[`src/interruptedRuns.ts`](./src/interruptedRuns.ts), and the desktop app's
|
|
153
153
|
importer counts by the same one.
|
|
154
154
|
|
|
155
|
+
A cut counts in the session you pressed Escape in, and in no other. Resuming
|
|
156
|
+
a session copies the history it inherits, markers included, so before this
|
|
157
|
+
rule every resume in a lineage reported its ancestors' interruptions as
|
|
158
|
+
well — a fifth of the counted total on the store this was measured against.
|
|
159
|
+
The same ownership rule as the minutes (`minutesBasis: owned_lines`).
|
|
160
|
+
|
|
161
|
+
### When two files both say a line is theirs
|
|
162
|
+
|
|
163
|
+
Most copies keep the session id the line was written with, which is how the
|
|
164
|
+
import tells a copy from the real thing. Some rewrite it to the file the copy
|
|
165
|
+
now sits in, keeping the line's own id and its original timestamp — so the
|
|
166
|
+
test that settles every other copy answers "written here" in both places, and
|
|
167
|
+
both sessions counted the line. Nothing on the line says which of the two
|
|
168
|
+
wrote it: the two records are identical apart from the fields the copy
|
|
169
|
+
rewrote.
|
|
170
|
+
|
|
171
|
+
Where the line sits is what settles it. A line two files claim is contested.
|
|
172
|
+
Inside a file, the run of contested lines at the top is the history it
|
|
173
|
+
inherited, and it ends at the first line that file claims and nobody else
|
|
174
|
+
does — from there on, what it holds it wrote. So a contested line belongs to
|
|
175
|
+
the file still holding it after its own history has started.
|
|
176
|
+
|
|
177
|
+
Not simply the first file the import reaches, which would be the easy rule and
|
|
178
|
+
the wrong one: on the store this was measured against it would hand two fifths
|
|
179
|
+
of these lines to the copy instead, which is the error this whole rule exists
|
|
180
|
+
to remove.
|
|
181
|
+
|
|
182
|
+
**What that leaves.** A lineage whose earliest file the 30-day cleanup has
|
|
183
|
+
already taken has no file that wrote those lines — every one still holding
|
|
184
|
+
them holds them in its inherited head, and nothing left on disk says which
|
|
185
|
+
session did the work. The line is still counted once, by the first file the
|
|
186
|
+
import reaches, the same answer any orphaned line gets. The total is right and
|
|
187
|
+
the session it lands on is a guess. That was 16.1% of contested lines here.
|
|
188
|
+
|
|
189
|
+
Sizes, on the same store: 3.7% of the lines claiming their own file were
|
|
190
|
+
claimed by two of them, and counting each once takes 3.9% off the summed
|
|
191
|
+
active minutes. **Your week doesn't move** — the union of your active time
|
|
192
|
+
holds to a minute, because a line counted twice was one minute counted twice.
|
|
193
|
+
Hands-on shifts a little more than that: a span changes sides when the lines
|
|
194
|
+
around it move, so the unioned hands-on figure moved by 19 minutes in 6,734.
|
|
195
|
+
|
|
155
196
|
### What counts as a prompt
|
|
156
197
|
|
|
157
198
|
`promptCount` on a Claude Code session counts the prompts you typed. Claude
|
|
@@ -180,13 +221,17 @@ test is `isTypedPromptLine` in
|
|
|
180
221
|
Two more rules need the whole store, so the extractor reads every transcript
|
|
181
222
|
once before it folds any session ([`src/promptLedger.ts`](./src/promptLedger.ts)):
|
|
182
223
|
|
|
183
|
-
- **Each
|
|
184
|
-
transcript that copies the history it inherited, line ids and timestamps
|
|
185
|
-
included.
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
224
|
+
- **Each line belongs to one session.** Resuming or forking a session writes a
|
|
225
|
+
new transcript that copies the history it inherited, line ids and timestamps
|
|
226
|
+
included. Each copy has one owner: the transcript the line's `sessionId`
|
|
227
|
+
names, or, where the purge took that file, the first in the walk's sorted
|
|
228
|
+
order. The owner counts the prompt, and the owner's timeline carries the
|
|
229
|
+
instant — so `activeMinutes`, the hands-on split, the day slices and
|
|
230
|
+
`startedAt` describe the session you resumed rather than everything behind
|
|
231
|
+
it. Two files can both name themselves on one line, which "When two files
|
|
232
|
+
both say a line is theirs" above settles. A line with no id is counted
|
|
233
|
+
wherever it appears; on the reference store that's `queue-operation` lines
|
|
234
|
+
and nothing else.
|
|
190
235
|
- **A chip's prompt isn't typed.** A session launched from a `spawn_task` chip
|
|
191
236
|
opens on the chip's prompt. When a line's text, wrappers stripped, matches a
|
|
192
237
|
chip that some session in the store offered, it counts in
|
|
@@ -202,6 +247,43 @@ typed. A handoff without the stamp counted every `user` line that wasn't a tool
|
|
|
202
247
|
result. Prompt counts for the same week run highest under that rule and lowest
|
|
203
248
|
under `typed_once`. The schema number doesn't move.
|
|
204
249
|
|
|
250
|
+
Beside it, `minutesBasis: "owned_lines"` says the minutes were cut the same
|
|
251
|
+
way. `"every_line"` — and an absent stamp — means a transcript's whole
|
|
252
|
+
contents counted there, inherited history included. Gate on this before
|
|
253
|
+
comparing minutes across two handoffs: a store's figures drop when it changes,
|
|
254
|
+
and nothing else in the file says so.
|
|
255
|
+
|
|
256
|
+
### How much history a resume carries
|
|
257
|
+
|
|
258
|
+
Enough to matter, which is why the rule above covers the minutes and not only
|
|
259
|
+
the counts. On one real store of 984 transcripts, 151 carried inherited lines:
|
|
260
|
+
275,801 of them, 29% of every dated line in the store. They held the whole of
|
|
261
|
+
the gap between what the sessions claimed and what the clock allowed. Both
|
|
262
|
+
columns are the same 983 sessions, read from one snapshot:
|
|
263
|
+
|
|
264
|
+
| | Every line | Owned lines |
|
|
265
|
+
|---|---|---|
|
|
266
|
+
| Summed active minutes | 70,212 | 53,600 |
|
|
267
|
+
| Summed hands-on minutes | 12,238 | 8,006 |
|
|
268
|
+
| Union of every active interval | 25,078 | 25,077 |
|
|
269
|
+
| Exact-duplicate spans | 262,500 of 873,599 | 21,982 of 635,126 |
|
|
270
|
+
| Sessions apparently spanning over a day | 158 | 154 |
|
|
271
|
+
|
|
272
|
+
The union is the control. It holds to the minute, so no work anyone did was
|
|
273
|
+
dropped: what went was the copy of it. Duplicate spans fell from 30% of the
|
|
274
|
+
store to 3.5%, which matters to any count of how many agents were running at
|
|
275
|
+
once, since a duplicate reads as a second agent. 127 sessions now start when
|
|
276
|
+
they were resumed, 13 of them more than an hour later than they used to.
|
|
277
|
+
|
|
278
|
+
That 3.5% was not the floor, and it was not coincidence: four fifths of it was
|
|
279
|
+
the copy path settled above. On a later and slightly larger snapshot of the
|
|
280
|
+
same store, the duplicate spans go from 21,982 to 4,087 once contested lines
|
|
281
|
+
are settled.
|
|
282
|
+
|
|
283
|
+
An earlier reading of this exposure put it at 0.72% of sessions. It counted
|
|
284
|
+
repeated `sessionRef`s, and a resumed transcript carries a fresh one, so it saw
|
|
285
|
+
three sessions where 151 transcripts were affected.
|
|
286
|
+
|
|
205
287
|
### Autonomy bands
|
|
206
288
|
|
|
207
289
|
`permissionMode` is on the transcript's human-prompt lines and nowhere else —
|
package/dist/cli.js
CHANGED
|
@@ -540,6 +540,10 @@ var require_metricKeys = __commonJS({
|
|
|
540
540
|
orphanedBubbles: { readBy: ["diagnostic"] },
|
|
541
541
|
orphanedSubagentBubbles: { readBy: ["diagnostic"] },
|
|
542
542
|
sessionsWithoutTimeline: { readBy: ["diagnostic"] },
|
|
543
|
+
sessionsWithOnlyInheritedLines: {
|
|
544
|
+
readBy: ["diagnostic"],
|
|
545
|
+
note: "Claude Code transcripts holding nothing but a copy of an ancestor's history, so no line's instant is theirs and no session is emitted for them. Excluded by rule, not a read failure: the work is in the transcript that owns it."
|
|
546
|
+
},
|
|
543
547
|
emptyComposers: { readBy: ["diagnostic"] },
|
|
544
548
|
apiErrorCount: { readBy: ["diagnostic"] },
|
|
545
549
|
assistantTurns: { readBy: ["diagnostic"] },
|
|
@@ -735,6 +739,16 @@ var require_out = __commonJS({
|
|
|
735
739
|
// `ai_prompt_submitted` closes. Neutral so a marker sent once per turn
|
|
736
740
|
// doesn't weigh on any workload leg.
|
|
737
741
|
ai_turn_completed: "neutral",
|
|
742
|
+
// The agent stopped and is waiting on the person: Claude Code's
|
|
743
|
+
// `Notification`, Codex's `PermissionRequest`. Supervision, beside the other
|
|
744
|
+
// oversight overhead, because that is what it is — time the person spends
|
|
745
|
+
// attending to the agent rather than to the work.
|
|
746
|
+
//
|
|
747
|
+
// Deliberately NOT risk. Risk is the leg that reads as something going
|
|
748
|
+
// wrong, and being asked is not a failure; classifying it there is the first
|
|
749
|
+
// step toward an interruption count becoming a score, a streak, or something
|
|
750
|
+
// that fires on a miss. It must never become any of those.
|
|
751
|
+
supervision_interruption: "supervision",
|
|
738
752
|
// Semantic (agent-observed) — see SEMANTIC_WORK_SIGNAL_EVENT_TYPES.
|
|
739
753
|
approach_churn_detected: "risk",
|
|
740
754
|
goal_drift_detected: "risk",
|
|
@@ -4385,7 +4399,6 @@ function localDayCounts(timestamps) {
|
|
|
4385
4399
|
}
|
|
4386
4400
|
function sliceSessionByLocalDay(timestamps, options = {}) {
|
|
4387
4401
|
const instants = timestamps.filter((t) => typeof t === "string" && t.length > 0).map((t) => new Date(t)).filter((d) => Number.isFinite(d.getTime())).sort((a, b) => a.getTime() - b.getTime());
|
|
4388
|
-
if (instants.length === 0) return [];
|
|
4389
4402
|
const prompts = /* @__PURE__ */ new Map();
|
|
4390
4403
|
for (const t of instants) {
|
|
4391
4404
|
const key = localDayKey(t);
|
|
@@ -4583,14 +4596,109 @@ var PromptLedger = class {
|
|
|
4583
4596
|
this.owners.set(uuid, null);
|
|
4584
4597
|
return true;
|
|
4585
4598
|
}
|
|
4599
|
+
/**
|
|
4600
|
+
* Hands a contested line to the transcript that wrote it.
|
|
4601
|
+
*
|
|
4602
|
+
* The ledger picks an owner by `sessionId` too, so a copy that rewrote it
|
|
4603
|
+
* reads as home here as well. That never counted a prompt twice — the id is
|
|
4604
|
+
* the key either way — but it could count it in the wrong session, whichever
|
|
4605
|
+
* of the two the walk reached first. `HomeClaims` settles the same question
|
|
4606
|
+
* for the timeline, and one answer serves both: a prompt and the instant it
|
|
4607
|
+
* happened at cannot belong to different sessions.
|
|
4608
|
+
*/
|
|
4609
|
+
applyContestedOwners(owners) {
|
|
4610
|
+
for (const [uuid, transcript] of owners) {
|
|
4611
|
+
if (!this.owners.has(uuid)) continue;
|
|
4612
|
+
this.owners.set(uuid, { transcript, home: true });
|
|
4613
|
+
}
|
|
4614
|
+
}
|
|
4586
4615
|
/** Distinct chip prompts seen, for tests and diagnostics. */
|
|
4587
4616
|
get chipPromptCount() {
|
|
4588
4617
|
return this.chipPrompts.size;
|
|
4589
4618
|
}
|
|
4590
4619
|
};
|
|
4620
|
+
var HomeClaims = class {
|
|
4621
|
+
/** Id to the first transcript that claimed it, in walk order. */
|
|
4622
|
+
firstClaim = /* @__PURE__ */ new Map();
|
|
4623
|
+
/** Id to every transcript claiming it, once a second one does. */
|
|
4624
|
+
contested = /* @__PURE__ */ new Map();
|
|
4625
|
+
/** Takes one line of the pre-read, in walk order. */
|
|
4626
|
+
observe(uuid, sessionId, transcript) {
|
|
4627
|
+
if (typeof uuid !== "string" || !isHomeTranscript(transcript, sessionId)) return;
|
|
4628
|
+
const first = this.firstClaim.get(uuid);
|
|
4629
|
+
if (first === void 0) {
|
|
4630
|
+
this.firstClaim.set(uuid, transcript);
|
|
4631
|
+
return;
|
|
4632
|
+
}
|
|
4633
|
+
if (first === transcript) return;
|
|
4634
|
+
const all = this.contested.get(uuid) ?? /* @__PURE__ */ new Set([first]);
|
|
4635
|
+
all.add(transcript);
|
|
4636
|
+
this.contested.set(uuid, all);
|
|
4637
|
+
}
|
|
4638
|
+
get contestedCount() {
|
|
4639
|
+
return this.contested.size;
|
|
4640
|
+
}
|
|
4641
|
+
/** Every transcript holding a contested id — the only ones worth re-reading. */
|
|
4642
|
+
transcriptsToClassify() {
|
|
4643
|
+
const out = /* @__PURE__ */ new Set();
|
|
4644
|
+
for (const files of this.contested.values()) for (const f of files) out.add(f);
|
|
4645
|
+
return [...out];
|
|
4646
|
+
}
|
|
4647
|
+
has(uuid) {
|
|
4648
|
+
return this.contested.has(uuid);
|
|
4649
|
+
}
|
|
4650
|
+
/** Every contested id, so each one can be given an owner. */
|
|
4651
|
+
contestedUuids() {
|
|
4652
|
+
return this.contested.keys();
|
|
4653
|
+
}
|
|
4654
|
+
/** The first transcript in walk order that claimed this id. */
|
|
4655
|
+
firstClaimOf(uuid) {
|
|
4656
|
+
return this.firstClaim.get(uuid);
|
|
4657
|
+
}
|
|
4658
|
+
};
|
|
4659
|
+
async function resolveContestedOwners(claims, walkOrder) {
|
|
4660
|
+
const owners = /* @__PURE__ */ new Map();
|
|
4661
|
+
const rank = new Map(walkOrder.map((file, i) => [file, i]));
|
|
4662
|
+
const toRead = claims.transcriptsToClassify().sort((a, b) => (rank.get(a) ?? 0) - (rank.get(b) ?? 0));
|
|
4663
|
+
for (const transcript of toRead) {
|
|
4664
|
+
let headOver = false;
|
|
4665
|
+
const own = [];
|
|
4666
|
+
try {
|
|
4667
|
+
await eachLine(transcript, (line) => {
|
|
4668
|
+
if (!line.includes('"uuid"')) return;
|
|
4669
|
+
let parsed;
|
|
4670
|
+
try {
|
|
4671
|
+
parsed = JSON.parse(line);
|
|
4672
|
+
} catch {
|
|
4673
|
+
return;
|
|
4674
|
+
}
|
|
4675
|
+
if (typeof parsed !== "object" || parsed === null) return;
|
|
4676
|
+
const record = parsed;
|
|
4677
|
+
if (typeof record.uuid !== "string") return;
|
|
4678
|
+
if (!isHomeTranscript(transcript, record.sessionId)) return;
|
|
4679
|
+
if (!claims.has(record.uuid)) {
|
|
4680
|
+
headOver = true;
|
|
4681
|
+
return;
|
|
4682
|
+
}
|
|
4683
|
+
if (headOver) own.push(record.uuid);
|
|
4684
|
+
});
|
|
4685
|
+
} catch {
|
|
4686
|
+
}
|
|
4687
|
+
for (const uuid of own) if (!owners.has(uuid)) owners.set(uuid, transcript);
|
|
4688
|
+
}
|
|
4689
|
+
for (const uuid of claims.contestedUuids()) {
|
|
4690
|
+
if (owners.has(uuid)) continue;
|
|
4691
|
+
const first = claims.firstClaimOf(uuid);
|
|
4692
|
+
if (first !== void 0) owners.set(uuid, first);
|
|
4693
|
+
}
|
|
4694
|
+
return owners;
|
|
4695
|
+
}
|
|
4591
4696
|
function mayMatter(line) {
|
|
4592
4697
|
return line.includes('"user"') || line.includes(SPAWN_TASK_TOOL_NAME);
|
|
4593
4698
|
}
|
|
4699
|
+
function mayClaim(line) {
|
|
4700
|
+
return line.includes('"uuid"');
|
|
4701
|
+
}
|
|
4594
4702
|
async function eachLine(transcript, onLine) {
|
|
4595
4703
|
const stream = createReadStream(transcript, { encoding: "utf8", highWaterMark: 1 << 20 });
|
|
4596
4704
|
let pending = [];
|
|
@@ -4609,10 +4717,12 @@ async function eachLine(transcript, onLine) {
|
|
|
4609
4717
|
}
|
|
4610
4718
|
async function readPromptLedger(mainTranscripts, subagentTranscripts, isToolResult) {
|
|
4611
4719
|
const ledger = new PromptLedger();
|
|
4720
|
+
const claims = new HomeClaims();
|
|
4612
4721
|
const read = async (transcript, isSidechain) => {
|
|
4613
4722
|
try {
|
|
4614
4723
|
await eachLine(transcript, (line) => {
|
|
4615
|
-
|
|
4724
|
+
const claiming = !isSidechain && mayClaim(line);
|
|
4725
|
+
if (!claiming && !mayMatter(line)) return;
|
|
4616
4726
|
let parsed;
|
|
4617
4727
|
try {
|
|
4618
4728
|
parsed = JSON.parse(line);
|
|
@@ -4620,15 +4730,59 @@ async function readPromptLedger(mainTranscripts, subagentTranscripts, isToolResu
|
|
|
4620
4730
|
return;
|
|
4621
4731
|
}
|
|
4622
4732
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return;
|
|
4623
|
-
|
|
4733
|
+
const record = parsed;
|
|
4734
|
+
if (claiming) claims.observe(record.uuid, record.sessionId, transcript);
|
|
4735
|
+
if (mayMatter(line)) {
|
|
4736
|
+
ledger.observe(record, transcript, { isSidechain, isToolResult });
|
|
4737
|
+
}
|
|
4624
4738
|
});
|
|
4625
4739
|
} catch {
|
|
4626
4740
|
}
|
|
4627
4741
|
};
|
|
4628
4742
|
for (const transcript of mainTranscripts) await read(transcript, false);
|
|
4629
4743
|
for (const transcript of subagentTranscripts) await read(transcript, true);
|
|
4630
|
-
return ledger;
|
|
4744
|
+
return { ledger, claims };
|
|
4631
4745
|
}
|
|
4746
|
+
var TimelineOwnership = class {
|
|
4747
|
+
/** Basenames of every main-thread transcript the walk listed. */
|
|
4748
|
+
transcriptIds;
|
|
4749
|
+
/** Ids of inherited lines whose home file is gone, claimed as they're met. */
|
|
4750
|
+
claimedOrphans = /* @__PURE__ */ new Set();
|
|
4751
|
+
/**
|
|
4752
|
+
* Ids two transcripts both claim as written in them, mapped to the one that
|
|
4753
|
+
* wrote it — see `HomeClaims`. Empty where no copy rewrote a `sessionId`,
|
|
4754
|
+
* which is most stores and every store this rule's first version was
|
|
4755
|
+
* measured on.
|
|
4756
|
+
*/
|
|
4757
|
+
contestedOwners;
|
|
4758
|
+
constructor(mainTranscripts, contestedOwners = /* @__PURE__ */ new Map()) {
|
|
4759
|
+
this.transcriptIds = new Set(mainTranscripts.map((file) => path4.basename(file, ".jsonl")));
|
|
4760
|
+
this.contestedOwners = contestedOwners;
|
|
4761
|
+
}
|
|
4762
|
+
/**
|
|
4763
|
+
* Whether this transcript's fold counts this line's instant. Called once per
|
|
4764
|
+
* known line, in the extractor's walk order — the orphan rule is first-come,
|
|
4765
|
+
* so a different order would hand a purged lineage's minutes to a different
|
|
4766
|
+
* fold.
|
|
4767
|
+
*/
|
|
4768
|
+
owns(line, transcript, opts) {
|
|
4769
|
+
if (opts.isSidechain) return true;
|
|
4770
|
+
if (line.sessionId === null || line.uuid === null) return true;
|
|
4771
|
+
if (isHomeTranscript(transcript, line.sessionId)) {
|
|
4772
|
+
const owner = this.contestedOwners.get(line.uuid);
|
|
4773
|
+
if (owner === void 0) return true;
|
|
4774
|
+
return owner === transcript;
|
|
4775
|
+
}
|
|
4776
|
+
if (this.transcriptIds.has(line.sessionId)) return false;
|
|
4777
|
+
if (this.claimedOrphans.has(line.uuid)) return false;
|
|
4778
|
+
this.claimedOrphans.add(line.uuid);
|
|
4779
|
+
return true;
|
|
4780
|
+
}
|
|
4781
|
+
/** Ids held for orphaned lines, for tests and diagnostics. */
|
|
4782
|
+
get orphanLineCount() {
|
|
4783
|
+
return this.claimedOrphans.size;
|
|
4784
|
+
}
|
|
4785
|
+
};
|
|
4632
4786
|
|
|
4633
4787
|
// src/extractors/claudeCode.ts
|
|
4634
4788
|
var KNOWN_CLAUDE_LINE_TYPES = [
|
|
@@ -4676,6 +4830,7 @@ function sniffClaudeLine(line) {
|
|
|
4676
4830
|
kind: type,
|
|
4677
4831
|
sourceVersion: typeof record.version === "string" ? record.version : null,
|
|
4678
4832
|
occurredAt: typeof record.timestamp === "string" ? record.timestamp : null,
|
|
4833
|
+
uuid: typeof record.uuid === "string" ? record.uuid : null,
|
|
4679
4834
|
sessionId: typeof record.sessionId === "string" ? record.sessionId : null,
|
|
4680
4835
|
model: message && typeof message.model === "string" ? message.model : null
|
|
4681
4836
|
};
|
|
@@ -4775,6 +4930,7 @@ function newFold(sessionId, projectSlug) {
|
|
|
4775
4930
|
toolCalls: [],
|
|
4776
4931
|
timelinePoints: [],
|
|
4777
4932
|
undatedTimelineLines: 0,
|
|
4933
|
+
inheritedTimelineLines: 0,
|
|
4778
4934
|
humanPromptTimestamps: [],
|
|
4779
4935
|
interruptedRuns: 0,
|
|
4780
4936
|
interruptedRunTimestamps: []
|
|
@@ -4834,11 +4990,12 @@ async function foldLinesInto(fold, filePath, opts) {
|
|
|
4834
4990
|
fold.unknownLines += 1;
|
|
4835
4991
|
continue;
|
|
4836
4992
|
}
|
|
4837
|
-
|
|
4993
|
+
const owned = opts.ownership.owns(sniffed, filePath, { isSidechain: opts.isSidechain });
|
|
4994
|
+
if (owned && !opts.isSidechain && sniffed.sessionId && fold.sessionId !== sniffed.sessionId) {
|
|
4838
4995
|
fold.sessionId = sniffed.sessionId;
|
|
4839
4996
|
}
|
|
4840
4997
|
if (sniffed.sourceVersion) fold.sourceVersion = sniffed.sourceVersion;
|
|
4841
|
-
if (sniffed.occurredAt) {
|
|
4998
|
+
if (owned && sniffed.occurredAt) {
|
|
4842
4999
|
if (!fold.firstTs || sniffed.occurredAt < fold.firstTs) fold.firstTs = sniffed.occurredAt;
|
|
4843
5000
|
if (!fold.lastTs || sniffed.occurredAt > fold.lastTs) fold.lastTs = sniffed.occurredAt;
|
|
4844
5001
|
}
|
|
@@ -4855,7 +5012,7 @@ async function foldLinesInto(fold, filePath, opts) {
|
|
|
4855
5012
|
if (!opts.isSidechain) {
|
|
4856
5013
|
const step = stepRun(turnRunning, record, isToolResultUserLine);
|
|
4857
5014
|
turnRunning = step.running;
|
|
4858
|
-
if (step.cut) {
|
|
5015
|
+
if (step.cut && owned) {
|
|
4859
5016
|
fold.interruptedRuns += 1;
|
|
4860
5017
|
fold.interruptedRunTimestamps.push(sniffed.occurredAt);
|
|
4861
5018
|
}
|
|
@@ -4966,7 +5123,9 @@ async function foldLinesInto(fold, filePath, opts) {
|
|
|
4966
5123
|
default:
|
|
4967
5124
|
break;
|
|
4968
5125
|
}
|
|
4969
|
-
if (sniffed.occurredAt) {
|
|
5126
|
+
if (sniffed.occurredAt && !owned) {
|
|
5127
|
+
fold.inheritedTimelineLines += 1;
|
|
5128
|
+
} else if (sniffed.occurredAt) {
|
|
4970
5129
|
const ms = Date.parse(sniffed.occurredAt);
|
|
4971
5130
|
if (Number.isFinite(ms)) {
|
|
4972
5131
|
fold.timelinePoints.push({
|
|
@@ -4984,9 +5143,9 @@ async function foldLinesInto(fold, filePath, opts) {
|
|
|
4984
5143
|
await handle.close();
|
|
4985
5144
|
}
|
|
4986
5145
|
}
|
|
4987
|
-
async function foldTranscript(filePath, projectSlug, ledger) {
|
|
5146
|
+
async function foldTranscript(filePath, projectSlug, ledger, ownership) {
|
|
4988
5147
|
const fold = newFold(path5.basename(filePath, ".jsonl"), projectSlug);
|
|
4989
|
-
await foldLinesInto(fold, filePath, { isSidechain: false, ledger });
|
|
5148
|
+
await foldLinesInto(fold, filePath, { isSidechain: false, ledger, ownership });
|
|
4990
5149
|
return fold;
|
|
4991
5150
|
}
|
|
4992
5151
|
async function walkJsonlFiles(root) {
|
|
@@ -5036,6 +5195,7 @@ async function* extractClaudeCode(snapshotDir, extractionId) {
|
|
|
5036
5195
|
let windowNewest = null;
|
|
5037
5196
|
let sessionCount = 0;
|
|
5038
5197
|
let projectsWithNoReadableTranscript = 0;
|
|
5198
|
+
let sessionsWithOnlyInheritedLines = 0;
|
|
5039
5199
|
const plan = [];
|
|
5040
5200
|
for (const slug of projectSlugs.sort()) {
|
|
5041
5201
|
const dir = path5.join(projectsDir, slug);
|
|
@@ -5059,17 +5219,21 @@ async function* extractClaudeCode(snapshotDir, extractionId) {
|
|
|
5059
5219
|
sessionDirs
|
|
5060
5220
|
});
|
|
5061
5221
|
}
|
|
5062
|
-
const
|
|
5063
|
-
|
|
5222
|
+
const mainTranscripts = plan.flatMap((p) => p.topLevelFiles.map((name) => path5.join(p.dir, name)));
|
|
5223
|
+
const { ledger, claims } = await readPromptLedger(
|
|
5224
|
+
mainTranscripts,
|
|
5064
5225
|
plan.flatMap((p) => p.sessionDirs.flatMap((d) => d.files)),
|
|
5065
5226
|
isToolResultUserLine
|
|
5066
5227
|
);
|
|
5228
|
+
const contestedOwners = await resolveContestedOwners(claims, mainTranscripts);
|
|
5229
|
+
ledger.applyContestedOwners(contestedOwners);
|
|
5230
|
+
const ownership = new TimelineOwnership(mainTranscripts, contestedOwners);
|
|
5067
5231
|
for (const { slug, dir, topLevelFiles, otherFiles, sessionDirs } of plan) {
|
|
5068
5232
|
let slugJsonlFiles = topLevelFiles.length;
|
|
5069
5233
|
let slugOtherFiles = otherFiles;
|
|
5070
5234
|
const foldsByKey = /* @__PURE__ */ new Map();
|
|
5071
5235
|
for (const name of topLevelFiles) {
|
|
5072
|
-
const fold = await foldTranscript(path5.join(dir, name), slug, ledger);
|
|
5236
|
+
const fold = await foldTranscript(path5.join(dir, name), slug, ledger, ownership);
|
|
5073
5237
|
foldsByKey.set(path5.basename(name, ".jsonl"), fold);
|
|
5074
5238
|
}
|
|
5075
5239
|
for (const { name: sessionDirName, files: nestedFiles, otherFiles: nestedOther } of sessionDirs) {
|
|
@@ -5083,12 +5247,15 @@ async function* extractClaudeCode(snapshotDir, extractionId) {
|
|
|
5083
5247
|
}
|
|
5084
5248
|
for (const nestedPath of nestedFiles) {
|
|
5085
5249
|
fold.subagentTranscripts += 1;
|
|
5086
|
-
await foldLinesInto(fold, nestedPath, { isSidechain: true, ledger });
|
|
5250
|
+
await foldLinesInto(fold, nestedPath, { isSidechain: true, ledger, ownership });
|
|
5087
5251
|
}
|
|
5088
5252
|
}
|
|
5089
5253
|
if (slugJsonlFiles === 0 && slugOtherFiles > 0) projectsWithNoReadableTranscript += 1;
|
|
5090
5254
|
for (const fold of foldsByKey.values()) {
|
|
5091
|
-
if (!fold.firstTs || !fold.lastTs)
|
|
5255
|
+
if (!fold.firstTs || !fold.lastTs) {
|
|
5256
|
+
if (fold.inheritedTimelineLines > 0) sessionsWithOnlyInheritedLines += 1;
|
|
5257
|
+
continue;
|
|
5258
|
+
}
|
|
5092
5259
|
sessionCount += 1;
|
|
5093
5260
|
if (!windowOldest || fold.firstTs < windowOldest) windowOldest = fold.firstTs;
|
|
5094
5261
|
if (!windowNewest || fold.lastTs > windowNewest) windowNewest = fold.lastTs;
|
|
@@ -5265,7 +5432,7 @@ async function* extractClaudeCode(snapshotDir, extractionId) {
|
|
|
5265
5432
|
}
|
|
5266
5433
|
}
|
|
5267
5434
|
}
|
|
5268
|
-
if (windowOldest && windowNewest || projectsWithNoReadableTranscript > 0) {
|
|
5435
|
+
if (windowOldest && windowNewest || projectsWithNoReadableTranscript > 0 || sessionsWithOnlyInheritedLines > 0) {
|
|
5269
5436
|
const window = windowOldest && windowNewest ? { windowOldest, windowNewest } : {};
|
|
5270
5437
|
yield {
|
|
5271
5438
|
// No window means nothing datable was read; the only honest timestamp
|
|
@@ -5279,7 +5446,8 @@ async function* extractClaudeCode(snapshotDir, extractionId) {
|
|
|
5279
5446
|
metrics: {
|
|
5280
5447
|
...window,
|
|
5281
5448
|
sessionCount,
|
|
5282
|
-
projectsWithNoReadableTranscript
|
|
5449
|
+
projectsWithNoReadableTranscript,
|
|
5450
|
+
sessionsWithOnlyInheritedLines
|
|
5283
5451
|
},
|
|
5284
5452
|
provenance: HISTORICAL_PROVENANCE.derived,
|
|
5285
5453
|
extractionId
|
|
@@ -7688,6 +7856,10 @@ function buildHandoff(events, extractionId, generatedAt) {
|
|
|
7688
7856
|
// and the prompts chips write, and counts each typed line in one session
|
|
7689
7857
|
// only, so `promptCount` below is typed prompts, once each.
|
|
7690
7858
|
promptBasis: "typed_once",
|
|
7859
|
+
// And the same lineage rule on the timeline: an inherited line's instant
|
|
7860
|
+
// belongs to the session it was written in, so the minutes below are this
|
|
7861
|
+
// session's own and `startedAt` is when it was resumed.
|
|
7862
|
+
minutesBasis: "owned_lines",
|
|
7691
7863
|
// And what each of those figures measures, keyed by the path a reader
|
|
7692
7864
|
// walks. The `projects` rollup below is why this store needs the stamp
|
|
7693
7865
|
// most: it carries the summed pair and the unioned pair under one
|
package/dist/daySlice.js
CHANGED
|
@@ -97,8 +97,21 @@ export function sliceSessionByLocalDay(timestamps, options = {}) {
|
|
|
97
97
|
.map((t) => new Date(t))
|
|
98
98
|
.filter((d) => Number.isFinite(d.getTime()))
|
|
99
99
|
.sort((a, b) => a.getTime() - b.getTime());
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
// No early return for an empty prompt list. There used to be one, and it
|
|
101
|
+
// ran before `activeInstants` and `interruptedRunTimestamps` were read, so a
|
|
102
|
+
// session with active time but no prompt of its own got no slices at all —
|
|
103
|
+
// silently, since the session's own `activeMinutes` were fine and only the
|
|
104
|
+
// placement of them vanished. Zero-prompt sessions are ordinary: one opened
|
|
105
|
+
// from a chip, or a resume whose prompts all belong to an ancestor. On the
|
|
106
|
+
// reference store 103 of 987 sessions have no prompt of their own, 97 of
|
|
107
|
+
// them hold active time, and 1,523 minutes were dropped from the day
|
|
108
|
+
// breakdown against 52,267 placed. Six cut-short runs went with them, which
|
|
109
|
+
// the option above promises a slice for "even with no prompt on it".
|
|
110
|
+
//
|
|
111
|
+
// Nothing replaces the guard, because nothing needs to: with no prompts, no
|
|
112
|
+
// instants and no cuts, `days` below is empty and the map returns `[]` on
|
|
113
|
+
// its own. "Nothing to place" is still an empty array, and it is now the
|
|
114
|
+
// answer to the whole question rather than to the first part of it.
|
|
102
115
|
const prompts = new Map();
|
|
103
116
|
for (const t of instants) {
|
|
104
117
|
const key = localDayKey(t);
|
package/dist/daySlice.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"daySlice.js","sourceRoot":"","sources":["../src/daySlice.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"daySlice.js","sourceRoot":"","sources":["../src/daySlice.ts"],"names":[],"mappings":"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"}
|