@alexgsdev/claude-code-transcript-export 0.1.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/LICENSE +21 -0
- package/README.md +134 -0
- package/dist/cli/args.d.ts +23 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +85 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +230 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +35 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/probe.d.ts +18 -0
- package/dist/cli/probe.d.ts.map +1 -0
- package/dist/cli/probe.js +83 -0
- package/dist/cli/probe.js.map +1 -0
- package/dist/commits.d.ts +31 -0
- package/dist/commits.d.ts.map +1 -0
- package/dist/commits.js +59 -0
- package/dist/commits.js.map +1 -0
- package/dist/config.d.ts +65 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +189 -0
- package/dist/config.js.map +1 -0
- package/dist/content.d.ts +50 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +141 -0
- package/dist/content.js.map +1 -0
- package/dist/discover.d.ts +55 -0
- package/dist/discover.d.ts.map +1 -0
- package/dist/discover.js +116 -0
- package/dist/discover.js.map +1 -0
- package/dist/extract.d.ts +47 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +127 -0
- package/dist/extract.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/paths.d.ts +31 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +47 -0
- package/dist/paths.js.map +1 -0
- package/dist/render/index.d.ts +21 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +68 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/transcript.d.ts +31 -0
- package/dist/render/transcript.d.ts.map +1 -0
- package/dist/render/transcript.js +54 -0
- package/dist/render/transcript.js.map +1 -0
- package/dist/session.d.ts +30 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +99 -0
- package/dist/session.js.map +1 -0
- package/dist/titles.d.ts +26 -0
- package/dist/titles.d.ts.map +1 -0
- package/dist/titles.js +51 -0
- package/dist/titles.js.map +1 -0
- package/dist/turns.d.ts +62 -0
- package/dist/turns.d.ts.map +1 -0
- package/dist/turns.js +108 -0
- package/dist/turns.js.map +1 -0
- package/dist/types.d.ts +95 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/dist/write.d.ts +61 -0
- package/dist/write.d.ts.map +1 -0
- package/dist/write.js +126 -0
- package/dist/write.js.map +1 -0
- package/package.json +64 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Commit } from '../commits.js';
|
|
2
|
+
import type { Session } from '../types.js';
|
|
3
|
+
export interface RenderTranscriptOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The `extracted:` date, `YYYY-MM-DD`.
|
|
6
|
+
*
|
|
7
|
+
* A parameter, never read from the clock here. The renderer is pure: given
|
|
8
|
+
* the same session and the same date it produces the same bytes, which is
|
|
9
|
+
* what lets the writer decide whether anything actually changed before
|
|
10
|
+
* stamping a new date. See specification §9.
|
|
11
|
+
*/
|
|
12
|
+
extractedOn: string;
|
|
13
|
+
/** Project root, recorded so a transcript says where it came from. */
|
|
14
|
+
project?: string;
|
|
15
|
+
/** Commits made while the session was open. Omitted when empty. */
|
|
16
|
+
commits?: Commit[];
|
|
17
|
+
}
|
|
18
|
+
/** The frontmatter block, as an object, before serialization. */
|
|
19
|
+
export declare function frontmatterFor(session: Session, options: RenderTranscriptOptions): Record<string, unknown>;
|
|
20
|
+
/** One session as markdown. Pure: no clock, no filesystem, no environment. */
|
|
21
|
+
export declare function renderTranscript(session: Session, options: RenderTranscriptOptions): string;
|
|
22
|
+
/**
|
|
23
|
+
* The `extracted:` value already on disk, or null.
|
|
24
|
+
*
|
|
25
|
+
* Scoped to the leading frontmatter block, never applied to the body. A
|
|
26
|
+
* transcript body is arbitrary prose that may well contain `extracted:` at the
|
|
27
|
+
* start of a line — quoted frontmatter, a discussion of this very field — and a
|
|
28
|
+
* body-wide search would read the wrong one.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readExtractedDate(existing: string): string | null;
|
|
31
|
+
//# sourceMappingURL=transcript.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript.d.ts","sourceRoot":"","sources":["../../src/render/transcript.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,uBAAuB;IACtC;;;;;;;OAOG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAC5B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,uBAAuB,GAC/B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqBzB;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,uBAAuB,GAC/B,MAAM,CASR;AAMD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOjE"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { stringify as stringifyYaml } from 'yaml';
|
|
2
|
+
/** The frontmatter block, as an object, before serialization. */
|
|
3
|
+
export function frontmatterFor(session, options) {
|
|
4
|
+
const commits = options.commits ?? [];
|
|
5
|
+
return {
|
|
6
|
+
title: session.title,
|
|
7
|
+
session_id: session.id,
|
|
8
|
+
...(options.project !== undefined ? { project: options.project } : {}),
|
|
9
|
+
source: 'claude-code',
|
|
10
|
+
created: session.created,
|
|
11
|
+
updated: session.updated,
|
|
12
|
+
turns: session.turns.length,
|
|
13
|
+
extracted: options.extractedOn,
|
|
14
|
+
...(session.kind !== undefined ? { kind: session.kind } : {}),
|
|
15
|
+
...(session.continuedIn !== undefined ? { continued_in: session.continuedIn } : {}),
|
|
16
|
+
...(session.continues !== undefined ? { continues: session.continues } : {}),
|
|
17
|
+
// `<hash> <subject>` so the hash is greppable: `grep -l <hash> *.md` runs
|
|
18
|
+
// the link the other way.
|
|
19
|
+
...(commits.length > 0
|
|
20
|
+
? { commits_in_window: commits.map((c) => `${c.hash} ${c.subject}`) }
|
|
21
|
+
: {}),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** One session as markdown. Pure: no clock, no filesystem, no environment. */
|
|
25
|
+
export function renderTranscript(session, options) {
|
|
26
|
+
const frontmatter = stringifyYaml(frontmatterFor(session, options));
|
|
27
|
+
const parts = [`---\n${frontmatter}---`, '', `# ${session.title}`];
|
|
28
|
+
session.turns.forEach((turn, index) => {
|
|
29
|
+
parts.push('', `## [${index + 1}] ${turn.speaker}`, '', turn.text);
|
|
30
|
+
});
|
|
31
|
+
return `${parts.join('\n').replace(/\n{3,}$/, '\n')}\n`;
|
|
32
|
+
}
|
|
33
|
+
/** The leading `---\n...\n---\n` block, and only the leading one. */
|
|
34
|
+
const FRONTMATTER = /^---\n([\s\S]*?\n)---\n/;
|
|
35
|
+
const EXTRACTED_LINE = /^extracted:.*$/m;
|
|
36
|
+
/**
|
|
37
|
+
* The `extracted:` value already on disk, or null.
|
|
38
|
+
*
|
|
39
|
+
* Scoped to the leading frontmatter block, never applied to the body. A
|
|
40
|
+
* transcript body is arbitrary prose that may well contain `extracted:` at the
|
|
41
|
+
* start of a line — quoted frontmatter, a discussion of this very field — and a
|
|
42
|
+
* body-wide search would read the wrong one.
|
|
43
|
+
*/
|
|
44
|
+
export function readExtractedDate(existing) {
|
|
45
|
+
const block = FRONTMATTER.exec(existing)?.[1];
|
|
46
|
+
if (block === undefined)
|
|
47
|
+
return null;
|
|
48
|
+
const line = EXTRACTED_LINE.exec(block)?.[0];
|
|
49
|
+
if (line === undefined)
|
|
50
|
+
return null;
|
|
51
|
+
const value = line.slice('extracted:'.length).trim();
|
|
52
|
+
return value === '' ? null : value;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=transcript.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript.js","sourceRoot":"","sources":["../../src/render/transcript.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { ContentPolicy, RawRecord, Session } from './types.js';
|
|
2
|
+
export interface ReadSessionOptions {
|
|
3
|
+
policy?: ContentPolicy;
|
|
4
|
+
/** Used when no record carries a session id. */
|
|
5
|
+
fallbackId?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Records to a `Session`.
|
|
9
|
+
*
|
|
10
|
+
* Timestamps are kept at full precision alongside the local dates, because the
|
|
11
|
+
* commit window needs to separate two sessions held on the same day.
|
|
12
|
+
*/
|
|
13
|
+
export declare function readSession(records: RawRecord[], options?: ReadSessionOptions): Session;
|
|
14
|
+
/**
|
|
15
|
+
* Fills in each session's `continues` — the reverse of `continuedIn`.
|
|
16
|
+
*
|
|
17
|
+
* Computed across the run rather than read from a record, because no record
|
|
18
|
+
* points backwards. A long piece of work spans several session files, and
|
|
19
|
+
* without both directions the transcript for the first half simply stops
|
|
20
|
+
* mid-thought with nothing saying where it went.
|
|
21
|
+
*
|
|
22
|
+
* Mutates in place and returns the same array, since the sessions were just
|
|
23
|
+
* built and nothing else holds them yet.
|
|
24
|
+
*/
|
|
25
|
+
export declare function linkContinuations(sessions: Session[]): Session[];
|
|
26
|
+
/** `<date>--<slug>--<id8>.md`. */
|
|
27
|
+
export declare function sessionFilename(session: Session, slugify: (s: string) => string): string;
|
|
28
|
+
/** A log's filename without its extension, for use as a fallback session id. */
|
|
29
|
+
export declare function fallbackIdFor(path: string): string;
|
|
30
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,OAAO,EAAgB,MAAM,YAAY,CAAC;AAkBlF,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,gDAAgD;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,SAAS,EAAE,EACpB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CA2CT;AAYD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAUhE;AAED,kCAAkC;AAClC,wBAAgB,eAAe,CAC7B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,GAC7B,MAAM,CAER;AAED,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
import { recordText } from './content.js';
|
|
3
|
+
import { resolveTitle, toLocalDate } from './titles.js';
|
|
4
|
+
import { buildTurns } from './turns.js';
|
|
5
|
+
/** The last non-empty value of a field across records of one type. */
|
|
6
|
+
function lastOf(records, type, field) {
|
|
7
|
+
let found;
|
|
8
|
+
for (const record of records) {
|
|
9
|
+
if (record.type === type && typeof record[field] === 'string') {
|
|
10
|
+
const value = record[field].trim();
|
|
11
|
+
if (value !== '')
|
|
12
|
+
found = value;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return found;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Records to a `Session`.
|
|
19
|
+
*
|
|
20
|
+
* Timestamps are kept at full precision alongside the local dates, because the
|
|
21
|
+
* commit window needs to separate two sessions held on the same day.
|
|
22
|
+
*/
|
|
23
|
+
export function readSession(records, options = {}) {
|
|
24
|
+
const conversational = records.filter((record) => record.type === 'user' || record.type === 'assistant');
|
|
25
|
+
const stamps = records
|
|
26
|
+
.map((record) => record.timestamp)
|
|
27
|
+
.filter((stamp) => typeof stamp === 'string')
|
|
28
|
+
.sort();
|
|
29
|
+
const id = records.find((record) => typeof record.sessionId === 'string')?.sessionId ??
|
|
30
|
+
options.fallbackId ??
|
|
31
|
+
'unknown';
|
|
32
|
+
const cwds = [];
|
|
33
|
+
for (const record of records) {
|
|
34
|
+
for (const dir of [record.cwd, record.relocatedCwd]) {
|
|
35
|
+
if (typeof dir === 'string' && dir !== '' && !cwds.includes(dir))
|
|
36
|
+
cwds.push(dir);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const images = [];
|
|
40
|
+
const context = {
|
|
41
|
+
policy: options.policy,
|
|
42
|
+
sessionId8: id.slice(0, 8),
|
|
43
|
+
collected: images,
|
|
44
|
+
};
|
|
45
|
+
return {
|
|
46
|
+
id,
|
|
47
|
+
title: resolveTitle(records),
|
|
48
|
+
created: toLocalDate(stamps[0]),
|
|
49
|
+
updated: toLocalDate(stamps[stamps.length - 1]),
|
|
50
|
+
startedAt: stamps[0],
|
|
51
|
+
endedAt: stamps[stamps.length - 1],
|
|
52
|
+
kind: lastOf(records, 'attachment', 'sessionKind') ?? findKind(records),
|
|
53
|
+
continuedIn: lastOf(records, 'continued-in', 'continuedInSessionId'),
|
|
54
|
+
cwds,
|
|
55
|
+
turns: buildTurns(records, context),
|
|
56
|
+
images,
|
|
57
|
+
rawMessages: conversational.length,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** `sessionKind` rides on ordinary records, not on a record type of its own. */
|
|
61
|
+
function findKind(records) {
|
|
62
|
+
for (const record of records) {
|
|
63
|
+
if (typeof record.sessionKind === 'string' && record.sessionKind !== '') {
|
|
64
|
+
return record.sessionKind;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Fills in each session's `continues` — the reverse of `continuedIn`.
|
|
71
|
+
*
|
|
72
|
+
* Computed across the run rather than read from a record, because no record
|
|
73
|
+
* points backwards. A long piece of work spans several session files, and
|
|
74
|
+
* without both directions the transcript for the first half simply stops
|
|
75
|
+
* mid-thought with nothing saying where it went.
|
|
76
|
+
*
|
|
77
|
+
* Mutates in place and returns the same array, since the sessions were just
|
|
78
|
+
* built and nothing else holds them yet.
|
|
79
|
+
*/
|
|
80
|
+
export function linkContinuations(sessions) {
|
|
81
|
+
const byId = new Map(sessions.map((session) => [session.id, session]));
|
|
82
|
+
for (const session of sessions) {
|
|
83
|
+
if (session.continuedIn === undefined)
|
|
84
|
+
continue;
|
|
85
|
+
const next = byId.get(session.continuedIn);
|
|
86
|
+
if (next !== undefined)
|
|
87
|
+
next.continues = session.id;
|
|
88
|
+
}
|
|
89
|
+
return sessions;
|
|
90
|
+
}
|
|
91
|
+
/** `<date>--<slug>--<id8>.md`. */
|
|
92
|
+
export function sessionFilename(session, slugify) {
|
|
93
|
+
return `${session.created}--${slugify(session.title)}--${session.id.slice(0, 8)}.md`;
|
|
94
|
+
}
|
|
95
|
+
/** A log's filename without its extension, for use as a fallback session id. */
|
|
96
|
+
export function fallbackIdFor(path) {
|
|
97
|
+
return basename(path, '.jsonl');
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
package/dist/titles.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { RawRecord } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A session's title, by precedence: `custom-title`, then `ai-title`, then
|
|
4
|
+
* `agent-name`.
|
|
5
|
+
*
|
|
6
|
+
* Custom wins because it is the name a human deliberately set, and a session
|
|
7
|
+
* the author renamed should not be filed under the model's name for it. Within
|
|
8
|
+
* each kind the last record wins: several accumulate as a session is renamed
|
|
9
|
+
* mid-flight, and the last one is the name the session ended up with.
|
|
10
|
+
*
|
|
11
|
+
* `agent-name` is the last resort — it is a background job's name, present in
|
|
12
|
+
* 11 of 15 background logs in the observed corpus and rescuing one that has no
|
|
13
|
+
* `ai-title` at all.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveTitle(records: RawRecord[], fallback?: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* A timestamp as `YYYY-MM-DD` in **local time**.
|
|
18
|
+
*
|
|
19
|
+
* Local, not UTC, and the difference is not cosmetic. A session beginning
|
|
20
|
+
* 2026-08-25T00:13Z is 17:13 on 2026-08-24 where the author was sitting, and
|
|
21
|
+
* every other date a project cites — git commits above all — is local. Filed by
|
|
22
|
+
* UTC it lands a day after the commit it produced, and a reader chasing "the
|
|
23
|
+
* 8/24 session" finds nothing there.
|
|
24
|
+
*/
|
|
25
|
+
export declare function toLocalDate(timestamp: string | undefined): string;
|
|
26
|
+
//# sourceMappingURL=titles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"titles.d.ts","sourceRoot":"","sources":["../src/titles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,SAAS,EAAE,EACpB,QAAQ,SAAqB,GAC5B,MAAM,CAkBR;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAQjE"}
|
package/dist/titles.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A session's title, by precedence: `custom-title`, then `ai-title`, then
|
|
3
|
+
* `agent-name`.
|
|
4
|
+
*
|
|
5
|
+
* Custom wins because it is the name a human deliberately set, and a session
|
|
6
|
+
* the author renamed should not be filed under the model's name for it. Within
|
|
7
|
+
* each kind the last record wins: several accumulate as a session is renamed
|
|
8
|
+
* mid-flight, and the last one is the name the session ended up with.
|
|
9
|
+
*
|
|
10
|
+
* `agent-name` is the last resort — it is a background job's name, present in
|
|
11
|
+
* 11 of 15 background logs in the observed corpus and rescuing one that has no
|
|
12
|
+
* `ai-title` at all.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveTitle(records, fallback = 'Untitled session') {
|
|
15
|
+
const last = (type, field) => {
|
|
16
|
+
let found;
|
|
17
|
+
for (const record of records) {
|
|
18
|
+
if (record.type === type && typeof record[field] === 'string') {
|
|
19
|
+
const value = record[field].trim();
|
|
20
|
+
if (value !== '')
|
|
21
|
+
found = value;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return found;
|
|
25
|
+
};
|
|
26
|
+
return (last('custom-title', 'customTitle') ??
|
|
27
|
+
last('ai-title', 'aiTitle') ??
|
|
28
|
+
last('agent-name', 'agentName') ??
|
|
29
|
+
fallback);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A timestamp as `YYYY-MM-DD` in **local time**.
|
|
33
|
+
*
|
|
34
|
+
* Local, not UTC, and the difference is not cosmetic. A session beginning
|
|
35
|
+
* 2026-08-25T00:13Z is 17:13 on 2026-08-24 where the author was sitting, and
|
|
36
|
+
* every other date a project cites — git commits above all — is local. Filed by
|
|
37
|
+
* UTC it lands a day after the commit it produced, and a reader chasing "the
|
|
38
|
+
* 8/24 session" finds nothing there.
|
|
39
|
+
*/
|
|
40
|
+
export function toLocalDate(timestamp) {
|
|
41
|
+
if (!timestamp)
|
|
42
|
+
return 'unknown';
|
|
43
|
+
const parsed = new Date(timestamp);
|
|
44
|
+
if (Number.isNaN(parsed.getTime()))
|
|
45
|
+
return 'unknown';
|
|
46
|
+
const year = parsed.getFullYear();
|
|
47
|
+
const month = `${parsed.getMonth() + 1}`.padStart(2, '0');
|
|
48
|
+
const day = `${parsed.getDate()}`.padStart(2, '0');
|
|
49
|
+
return `${year}-${month}-${day}`;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=titles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"titles.js","sourceRoot":"","sources":["../src/titles.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAC1B,OAAoB,EACpB,QAAQ,GAAG,kBAAkB;IAE7B,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,KAAsB,EAAsB,EAAE;QACxE,IAAI,KAAyB,CAAC;QAC9B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,IAAI,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;gBAC9D,MAAM,KAAK,GAAI,MAAM,CAAC,KAAK,CAAY,CAAC,IAAI,EAAE,CAAC;gBAC/C,IAAI,KAAK,KAAK,EAAE;oBAAE,KAAK,GAAG,KAAK,CAAC;YAClC,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,OAAO,CACL,IAAI,CAAC,cAAc,EAAE,aAAa,CAAC;QACnC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC;QAC3B,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC;QAC/B,QAAQ,CACT,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAC,SAA6B;IACvD,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IACjC,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC;IACnC,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,SAAS,CAAC;IACrD,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,GAAG,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1D,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnD,OAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC;AACnC,CAAC"}
|
package/dist/turns.d.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { RawRecord, SessionTurn } from './types.js';
|
|
2
|
+
import type { RenderContext as Ctx } from './content.js';
|
|
3
|
+
/**
|
|
4
|
+
* True for a `user` record that is the author actually speaking.
|
|
5
|
+
*
|
|
6
|
+
* This is the load-bearing predicate in the package. Across the corpus this
|
|
7
|
+
* was written against, 778 of 10832 `user` records are human turns — 7.2%. The
|
|
8
|
+
* rest are tool results the harness feeds back and injected context. Numbering
|
|
9
|
+
* those as Human turns would inflate a transcript fourteenfold and destroy the
|
|
10
|
+
* property that makes citation cheap: that turns alternate, so the speaker of
|
|
11
|
+
* turn N is known from N alone.
|
|
12
|
+
*
|
|
13
|
+
* `isSidechain` is checked here as a second, independent defense. Subagent
|
|
14
|
+
* records carry it as `true` and live in a nested directory the scan does not
|
|
15
|
+
* descend into (specification §4.4); this catches them if one ever arrives by
|
|
16
|
+
* another route.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isHumanTurn(record: RawRecord): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Records to turns, coalescing each run of assistant records into one.
|
|
21
|
+
*
|
|
22
|
+
* A single reply in Claude Code is spread across many records — text, a tool
|
|
23
|
+
* call, its result, more text — where a chat message is one message. The unit
|
|
24
|
+
* here is the *exchange*, which is what a citation wants to name anyway.
|
|
25
|
+
*
|
|
26
|
+
* The cost, stated plainly: these turn numbers are stable only while this
|
|
27
|
+
* filter is stable. A change to `isHumanTurn` renumbers every transcript.
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildTurns(records: RawRecord[], context?: Ctx): SessionTurn[];
|
|
30
|
+
/** What a session's turn sequence actually looks like, as measured. */
|
|
31
|
+
export interface ParityReport {
|
|
32
|
+
/** Turns counted, for aggregating across sessions. */
|
|
33
|
+
turns: number;
|
|
34
|
+
/** False when a session opens with an assistant turn. */
|
|
35
|
+
startsWithHuman: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Adjacent turns sharing a speaker — the places alternation genuinely
|
|
38
|
+
* breaks.
|
|
39
|
+
*/
|
|
40
|
+
alternationBreaks: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Measures alternation rather than assuming it.
|
|
44
|
+
*
|
|
45
|
+
* "Odd turns are the author" is exactly the kind of plausible statement a
|
|
46
|
+
* corpus gets burned by, so it is checked on generation. Across the corpus this
|
|
47
|
+
* was written against it is false: 13 alternation breaks across 9 of 74
|
|
48
|
+
* sessions, plus one session that opens with an assistant turn.
|
|
49
|
+
*
|
|
50
|
+
* **Counted as adjacent same-speaker pairs, not as index parity.** The obvious
|
|
51
|
+
* implementation — compare each turn against `index % 2` — is shift-invariant
|
|
52
|
+
* in the wrong direction: one break early in a long session flips every turn
|
|
53
|
+
* after it, so the same 13 real breaks report as 172. That number is not wrong
|
|
54
|
+
* so much as meaningless, and putting it in a generated index would be worse
|
|
55
|
+
* than saying nothing.
|
|
56
|
+
*/
|
|
57
|
+
export declare function parityReport(turns: SessionTurn[]): ParityReport;
|
|
58
|
+
/** Sums per-session reports for the index. */
|
|
59
|
+
export declare function aggregateParity(reports: ParityReport[]): ParityReport & {
|
|
60
|
+
sessionsNotStartingWithHuman: number;
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=turns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turns.d.ts","sourceRoot":"","sources":["../src/turns.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,IAAI,GAAG,EAAE,MAAM,cAAc,CAAC;AAEzD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAatD;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,EAAE,OAAO,GAAE,GAAQ,GAAG,WAAW,EAAE,CA4BjF;AAED,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,eAAe,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,YAAY,CAW/D;AAED,8CAA8C;AAC9C,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,YAAY,GAAG;IACvE,4BAA4B,EAAE,MAAM,CAAC;CACtC,CAOA"}
|
package/dist/turns.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { recordText, stripHarnessTags } from './content.js';
|
|
2
|
+
/**
|
|
3
|
+
* True for a `user` record that is the author actually speaking.
|
|
4
|
+
*
|
|
5
|
+
* This is the load-bearing predicate in the package. Across the corpus this
|
|
6
|
+
* was written against, 778 of 10832 `user` records are human turns — 7.2%. The
|
|
7
|
+
* rest are tool results the harness feeds back and injected context. Numbering
|
|
8
|
+
* those as Human turns would inflate a transcript fourteenfold and destroy the
|
|
9
|
+
* property that makes citation cheap: that turns alternate, so the speaker of
|
|
10
|
+
* turn N is known from N alone.
|
|
11
|
+
*
|
|
12
|
+
* `isSidechain` is checked here as a second, independent defense. Subagent
|
|
13
|
+
* records carry it as `true` and live in a nested directory the scan does not
|
|
14
|
+
* descend into (specification §4.4); this catches them if one ever arrives by
|
|
15
|
+
* another route.
|
|
16
|
+
*/
|
|
17
|
+
export function isHumanTurn(record) {
|
|
18
|
+
if (record.type !== 'user')
|
|
19
|
+
return false;
|
|
20
|
+
if (record.isMeta)
|
|
21
|
+
return false;
|
|
22
|
+
if (record.isSidechain)
|
|
23
|
+
return false;
|
|
24
|
+
const content = record.message?.content;
|
|
25
|
+
if (typeof content === 'string')
|
|
26
|
+
return stripHarnessTags(content) !== '';
|
|
27
|
+
if (!Array.isArray(content))
|
|
28
|
+
return false;
|
|
29
|
+
const types = content.map((block) => block?.type);
|
|
30
|
+
if (types.length > 0 && types.every((type) => type === 'tool_result'))
|
|
31
|
+
return false;
|
|
32
|
+
return stripHarnessTags(recordText(record)) !== '';
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Records to turns, coalescing each run of assistant records into one.
|
|
36
|
+
*
|
|
37
|
+
* A single reply in Claude Code is spread across many records — text, a tool
|
|
38
|
+
* call, its result, more text — where a chat message is one message. The unit
|
|
39
|
+
* here is the *exchange*, which is what a citation wants to name anyway.
|
|
40
|
+
*
|
|
41
|
+
* The cost, stated plainly: these turn numbers are stable only while this
|
|
42
|
+
* filter is stable. A change to `isHumanTurn` renumbers every transcript.
|
|
43
|
+
*/
|
|
44
|
+
export function buildTurns(records, context = {}) {
|
|
45
|
+
const turns = [];
|
|
46
|
+
let assistantBuffer = [];
|
|
47
|
+
const flush = () => {
|
|
48
|
+
const text = assistantBuffer.join('\n\n').trim();
|
|
49
|
+
if (text)
|
|
50
|
+
turns.push({ speaker: 'Assistant', text });
|
|
51
|
+
assistantBuffer = [];
|
|
52
|
+
};
|
|
53
|
+
for (const record of records) {
|
|
54
|
+
if (record.isSidechain)
|
|
55
|
+
continue;
|
|
56
|
+
if (isHumanTurn(record)) {
|
|
57
|
+
flush();
|
|
58
|
+
const text = stripHarnessTags(recordText(record, context));
|
|
59
|
+
if (text)
|
|
60
|
+
turns.push({ speaker: 'Human', text });
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (record.type === 'assistant' && !record.isMeta) {
|
|
64
|
+
const text = recordText(record, context);
|
|
65
|
+
if (text)
|
|
66
|
+
assistantBuffer.push(text);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
flush();
|
|
70
|
+
return turns;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Measures alternation rather than assuming it.
|
|
74
|
+
*
|
|
75
|
+
* "Odd turns are the author" is exactly the kind of plausible statement a
|
|
76
|
+
* corpus gets burned by, so it is checked on generation. Across the corpus this
|
|
77
|
+
* was written against it is false: 13 alternation breaks across 9 of 74
|
|
78
|
+
* sessions, plus one session that opens with an assistant turn.
|
|
79
|
+
*
|
|
80
|
+
* **Counted as adjacent same-speaker pairs, not as index parity.** The obvious
|
|
81
|
+
* implementation — compare each turn against `index % 2` — is shift-invariant
|
|
82
|
+
* in the wrong direction: one break early in a long session flips every turn
|
|
83
|
+
* after it, so the same 13 real breaks report as 172. That number is not wrong
|
|
84
|
+
* so much as meaningless, and putting it in a generated index would be worse
|
|
85
|
+
* than saying nothing.
|
|
86
|
+
*/
|
|
87
|
+
export function parityReport(turns) {
|
|
88
|
+
let alternationBreaks = 0;
|
|
89
|
+
for (let i = 1; i < turns.length; i += 1) {
|
|
90
|
+
if (turns[i]?.speaker === turns[i - 1]?.speaker)
|
|
91
|
+
alternationBreaks += 1;
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
turns: turns.length,
|
|
95
|
+
startsWithHuman: turns.length === 0 || turns[0]?.speaker === 'Human',
|
|
96
|
+
alternationBreaks,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/** Sums per-session reports for the index. */
|
|
100
|
+
export function aggregateParity(reports) {
|
|
101
|
+
return {
|
|
102
|
+
turns: reports.reduce((n, r) => n + r.turns, 0),
|
|
103
|
+
startsWithHuman: reports.every((r) => r.startsWithHuman),
|
|
104
|
+
alternationBreaks: reports.reduce((n, r) => n + r.alternationBreaks, 0),
|
|
105
|
+
sessionsNotStartingWithHuman: reports.filter((r) => !r.startsWithHuman).length,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=turns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turns.js","sourceRoot":"","sources":["../src/turns.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the Claude Code JSONL log format.
|
|
3
|
+
*
|
|
4
|
+
* Every field is optional. The schema is undocumented, observed rather than
|
|
5
|
+
* guaranteed, and has drifted before: see the specification's §3.1 for two
|
|
6
|
+
* changes that reversed design decisions during drafting. Nothing here should
|
|
7
|
+
* be read as a promise about what a record contains.
|
|
8
|
+
*/
|
|
9
|
+
/** One content block inside a message. */
|
|
10
|
+
export interface RawBlock {
|
|
11
|
+
type?: string;
|
|
12
|
+
text?: string;
|
|
13
|
+
name?: string;
|
|
14
|
+
input?: unknown;
|
|
15
|
+
content?: unknown;
|
|
16
|
+
source?: {
|
|
17
|
+
type?: string;
|
|
18
|
+
media_type?: string;
|
|
19
|
+
data?: string;
|
|
20
|
+
};
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
/** One JSONL line. */
|
|
24
|
+
export interface RawRecord {
|
|
25
|
+
type?: string;
|
|
26
|
+
uuid?: string;
|
|
27
|
+
timestamp?: string;
|
|
28
|
+
sessionId?: string;
|
|
29
|
+
isMeta?: boolean;
|
|
30
|
+
isSidechain?: boolean;
|
|
31
|
+
sessionKind?: string;
|
|
32
|
+
cwd?: string;
|
|
33
|
+
gitBranch?: string;
|
|
34
|
+
version?: string;
|
|
35
|
+
/** `ai-title` records: the model's name for the session. */
|
|
36
|
+
aiTitle?: string;
|
|
37
|
+
/** `custom-title` records: the name a human set, which wins. */
|
|
38
|
+
customTitle?: string;
|
|
39
|
+
/** `agent-name` records: a background job's name. Last-resort title. */
|
|
40
|
+
agentName?: string;
|
|
41
|
+
/** `continued-in` records: the session this one was continued into. */
|
|
42
|
+
continuedInSessionId?: string;
|
|
43
|
+
/** `relocated` records: a working directory the session moved to. */
|
|
44
|
+
relocatedCwd?: string;
|
|
45
|
+
message?: {
|
|
46
|
+
role?: string;
|
|
47
|
+
content?: RawBlock[] | string;
|
|
48
|
+
};
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
export type Speaker = 'Human' | 'Assistant';
|
|
52
|
+
export interface SessionTurn {
|
|
53
|
+
speaker: Speaker;
|
|
54
|
+
text: string;
|
|
55
|
+
}
|
|
56
|
+
/** An image block lifted out of a transcript, ready to be written. */
|
|
57
|
+
export interface SessionImage {
|
|
58
|
+
/** `<id8>-<hash8>.<ext>`. Content-addressed, so reruns never duplicate. */
|
|
59
|
+
filename: string;
|
|
60
|
+
mediaType: string;
|
|
61
|
+
/** Decoded bytes. The writer is responsible for putting them on disk. */
|
|
62
|
+
data: Buffer;
|
|
63
|
+
}
|
|
64
|
+
export interface Session {
|
|
65
|
+
id: string;
|
|
66
|
+
title: string;
|
|
67
|
+
/** Local-time `YYYY-MM-DD`. */
|
|
68
|
+
created: string;
|
|
69
|
+
updated: string;
|
|
70
|
+
/** First and last record timestamps, full precision. */
|
|
71
|
+
startedAt?: string;
|
|
72
|
+
endedAt?: string;
|
|
73
|
+
/** `sessionKind`, e.g. `bg`. Recorded because it is true; drives nothing. */
|
|
74
|
+
kind?: string;
|
|
75
|
+
/** From a `continued-in` record. */
|
|
76
|
+
continuedIn?: string;
|
|
77
|
+
/** Computed across the run, not read from a record. */
|
|
78
|
+
continues?: string;
|
|
79
|
+
/** Working directories seen, including relocations. */
|
|
80
|
+
cwds: string[];
|
|
81
|
+
turns: SessionTurn[];
|
|
82
|
+
images: SessionImage[];
|
|
83
|
+
/** user/assistant records seen before filtering. Reported, not rendered. */
|
|
84
|
+
rawMessages: number;
|
|
85
|
+
}
|
|
86
|
+
export type ToolPolicy = 'strip' | 'keep';
|
|
87
|
+
export type ThinkingPolicy = 'drop' | 'keep';
|
|
88
|
+
export type ImagePolicy = 'extract' | 'marker';
|
|
89
|
+
export interface ContentPolicy {
|
|
90
|
+
tools: ToolPolicy;
|
|
91
|
+
thinking: ThinkingPolicy;
|
|
92
|
+
images: ImagePolicy;
|
|
93
|
+
}
|
|
94
|
+
export declare const DEFAULT_CONTENT_POLICY: ContentPolicy;
|
|
95
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the Claude Code JSONL log format.
|
|
3
|
+
*
|
|
4
|
+
* Every field is optional. The schema is undocumented, observed rather than
|
|
5
|
+
* guaranteed, and has drifted before: see the specification's §3.1 for two
|
|
6
|
+
* changes that reversed design decisions during drafting. Nothing here should
|
|
7
|
+
* be read as a promise about what a record contains.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_CONTENT_POLICY = {
|
|
10
|
+
tools: 'strip',
|
|
11
|
+
thinking: 'drop',
|
|
12
|
+
images: 'extract',
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA4FH,MAAM,CAAC,MAAM,sBAAsB,GAAkB;IACnD,KAAK,EAAE,OAAO;IACd,QAAQ,EAAE,MAAM;IAChB,MAAM,EAAE,SAAS;CAClB,CAAC"}
|