@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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +134 -0
  3. package/dist/cli/args.d.ts +23 -0
  4. package/dist/cli/args.d.ts.map +1 -0
  5. package/dist/cli/args.js +85 -0
  6. package/dist/cli/args.js.map +1 -0
  7. package/dist/cli/index.d.ts +10 -0
  8. package/dist/cli/index.d.ts.map +1 -0
  9. package/dist/cli/index.js +230 -0
  10. package/dist/cli/index.js.map +1 -0
  11. package/dist/cli/init.d.ts +2 -0
  12. package/dist/cli/init.d.ts.map +1 -0
  13. package/dist/cli/init.js +35 -0
  14. package/dist/cli/init.js.map +1 -0
  15. package/dist/cli/probe.d.ts +18 -0
  16. package/dist/cli/probe.d.ts.map +1 -0
  17. package/dist/cli/probe.js +83 -0
  18. package/dist/cli/probe.js.map +1 -0
  19. package/dist/commits.d.ts +31 -0
  20. package/dist/commits.d.ts.map +1 -0
  21. package/dist/commits.js +59 -0
  22. package/dist/commits.js.map +1 -0
  23. package/dist/config.d.ts +65 -0
  24. package/dist/config.d.ts.map +1 -0
  25. package/dist/config.js +189 -0
  26. package/dist/config.js.map +1 -0
  27. package/dist/content.d.ts +50 -0
  28. package/dist/content.d.ts.map +1 -0
  29. package/dist/content.js +141 -0
  30. package/dist/content.js.map +1 -0
  31. package/dist/discover.d.ts +55 -0
  32. package/dist/discover.d.ts.map +1 -0
  33. package/dist/discover.js +116 -0
  34. package/dist/discover.js.map +1 -0
  35. package/dist/extract.d.ts +47 -0
  36. package/dist/extract.d.ts.map +1 -0
  37. package/dist/extract.js +127 -0
  38. package/dist/extract.js.map +1 -0
  39. package/dist/index.d.ts +24 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +23 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/paths.d.ts +31 -0
  44. package/dist/paths.d.ts.map +1 -0
  45. package/dist/paths.js +47 -0
  46. package/dist/paths.js.map +1 -0
  47. package/dist/render/index.d.ts +21 -0
  48. package/dist/render/index.d.ts.map +1 -0
  49. package/dist/render/index.js +68 -0
  50. package/dist/render/index.js.map +1 -0
  51. package/dist/render/transcript.d.ts +31 -0
  52. package/dist/render/transcript.d.ts.map +1 -0
  53. package/dist/render/transcript.js +54 -0
  54. package/dist/render/transcript.js.map +1 -0
  55. package/dist/session.d.ts +30 -0
  56. package/dist/session.d.ts.map +1 -0
  57. package/dist/session.js +99 -0
  58. package/dist/session.js.map +1 -0
  59. package/dist/titles.d.ts +26 -0
  60. package/dist/titles.d.ts.map +1 -0
  61. package/dist/titles.js +51 -0
  62. package/dist/titles.js.map +1 -0
  63. package/dist/turns.d.ts +62 -0
  64. package/dist/turns.d.ts.map +1 -0
  65. package/dist/turns.js +108 -0
  66. package/dist/turns.js.map +1 -0
  67. package/dist/types.d.ts +95 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +14 -0
  70. package/dist/types.js.map +1 -0
  71. package/dist/write.d.ts +61 -0
  72. package/dist/write.d.ts.map +1 -0
  73. package/dist/write.js +126 -0
  74. package/dist/write.js.map +1 -0
  75. 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"}
@@ -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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"}
@@ -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"}
@@ -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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"}
@@ -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"}