@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 @@
1
+ {"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEpD;;;;GAIG;AACH,MAAM,YAAY,GAAG;IACnB,sBAAsB;IACtB,sBAAsB;IACtB,cAAc;IACd,iBAAiB;IACjB,cAAc;IACd,iBAAiB;CACT,CAAC;AAEX,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,KAAK,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;AAChF,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,OAAO,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAEpE;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,KAAK,CAAC,OAAe,EAAE,IAAY;IACjD,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACtF,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;IACnD,OAAO,GAAG,KAAK,GAAG,IAAI,KAAK,OAAO,KAAK,KAAK,EAAE,CAAC;AACjD,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,MAAM,SAAS,GAAG,KAAK;SACpB,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAC3B,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,UAAU,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,GAA2B;IACzC,WAAW,EAAE,KAAK;IAClB,YAAY,EAAE,KAAK;IACnB,WAAW,EAAE,KAAK;IAClB,YAAY,EAAE,MAAM;CACrB,CAAC;AAEF,2DAA2D;AAC3D,SAAS,SAAS,CAAC,KAAa;IAC9B,IAAI,KAAK,GAAG,IAAI;QAAE,OAAO,GAAG,KAAK,IAAI,CAAC;IACtC,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;IACjE,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;AACpD,CAAC;AAUD;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,KAAe,EAAE,OAAsB;IACjE,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,UAAU,IAAI,SAAS,CAAC;IACpD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC;IAEhC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;QAC5C,OAAO,aAAa,KAAK,YAAY,CAAC;IACxC,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC1C,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAEpC,IAAI,OAAO,CAAC,MAAM,EAAE,MAAM,KAAK,QAAQ,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QACnE,OAAO,aAAa,KAAK,KAAK,SAAS,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;IAC/D,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC1E,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC,UAAU,IAAI,SAAS,IAAI,IAAI,IAAI,SAAS,EAAE,CAAC;IAE3E,IAAI,OAAO,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,EAAE,CAAC;QACjF,OAAO,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,0BAA0B,QAAQ,GAAG,CAAC;AAC/C,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CACzB,KAAe,EACf,UAAyB,EAAE;IAE3B,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,sBAAsB,CAAC;IAExD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,MAAM;YACT,OAAO,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAE1D,KAAK,UAAU;YACb,OAAO,MAAM,CAAC,QAAQ,KAAK,MAAM;gBAC/B,CAAC,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,UAAU,CAAC;gBAC7E,CAAC,CAAC,IAAI,CAAC;QAEX,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,IAAI,MAAM,CAAC,KAAK,KAAK,OAAO;gBAAE,OAAO,IAAI,CAAC;YAC1C,MAAM,IAAI,GAAG,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;YACrE,OAAO,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,YAAY,IAAI,EAAE,CAAC,CAAC;QACjF,CAAC;QAED,KAAK,aAAa,CAAC,CAAC,CAAC;YACnB,IAAI,MAAM,CAAC,KAAK,KAAK,OAAO;gBAAE,OAAO,IAAI,CAAC;YAC1C,MAAM,IAAI,GAAG,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;YACrE,OAAO,KAAK,CACV,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,IAAI,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAC9C,eAAe,IAAI,EAAE,CACtB,CAAC;QACJ,CAAC;QAED,KAAK,OAAO;YACV,OAAO,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAErC;YACE,OAAO,4BAA4B,KAAK,CAAC,IAAI,IAAI,WAAW,GAAG,CAAC;IACpE,CAAC;AACH,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,UAAU,CAAC,MAAiB,EAAE,UAAyB,EAAE;IACvE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC;IACxC,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;IACvD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IAEvC,OAAO,OAAO;SACX,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;SAC3C,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;SACrE,IAAI,CAAC,MAAM,CAAC;SACZ,IAAI,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,55 @@
1
+ import type { RawRecord } from './types.js';
2
+ export interface LogIdentity {
3
+ path: string;
4
+ sessionId: string | null;
5
+ /** Working directories seen: `cwd` plus any `relocatedCwd`. */
6
+ cwds: string[];
7
+ /** True if any record in the identity scan was flagged as a sidechain. */
8
+ sidechain: boolean;
9
+ }
10
+ /** Parses a JSONL file, skipping unparseable lines. */
11
+ export declare function parseRecords(contents: string): RawRecord[];
12
+ export declare function readLogFile(path: string): RawRecord[];
13
+ /**
14
+ * Every session log under `logRoot`, at **depth one exactly**.
15
+ *
16
+ * `<logRoot>/<project>/<session>.jsonl` and nothing deeper. This is not an
17
+ * optimization; it is a correctness requirement. Subagent transcripts live at
18
+ * `<logRoot>/<project>/<parent-session-id>/subagents/agent-<id>.jsonl`, and
19
+ * every record in one carries the *parent's* `sessionId` along with a `cwd`
20
+ * that matches the project. A recursive walk would accept them as project
21
+ * sessions and file them under a UUID that already belongs to a real
22
+ * transcript — producing not an error but a second, wrong document competing
23
+ * with a correct one. See specification §4.4.
24
+ */
25
+ export declare function listLogFiles(logRoot: string): string[];
26
+ /**
27
+ * Session id and working directories, read from the head of a log.
28
+ *
29
+ * Capped rather than parsing the whole file: the first record is usually a
30
+ * `mode` record carrying nothing, and across the observed corpus every log
31
+ * reveals its `cwd` within twelve records. Sessions that fail the project match
32
+ * are never fully parsed.
33
+ */
34
+ export declare function readIdentity(path: string, records: RawRecord[]): LogIdentity;
35
+ /**
36
+ * True when a log belongs to the project rooted at `root`.
37
+ *
38
+ * Membership is decided by `cwd`, never by decoding the project directory name:
39
+ * that encoding is lossy and cannot be reversed. Descendant matching rather
40
+ * than equality covers Claude Code launched from a subdirectory, and worktrees
41
+ * that `relocated` records point at beneath `.claude/worktrees/`.
42
+ */
43
+ export declare function belongsToProject(identity: LogIdentity, root: string): boolean;
44
+ export interface DiscoveredLog extends LogIdentity {
45
+ records: RawRecord[];
46
+ }
47
+ /**
48
+ * Logs belonging to `root`, fully parsed.
49
+ *
50
+ * Two passes by design: a cheap identity scan decides membership, and only
51
+ * members are parsed in full. Titles arrive late in a file, so there is no
52
+ * shortcut for members — but there is no reason to pay it for other projects.
53
+ */
54
+ export declare function discover(logRoot: string, root: string): DiscoveredLog[];
55
+ //# sourceMappingURL=discover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.d.ts","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAK5C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,+DAA+D;IAC/D,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,0EAA0E;IAC1E,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,uDAAuD;AACvD,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,CAY1D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CAErD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAwBtD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,WAAW,CAgB5E;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAG7E;AAED,MAAM,WAAW,aAAc,SAAQ,WAAW;IAChD,OAAO,EAAE,SAAS,EAAE,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAWvE"}
@@ -0,0 +1,116 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { UsageError } from './config.js';
4
+ import { isWithin } from './paths.js';
5
+ /** How many records to read before giving up on finding identity fields. */
6
+ const IDENTITY_SCAN_LIMIT = 50;
7
+ /** Parses a JSONL file, skipping unparseable lines. */
8
+ export function parseRecords(contents) {
9
+ const records = [];
10
+ for (const line of contents.split('\n')) {
11
+ if (!line.trim())
12
+ continue;
13
+ try {
14
+ records.push(JSON.parse(line));
15
+ }
16
+ catch {
17
+ // A truncated final line is normal for a session still being written.
18
+ // Skipping it silently is correct; failing the run over it is not.
19
+ }
20
+ }
21
+ return records;
22
+ }
23
+ export function readLogFile(path) {
24
+ return parseRecords(readFileSync(path, 'utf8'));
25
+ }
26
+ /**
27
+ * Every session log under `logRoot`, at **depth one exactly**.
28
+ *
29
+ * `<logRoot>/<project>/<session>.jsonl` and nothing deeper. This is not an
30
+ * optimization; it is a correctness requirement. Subagent transcripts live at
31
+ * `<logRoot>/<project>/<parent-session-id>/subagents/agent-<id>.jsonl`, and
32
+ * every record in one carries the *parent's* `sessionId` along with a `cwd`
33
+ * that matches the project. A recursive walk would accept them as project
34
+ * sessions and file them under a UUID that already belongs to a real
35
+ * transcript — producing not an error but a second, wrong document competing
36
+ * with a correct one. See specification §4.4.
37
+ */
38
+ export function listLogFiles(logRoot) {
39
+ if (!existsSync(logRoot)) {
40
+ throw new UsageError(`no such directory: ${logRoot}. Claude Code keeps session logs in ` +
41
+ '~/.claude/projects; pass --log-root if yours are elsewhere.');
42
+ }
43
+ if (!statSync(logRoot).isDirectory()) {
44
+ throw new UsageError(`${logRoot} is not a directory.`);
45
+ }
46
+ const files = [];
47
+ for (const project of readdirSync(logRoot, { withFileTypes: true })) {
48
+ if (!project.isDirectory())
49
+ continue;
50
+ const dir = join(logRoot, project.name);
51
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
52
+ // Depth one: a directory here is a session's subagent store, never a
53
+ // session itself. Not descended into, deliberately.
54
+ if (!entry.isFile() || !entry.name.endsWith('.jsonl'))
55
+ continue;
56
+ files.push(join(dir, entry.name));
57
+ }
58
+ }
59
+ return files.sort();
60
+ }
61
+ /**
62
+ * Session id and working directories, read from the head of a log.
63
+ *
64
+ * Capped rather than parsing the whole file: the first record is usually a
65
+ * `mode` record carrying nothing, and across the observed corpus every log
66
+ * reveals its `cwd` within twelve records. Sessions that fail the project match
67
+ * are never fully parsed.
68
+ */
69
+ export function readIdentity(path, records) {
70
+ const identity = { path, sessionId: null, cwds: [], sidechain: false };
71
+ for (const record of records.slice(0, IDENTITY_SCAN_LIMIT)) {
72
+ if (identity.sessionId === null && typeof record.sessionId === 'string') {
73
+ identity.sessionId = record.sessionId;
74
+ }
75
+ if (record.isSidechain === true)
76
+ identity.sidechain = true;
77
+ for (const dir of [record.cwd, record.relocatedCwd]) {
78
+ if (typeof dir === 'string' && dir !== '' && !identity.cwds.includes(dir)) {
79
+ identity.cwds.push(dir);
80
+ }
81
+ }
82
+ }
83
+ return identity;
84
+ }
85
+ /**
86
+ * True when a log belongs to the project rooted at `root`.
87
+ *
88
+ * Membership is decided by `cwd`, never by decoding the project directory name:
89
+ * that encoding is lossy and cannot be reversed. Descendant matching rather
90
+ * than equality covers Claude Code launched from a subdirectory, and worktrees
91
+ * that `relocated` records point at beneath `.claude/worktrees/`.
92
+ */
93
+ export function belongsToProject(identity, root) {
94
+ if (identity.sidechain)
95
+ return false;
96
+ return identity.cwds.some((dir) => isWithin(root, dir));
97
+ }
98
+ /**
99
+ * Logs belonging to `root`, fully parsed.
100
+ *
101
+ * Two passes by design: a cheap identity scan decides membership, and only
102
+ * members are parsed in full. Titles arrive late in a file, so there is no
103
+ * shortcut for members — but there is no reason to pay it for other projects.
104
+ */
105
+ export function discover(logRoot, root) {
106
+ const found = [];
107
+ for (const path of listLogFiles(logRoot)) {
108
+ const records = readLogFile(path);
109
+ const identity = readIdentity(path, records);
110
+ if (!belongsToProject(identity, root))
111
+ continue;
112
+ found.push({ ...identity, records });
113
+ }
114
+ return found;
115
+ }
116
+ //# sourceMappingURL=discover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discover.js","sourceRoot":"","sources":["../src/discover.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { type Commit } from './commits.js';
2
+ import { type Config } from './config.js';
3
+ import { type DiscoveredLog } from './discover.js';
4
+ import type { Session } from './types.js';
5
+ /** Why a session was not written. */
6
+ export type SkipReason = 'empty' | 'excluded' | 'not-included' | 'active';
7
+ export interface SkippedSession {
8
+ id: string;
9
+ title: string;
10
+ reason: SkipReason;
11
+ }
12
+ export interface ExtractSummary {
13
+ root: string;
14
+ outDir: string;
15
+ sessions: number;
16
+ written: number;
17
+ unchanged: number;
18
+ turns: number;
19
+ imagesWritten: string[];
20
+ skipped: SkippedSession[];
21
+ /** Transcripts deleted, with the rule that removed them. */
22
+ removed: {
23
+ name: string;
24
+ reason: 'renamed' | 'excluded';
25
+ }[];
26
+ /** Reported only; never deleted. */
27
+ orphanImages: string[];
28
+ }
29
+ export interface ExtractOptions {
30
+ root: string;
31
+ logRoot: string;
32
+ config: Config;
33
+ /** `YYYY-MM-DD`. Injected so the caller owns the only clock read. */
34
+ today: string;
35
+ /** Skip this session id — the one cctx is running inside, when known. */
36
+ currentSessionId?: string;
37
+ /** Milliseconds since the epoch, for the active-session grace window. */
38
+ now?: number;
39
+ dryRun?: boolean;
40
+ /** Overrides the git lookup; tests pass a fixed list instead of shelling out. */
41
+ commits?: Commit[];
42
+ }
43
+ /** Reads every log belonging to the project into sessions, links included. */
44
+ export declare function readSessions(logs: DiscoveredLog[], config: Config): Session[];
45
+ /** The whole run. */
46
+ export declare function extract(options: ExtractOptions): ExtractSummary;
47
+ //# sourceMappingURL=extract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgC,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAiB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAEzD,OAAO,EAAY,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAS7D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAU1C,qCAAqC;AACrC,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,cAAc,GAAG,QAAQ,CAAC;AAE1E,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,4DAA4D;IAC5D,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,SAAS,GAAG,UAAU,CAAA;KAAE,EAAE,CAAC;IAC5D,oCAAoC;IACpC,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AA6BD,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,IAAI,EAAE,aAAa,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,CAQ7E;AAED,qBAAqB;AACrB,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CA6F/D"}
@@ -0,0 +1,127 @@
1
+ import { mkdirSync } from 'node:fs';
2
+ import { basename, join, resolve } from 'node:path';
3
+ import { readCommits, commitsInWindow } from './commits.js';
4
+ import { contentPolicy } from './config.js';
5
+ import { slugify } from './content.js';
6
+ import { discover } from './discover.js';
7
+ import { renderIndex } from './render/index.js';
8
+ import { renderTranscript } from './render/transcript.js';
9
+ import { fallbackIdFor, linkContinuations, readSession, sessionFilename, } from './session.js';
10
+ import { findOrphanImages, pruneExcluded, pruneRenamed, writeIfChanged, writeImages, writePlainIfChanged, } from './write.js';
11
+ /** True when a session's last record is inside the grace window. */
12
+ function isActive(session, now, graceMinutes) {
13
+ if (session.endedAt === undefined)
14
+ return false;
15
+ const end = Date.parse(session.endedAt);
16
+ if (Number.isNaN(end))
17
+ return false;
18
+ return now - end < graceMinutes * 60_000;
19
+ }
20
+ /** Decides whether a session is written, and records why not. */
21
+ function selectionReason(session, options) {
22
+ const { config } = options;
23
+ if (config.exclude.includes(session.id))
24
+ return 'excluded';
25
+ if (config.include.length > 0 && !config.include.includes(session.id)) {
26
+ return 'not-included';
27
+ }
28
+ if (config.skipEmpty && session.turns.length === 0)
29
+ return 'empty';
30
+ if (session.id === options.currentSessionId)
31
+ return 'active';
32
+ if (config.skipActive &&
33
+ isActive(session, options.now ?? Date.now(), config.activeGraceMinutes)) {
34
+ return 'active';
35
+ }
36
+ return null;
37
+ }
38
+ /** Reads every log belonging to the project into sessions, links included. */
39
+ export function readSessions(logs, config) {
40
+ const policy = contentPolicy(config);
41
+ const sessions = logs.map((log) => readSession(log.records, { policy, fallbackId: fallbackIdFor(log.path) }));
42
+ linkContinuations(sessions);
43
+ sessions.sort((a, b) => `${a.created}${a.id}`.localeCompare(`${b.created}${b.id}`));
44
+ return sessions;
45
+ }
46
+ /** The whole run. */
47
+ export function extract(options) {
48
+ const { config, root } = options;
49
+ const outDir = resolve(root, config.out);
50
+ const imageDir = join(outDir, config.imageDir);
51
+ const sessions = readSessions(discover(options.logRoot, root), config);
52
+ const selected = [];
53
+ const skipped = [];
54
+ for (const session of sessions) {
55
+ const reason = selectionReason(session, options);
56
+ if (reason === null)
57
+ selected.push(session);
58
+ else
59
+ skipped.push({ id: session.id, title: session.title, reason });
60
+ }
61
+ const summary = {
62
+ root,
63
+ outDir,
64
+ sessions: selected.length,
65
+ written: 0,
66
+ unchanged: 0,
67
+ turns: selected.reduce((n, s) => n + s.turns.length, 0),
68
+ imagesWritten: [],
69
+ skipped,
70
+ removed: [],
71
+ orphanImages: [],
72
+ };
73
+ if (options.dryRun === true)
74
+ return summary;
75
+ mkdirSync(outDir, { recursive: true });
76
+ const commits = options.commits ?? (config.commits ? readCommits(root) : []);
77
+ // Every name this run produced, whether or not the bytes needed rewriting: a
78
+ // file skipped as unchanged is still a file this run stands behind, and
79
+ // pruneRenamed must not treat it as a superseded name.
80
+ const names = [];
81
+ const referencedImages = new Set();
82
+ for (const session of selected) {
83
+ const name = sessionFilename(session, slugify);
84
+ const wrote = writeIfChanged(join(outDir, name), (extractedOn) => renderTranscript(session, {
85
+ extractedOn,
86
+ project: root,
87
+ commits: commitsInWindow(commits, session),
88
+ }), options.today);
89
+ if (wrote)
90
+ summary.written += 1;
91
+ else
92
+ summary.unchanged += 1;
93
+ names.push(name);
94
+ summary.imagesWritten.push(...writeImages(imageDir, session.images));
95
+ for (const image of session.images)
96
+ referencedImages.add(image.filename);
97
+ }
98
+ if (config.index.enabled) {
99
+ const excluded = skipped
100
+ .filter((s) => s.reason === 'excluded' || s.reason === 'not-included')
101
+ .map((s) => s.id);
102
+ const indexPath = join(outDir, 'index.md');
103
+ const wrote = writePlainIfChanged(indexPath, renderIndex(selected, {
104
+ preamble: config.index.preamble,
105
+ excluded,
106
+ listExcluded: config.index.listExcluded,
107
+ }));
108
+ if (wrote)
109
+ summary.written += 1;
110
+ else
111
+ summary.unchanged += 1;
112
+ names.push(basename(indexPath));
113
+ }
114
+ // After the writes, so a name this run produced is never a deletion
115
+ // candidate.
116
+ for (const name of pruneRenamed(outDir, names)) {
117
+ summary.removed.push({ name, reason: 'renamed' });
118
+ }
119
+ for (const name of pruneExcluded(outDir, config.exclude)) {
120
+ if (!summary.removed.some((r) => r.name === name)) {
121
+ summary.removed.push({ name, reason: 'excluded' });
122
+ }
123
+ }
124
+ summary.orphanImages = findOrphanImages(imageDir, referencedImages);
125
+ return summary;
126
+ }
127
+ //# sourceMappingURL=extract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extract.js","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * cctx — extract Claude Code session logs into markdown transcripts.
3
+ *
4
+ * Library entry point. See `docs/specs/01-initial-specification.md` §12.
5
+ *
6
+ * Everything exported here is pure unless its name says otherwise: nothing
7
+ * below the CLI layer reads the clock, the environment, or the home directory.
8
+ * Where a function needs one of those, it takes it as a parameter.
9
+ */
10
+ export { defaultLogRoot, encodeProjectDir, isWithin } from './paths.js';
11
+ export { CONFIG_NAMES, DEFAULT_CONFIG, UsageError, contentPolicy, findConfig, loadConfig, mergeConfig, parseConfigFile, type Config, type IndexConfig, type ResolvedConfig, } from './config.js';
12
+ export { belongsToProject, discover, listLogFiles, parseRecords, readIdentity, readLogFile, type DiscoveredLog, type LogIdentity, } from './discover.js';
13
+ export { fence, recordText, renderBlock, renderImage, slugify, stripHarnessTags, type RenderContext, } from './content.js';
14
+ export { aggregateParity, buildTurns, isHumanTurn, parityReport, type ParityReport, } from './turns.js';
15
+ export { resolveTitle, toLocalDate } from './titles.js';
16
+ export { fallbackIdFor, linkContinuations, readSession, sessionFilename, type ReadSessionOptions, } from './session.js';
17
+ export { commitsInWindow, readCommits, type Commit } from './commits.js';
18
+ export { frontmatterFor, readExtractedDate, renderTranscript, type RenderTranscriptOptions, } from './render/transcript.js';
19
+ export { parityStatement, renderIndex, type RenderIndexOptions, } from './render/index.js';
20
+ export { findOrphanImages, pruneExcluded, pruneRenamed, writeIfChanged, writeImages, writePlainIfChanged, } from './write.js';
21
+ export { extract, readSessions, type ExtractOptions, type ExtractSummary, type SkipReason, type SkippedSession, } from './extract.js';
22
+ export { formatProbe, probe, probeRecords, type ProbeReport } from './cli/probe.js';
23
+ export type { ContentPolicy, ImagePolicy, RawBlock, RawRecord, Session, SessionImage, SessionTurn, Speaker, ThinkingPolicy, ToolPolicy, } from './types.js';
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAExE,OAAO,EACL,YAAY,EACZ,cAAc,EACd,UAAU,EACV,aAAa,EACb,UAAU,EACV,UAAU,EACV,WAAW,EACX,eAAe,EACf,KAAK,MAAM,EACX,KAAK,WAAW,EAChB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,gBAAgB,EAChB,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,KAAK,aAAa,EAClB,KAAK,WAAW,GACjB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,KAAK,EACL,UAAU,EACV,WAAW,EACX,WAAW,EACX,OAAO,EACP,gBAAgB,EAChB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,eAAe,EACf,UAAU,EACV,WAAW,EACX,YAAY,EACZ,KAAK,YAAY,GAClB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,eAAe,EACf,KAAK,kBAAkB,GACxB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AAEzE,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,uBAAuB,GAC7B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,eAAe,EACf,WAAW,EACX,KAAK,kBAAkB,GACxB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,cAAc,EACd,WAAW,EACX,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,OAAO,EACP,YAAY,EACZ,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAEpF,YAAY,EACV,aAAa,EACb,WAAW,EACX,QAAQ,EACR,SAAS,EACT,OAAO,EACP,YAAY,EACZ,WAAW,EACX,OAAO,EACP,cAAc,EACd,UAAU,GACX,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * cctx — extract Claude Code session logs into markdown transcripts.
3
+ *
4
+ * Library entry point. See `docs/specs/01-initial-specification.md` §12.
5
+ *
6
+ * Everything exported here is pure unless its name says otherwise: nothing
7
+ * below the CLI layer reads the clock, the environment, or the home directory.
8
+ * Where a function needs one of those, it takes it as a parameter.
9
+ */
10
+ export { defaultLogRoot, encodeProjectDir, isWithin } from './paths.js';
11
+ export { CONFIG_NAMES, DEFAULT_CONFIG, UsageError, contentPolicy, findConfig, loadConfig, mergeConfig, parseConfigFile, } from './config.js';
12
+ export { belongsToProject, discover, listLogFiles, parseRecords, readIdentity, readLogFile, } from './discover.js';
13
+ export { fence, recordText, renderBlock, renderImage, slugify, stripHarnessTags, } from './content.js';
14
+ export { aggregateParity, buildTurns, isHumanTurn, parityReport, } from './turns.js';
15
+ export { resolveTitle, toLocalDate } from './titles.js';
16
+ export { fallbackIdFor, linkContinuations, readSession, sessionFilename, } from './session.js';
17
+ export { commitsInWindow, readCommits } from './commits.js';
18
+ export { frontmatterFor, readExtractedDate, renderTranscript, } from './render/transcript.js';
19
+ export { parityStatement, renderIndex, } from './render/index.js';
20
+ export { findOrphanImages, pruneExcluded, pruneRenamed, writeIfChanged, writeImages, writePlainIfChanged, } from './write.js';
21
+ export { extract, readSessions, } from './extract.js';
22
+ export { formatProbe, probe, probeRecords } from './cli/probe.js';
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAExE,OAAO,EACL,YAAY,EACZ,cAAc,EACd,UAAU,EACV,aAAa,EACb,UAAU,EACV,UAAU,EACV,WAAW,EACX,eAAe,GAIhB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,gBAAgB,EAChB,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,WAAW,GAGZ,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,KAAK,EACL,UAAU,EACV,WAAW,EACX,WAAW,EACX,OAAO,EACP,gBAAgB,GAEjB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,eAAe,EACf,UAAU,EACV,WAAW,EACX,YAAY,GAEb,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAExD,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,eAAe,GAEhB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,eAAe,EAAE,WAAW,EAAe,MAAM,cAAc,CAAC;AAEzE,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,gBAAgB,GAEjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,eAAe,EACf,WAAW,GAEZ,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,cAAc,EACd,WAAW,EACX,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,OAAO,EACP,YAAY,GAKb,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,EAAoB,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Where Claude Code keeps its session logs.
3
+ *
4
+ * Takes `home` as a parameter rather than calling `homedir()` at the point of
5
+ * use. Nothing below the CLI layer may read the environment, the home
6
+ * directory, or the clock — CI has no `~/.claude/projects` and must never grow
7
+ * one, so every test reads checked-in fixtures instead. Retrofitting that later
8
+ * reaches every call site at once; doing it first costs nothing.
9
+ */
10
+ export declare function defaultLogRoot(home?: string): string;
11
+ /**
12
+ * Claude Code encodes a project path into a directory name by replacing every
13
+ * path separator with a hyphen: `/Users/a/p` becomes `-Users-a-p`.
14
+ *
15
+ * Exported for documentation and for building a fast-path guess, never for
16
+ * decoding. The encoding is lossy — a path containing a hyphen is
17
+ * indistinguishable from one containing a separator — so the direction that
18
+ * matters, name back to path, is not recoverable. Project membership is decided
19
+ * by reading `cwd` out of the records instead.
20
+ */
21
+ export declare function encodeProjectDir(projectPath: string): string;
22
+ /**
23
+ * True when `candidate` is `root` or lives beneath it.
24
+ *
25
+ * Descendant rather than equality, which covers two real cases: Claude Code
26
+ * launched from a subdirectory, which lands in a differently-named project
27
+ * directory; and worktrees, which `relocated` records point at under
28
+ * `<project>/.claude/worktrees/`.
29
+ */
30
+ export declare function isWithin(root: string, candidate: string): boolean;
31
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAE/D;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAWjE"}
package/dist/paths.js ADDED
@@ -0,0 +1,47 @@
1
+ import { homedir } from 'node:os';
2
+ import { join, relative, resolve, sep } from 'node:path';
3
+ /**
4
+ * Where Claude Code keeps its session logs.
5
+ *
6
+ * Takes `home` as a parameter rather than calling `homedir()` at the point of
7
+ * use. Nothing below the CLI layer may read the environment, the home
8
+ * directory, or the clock — CI has no `~/.claude/projects` and must never grow
9
+ * one, so every test reads checked-in fixtures instead. Retrofitting that later
10
+ * reaches every call site at once; doing it first costs nothing.
11
+ */
12
+ export function defaultLogRoot(home = homedir()) {
13
+ return join(home, '.claude', 'projects');
14
+ }
15
+ /**
16
+ * Claude Code encodes a project path into a directory name by replacing every
17
+ * path separator with a hyphen: `/Users/a/p` becomes `-Users-a-p`.
18
+ *
19
+ * Exported for documentation and for building a fast-path guess, never for
20
+ * decoding. The encoding is lossy — a path containing a hyphen is
21
+ * indistinguishable from one containing a separator — so the direction that
22
+ * matters, name back to path, is not recoverable. Project membership is decided
23
+ * by reading `cwd` out of the records instead.
24
+ */
25
+ export function encodeProjectDir(projectPath) {
26
+ return projectPath.replace(/\/+$/, '').replace(/\//g, '-');
27
+ }
28
+ /**
29
+ * True when `candidate` is `root` or lives beneath it.
30
+ *
31
+ * Descendant rather than equality, which covers two real cases: Claude Code
32
+ * launched from a subdirectory, which lands in a differently-named project
33
+ * directory; and worktrees, which `relocated` records point at under
34
+ * `<project>/.claude/worktrees/`.
35
+ */
36
+ export function isWithin(root, candidate) {
37
+ const from = resolve(root);
38
+ const to = resolve(candidate);
39
+ if (from === to)
40
+ return true;
41
+ const rel = relative(from, to);
42
+ return (rel !== '' &&
43
+ !rel.startsWith('..') &&
44
+ !rel.startsWith(sep) &&
45
+ !rel.includes(`..${sep}`));
46
+ }
47
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEzD;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe,OAAO,EAAE;IACrD,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAClD,OAAO,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,SAAiB;IACtD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAC9B,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAC7B,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC/B,OAAO,CACL,GAAG,KAAK,EAAE;QACV,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;QACrB,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;QACpB,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,GAAG,EAAE,CAAC,CAC1B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { Session } from '../types.js';
2
+ export interface RenderIndexOptions {
3
+ /** Project-specific prose, inserted verbatim. */
4
+ preamble?: string | null;
5
+ /** Sessions the config withheld. */
6
+ excluded?: string[];
7
+ listExcluded?: 'count' | 'ids' | 'none';
8
+ }
9
+ /**
10
+ * A sentence about turn alternation, measured rather than asserted.
11
+ *
12
+ * "Odd turns are the author" is the kind of plausible claim a corpus gets
13
+ * burned by, so it is checked on generation. Breaks are counted as adjacent
14
+ * same-speaker pairs, not as index parity — a single break early in a long
15
+ * session flips every turn after it, so index parity reports an order of
16
+ * magnitude more breaks than exist.
17
+ */
18
+ export declare function parityStatement(sessions: Session[]): string;
19
+ /** The generated index. Pure. */
20
+ export declare function renderIndex(sessions: Session[], options?: RenderIndexOptions): string;
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/render/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,kBAAkB;IACjC,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;CACzC;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAuC3D;AAED,iCAAiC;AACjC,wBAAgB,WAAW,CACzB,QAAQ,EAAE,OAAO,EAAE,EACnB,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CA0DR"}
@@ -0,0 +1,68 @@
1
+ import { aggregateParity, parityReport } from '../turns.js';
2
+ /**
3
+ * A sentence about turn alternation, measured rather than asserted.
4
+ *
5
+ * "Odd turns are the author" is the kind of plausible claim a corpus gets
6
+ * burned by, so it is checked on generation. Breaks are counted as adjacent
7
+ * same-speaker pairs, not as index parity — a single break early in a long
8
+ * session flips every turn after it, so index parity reports an order of
9
+ * magnitude more breaks than exist.
10
+ */
11
+ export function parityStatement(sessions) {
12
+ const totals = aggregateParity(sessions.map((s) => parityReport(s.turns)));
13
+ if (totals.alternationBreaks === 0 && totals.sessionsNotStartingWithHuman === 0) {
14
+ return ('Turns alternate without exception: odd turns are the author, even turns ' +
15
+ 'the assistant. Checked on generation, not assumed — so the speaker of ' +
16
+ 'turn N is known from N alone.');
17
+ }
18
+ const parts = [];
19
+ if (totals.alternationBreaks > 0) {
20
+ parts.push(`${totals.alternationBreaks} place${totals.alternationBreaks === 1 ? '' : 's'} ` +
21
+ 'where two turns in a row share a speaker');
22
+ }
23
+ if (totals.sessionsNotStartingWithHuman > 0) {
24
+ parts.push(`${totals.sessionsNotStartingWithHuman} session${totals.sessionsNotStartingWithHuman === 1 ? '' : 's'} opening with an assistant turn`);
25
+ }
26
+ // Agreement follows the count inside the clause, not the number of clauses:
27
+ // one clause reading "2 places" still takes "are".
28
+ const onlyCount = totals.alternationBreaks > 0 && totals.sessionsNotStartingWithHuman > 0
29
+ ? 2
30
+ : totals.alternationBreaks + totals.sessionsNotStartingWithHuman;
31
+ const verb = parts.length === 1 && onlyCount === 1 ? 'is' : 'are';
32
+ return ('Turns mostly alternate — odd the author, even the assistant — but there ' +
33
+ `${verb} ${parts.join(' and ')}. ` +
34
+ 'Read the heading rather than inferring the speaker from the number.');
35
+ }
36
+ /** The generated index. Pure. */
37
+ export function renderIndex(sessions, options = {}) {
38
+ const excluded = options.excluded ?? [];
39
+ const listExcluded = options.listExcluded ?? 'count';
40
+ const rows = sessions.map((session) => {
41
+ const title = session.title.replace(/\|/g, '\\|');
42
+ return `| ${session.created} | ${title} | ${session.turns.length} | \`${session.id}\` |`;
43
+ });
44
+ const lines = [
45
+ '# Claude Code sessions',
46
+ '',
47
+ 'Session transcripts captured from the local Claude Code logs by `cctx`.',
48
+ 'This file is generated; edits to it will be overwritten.',
49
+ '',
50
+ ];
51
+ if (options.preamble !== undefined &&
52
+ options.preamble !== null &&
53
+ options.preamble !== '') {
54
+ lines.push(options.preamble.trim(), '');
55
+ }
56
+ lines.push('Two things to know before reading a turn number. Tool calls and their', 'results are stripped, because anything authored in a session lands in the', 'working tree and is captured by the commit; what survives is the prose. And', 'a turn is one *exchange*, coalesced from the several records a single reply', 'occupies, so these numbers are stable only while that filter is.', '', parityStatement(sessions), '');
57
+ if (excluded.length > 0 && listExcluded !== 'none') {
58
+ // Visible, never silent: a reader cannot audit an absence they cannot see.
59
+ lines.push(listExcluded === 'ids'
60
+ ? `${excluded.length} session${excluded.length === 1 ? '' : 's'} withheld by ` +
61
+ `configuration: ${excluded.map((id) => `\`${id}\``).join(', ')}.`
62
+ : `${excluded.length} session${excluded.length === 1 ? '' : 's'} withheld by ` +
63
+ 'configuration.', '');
64
+ }
65
+ lines.push('These sessions have no claude.ai URL because they never existed there.', '', '| Created | Title | Turns | Session ID |', '| --- | --- | --- | --- |', ...rows, '');
66
+ return lines.join('\n');
67
+ }
68
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/render/index.ts"],"names":[],"mappings":"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"}