vouchington-tooling 0.0.21 → 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 (44) hide show
  1. package/README.md +28 -0
  2. package/dist/browser-session-runner/attempt.d.mts +2 -0
  3. package/dist/browser-session-runner/attempt.mjs +195 -0
  4. package/dist/browser-session-runner/index.d.mts +4 -0
  5. package/dist/browser-session-runner/index.mjs +64 -0
  6. package/dist/browser-session-runner/process-group.d.mts +5 -0
  7. package/dist/browser-session-runner/process-group.mjs +23 -0
  8. package/dist/browser-session-runner/result.d.mts +2 -0
  9. package/dist/browser-session-runner/result.mjs +11 -0
  10. package/dist/browser-session-runner/tail-queue.d.mts +8 -0
  11. package/dist/browser-session-runner/tail-queue.mjs +30 -0
  12. package/dist/browser-session-runner/tail.d.mts +1 -0
  13. package/dist/browser-session-runner/tail.mjs +12 -0
  14. package/dist/browser-session-runner/types.d.mts +53 -0
  15. package/dist/browser-session-runner/types.mjs +1 -0
  16. package/dist/cli/commands/retrospective-transcript.d.mts +1 -0
  17. package/dist/cli/commands/retrospective-transcript.mjs +31 -0
  18. package/dist/cli/index.mjs +3 -0
  19. package/dist/cli/parse.d.mts +3 -0
  20. package/dist/cli/parse.mjs +2 -0
  21. package/dist/cli/usage.d.mts +1 -1
  22. package/dist/cli/usage.mjs +2 -0
  23. package/dist/coverage-transport/outcome.mjs +3 -1
  24. package/dist/index.d.mts +6 -0
  25. package/dist/index.mjs +3 -0
  26. package/dist/retrospective-transcript/claude.d.mts +2 -0
  27. package/dist/retrospective-transcript/claude.mjs +67 -0
  28. package/dist/retrospective-transcript/codex.d.mts +11 -0
  29. package/dist/retrospective-transcript/codex.mjs +174 -0
  30. package/dist/retrospective-transcript/format.d.mts +4 -0
  31. package/dist/retrospective-transcript/format.mjs +22 -0
  32. package/dist/retrospective-transcript/index.d.mts +22 -0
  33. package/dist/retrospective-transcript/index.mjs +151 -0
  34. package/dist/retrospective-transcript/javascript-command.d.mts +1 -0
  35. package/dist/retrospective-transcript/javascript-command.mjs +25 -0
  36. package/dist/retrospective-transcript/shared.d.mts +31 -0
  37. package/dist/retrospective-transcript/shared.mjs +213 -0
  38. package/dist/vitest-diagnostics/directory.d.mts +12 -0
  39. package/dist/vitest-diagnostics/directory.mjs +44 -0
  40. package/dist/vitest-diagnostics/index.d.mts +22 -0
  41. package/dist/vitest-diagnostics/index.mjs +123 -0
  42. package/dist/vitest-diagnostics/read-file.d.mts +2 -0
  43. package/dist/vitest-diagnostics/read-file.mjs +37 -0
  44. package/package.json +16 -1
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable max-lines -- package entry point enumerates the supported public API. */
2
+ export { codexChildren, codexIdentity, computeTranscriptFacts, formatTranscriptFacts, formatUnavailable, resolveTranscriptFile, runRetrospectiveTranscript, } from './retrospective-transcript/index.mjs';
2
3
  export { EphemeralListenerAttemptsExhaustedError, isRunnerReservedPort, listenOnRunnerUnreservedEphemeralPort, loadRunnerPortPolicy, runnerPortPolicy, validateRunnerPortPolicy, } from './runner-port-policy/index.mjs';
3
4
  export { extractAlterTableAddColumnLocations, extractCreateIndexMetadata, extractCreateTableMetadata, extractDefaultFunction, extractDropIndexMetadata, extractFuncCallArgColumnNames, extractMigrationConstraintMetadata, initSqlAst, lineOfUtf8ByteOffset, MissingSqlAstParserError, parseSql, } from './sql-ast/index.mjs';
4
5
  export { dollarQuoteEnd, lineOf, maskSqlQuotedText, readDollarQuoteDelimiter, readStringLiteral, splitSqlStatements, sqlFragments, stripSqlComments, } from './sql-scanner/index.mjs';
@@ -12,6 +13,7 @@ export { decodeSelectedFiles, encodeSelectedFiles, formatMultilineOutput, SELECT
12
13
  export { createArtifactClassifier, parseArtifactPatternsJson, planRunDeletions, runCleanup, sweepCleanup, } from './gha-artifacts-cleanup/index.mjs';
13
14
  export { validateOptionalHttpOrigin } from './http-origin/index.mjs';
14
15
  export { boundPendingLine, DEFAULT_MAX_PENDING_LINE_LENGTH, DEFAULT_TRUNCATED_LINE_MARKER, splitCompleteLines, } from './process-line-buffer/index.mjs';
16
+ export { ProcessGroupDrainTimeoutError, runBrowserSession, } from './browser-session-runner/index.mjs';
15
17
  export { buildSchemaSnapshot, detectRenamedIndexes, generateSchemaSnapshot, indexShapeKey, readSchemaCatalog, renderSchemaMarkdown, stableStringify, writeSchemaSnapshot, } from './pg-schema-snapshot/index.mjs';
16
18
  export { buildOpenApiDocument, hashContractSchema, nodeToOpenApi, writeOpenApi, } from './openapi-document/index.mjs';
17
19
  export { decide, deriveRetryAttempt } from './transient-retry/index.mjs';
@@ -33,3 +35,4 @@ export { validateNugetUpdate } from './nuget-central-version/index.mjs';
33
35
  export { normalizeSwiftSource } from './swift-semantic-equal/index.mjs';
34
36
  export { isSwiftCodeOffset, parseUniqueSwiftBinaryTargetChecksum, } from './swift-source-offset/index.mjs';
35
37
  export { validateResolvedPinDelta } from './swift-resolved-pin-delta/index.mjs';
38
+ export { DEFAULT_MAX_DIAGNOSTIC_REPORTS, DEFAULT_MAX_FORMATTED_DIAGNOSTIC_REPORTS, formatDiagnosticReportSummaries, HARD_MAX_DIAGNOSTIC_REPORTS, readDiagnosticReportSummaries, summarizeDiagnosticReport, } from './vitest-diagnostics/index.mjs';
@@ -0,0 +1,2 @@
1
+ import { type TranscriptFacts } from './shared.mts';
2
+ export declare function computeClaude(lines: string[][]): TranscriptFacts;
@@ -0,0 +1,67 @@
1
+ import { applyCommand, asNumber, asRecord, emptyFacts, parseLines, } from './shared.mjs';
2
+ function hasPromptContent(content) {
3
+ if (typeof content === 'string')
4
+ return true;
5
+ if (!Array.isArray(content))
6
+ return false;
7
+ return content.some((block) => {
8
+ const value = asRecord(block);
9
+ return (value !== undefined && value.type !== 'tool_result' && value.type !== 'advisor_tool_result');
10
+ });
11
+ }
12
+ export function computeClaude(lines) {
13
+ const facts = emptyFacts();
14
+ const seen = new Set();
15
+ const advisorIds = new Set();
16
+ for (const group of lines) {
17
+ for (const record of parseLines(group)) {
18
+ if (typeof record.uuid === 'string' && (seen.has(record.uuid) || !seen.add(record.uuid)))
19
+ continue;
20
+ const subagent = record.isSidechain === true;
21
+ const message = asRecord(record.message);
22
+ if (!subagent && record.type === 'user' && record.isCompactSummary === true)
23
+ facts.compactions++;
24
+ if (!subagent &&
25
+ record.type === 'user' &&
26
+ hasPromptContent(message?.content) &&
27
+ record.isMeta !== true &&
28
+ record.isCompactSummary !== true)
29
+ facts.userPrompts++;
30
+ if (record.type === 'assistant') {
31
+ if (!subagent)
32
+ facts.assistantResponses++;
33
+ const usage = asRecord(message?.usage);
34
+ const totals = subagent ? facts.subagentTokens : facts.tokens;
35
+ totals.input += asNumber(usage?.input_tokens);
36
+ totals.output += asNumber(usage?.output_tokens);
37
+ totals.cacheRead += asNumber(usage?.cache_read_input_tokens);
38
+ totals.cacheCreation += asNumber(usage?.cache_creation_input_tokens);
39
+ }
40
+ const blocks = Array.isArray(message?.content) ? message.content : [];
41
+ for (const block of blocks) {
42
+ const value = asRecord(block);
43
+ if (!value)
44
+ continue;
45
+ if (value.type === 'tool_use' || value.type === 'server_tool_use') {
46
+ facts.toolCalls++;
47
+ if (subagent)
48
+ facts.subagentToolCalls++;
49
+ if (value.name === 'advisor' && typeof value.id === 'string')
50
+ advisorIds.add(value.id);
51
+ if (value.name === 'Bash' || value.name === 'bash') {
52
+ const command = asRecord(value.input)?.command;
53
+ if (typeof command === 'string')
54
+ applyCommand(command, facts);
55
+ }
56
+ }
57
+ else if ((value.type === 'tool_result' && value.is_error === true) ||
58
+ (typeof value.type === 'string' && value.type.endsWith('_tool_result_error')))
59
+ facts.failedToolCalls++;
60
+ else if (value.type === 'advisor_tool_result' && typeof value.tool_use_id === 'string')
61
+ advisorIds.add(value.tool_use_id);
62
+ }
63
+ }
64
+ }
65
+ facts.advisorCalls = advisorIds.size;
66
+ return facts;
67
+ }
@@ -0,0 +1,11 @@
1
+ import { type CodexSegment, type TokenTotals, type TranscriptFacts } from './shared.mts';
2
+ export declare function codexChildren(lines: string[], ownerPath?: string): Array<{
3
+ threadId: string;
4
+ agentPath: string;
5
+ }>;
6
+ export declare function codexIdentity(lines: string[]): {
7
+ threadId?: string;
8
+ agentPath: string;
9
+ };
10
+ export declare function withoutLeadingSessionMetadata(lines: string[]): string[];
11
+ export declare function computeCodex(lines: string[], subagents?: CodexSegment[], baseline?: TokenTotals): TranscriptFacts;
@@ -0,0 +1,174 @@
1
+ import { applyCommand, asNumber, asRecord, emptyFacts, emptyTokens, parseLines, } from './shared.mjs';
2
+ import { customExecCommands } from './javascript-command.mjs';
3
+ function usage(record) {
4
+ const payload = asRecord(record.payload);
5
+ const totals = asRecord(asRecord(payload?.info)?.total_token_usage);
6
+ if (record.type !== 'event_msg' || payload?.type !== 'token_count' || !totals)
7
+ return undefined;
8
+ return {
9
+ input: asNumber(totals.input_tokens),
10
+ output: asNumber(totals.output_tokens),
11
+ cacheRead: asNumber(totals.cached_input_tokens),
12
+ cacheCreation: 0,
13
+ };
14
+ }
15
+ function commands(payload) {
16
+ const raw = payload.type === 'function_call' ? payload.arguments : payload.input;
17
+ if (payload.type === 'local_shell_call') {
18
+ const input = asRecord(raw);
19
+ const value = asRecord(input?.action)?.command ?? input?.command;
20
+ if (typeof value === 'string')
21
+ return [value];
22
+ if (!Array.isArray(value))
23
+ return [];
24
+ return value.every((item) => typeof item === 'string') ? [value.join(' ')] : [];
25
+ }
26
+ const customExec = payload.type === 'custom_tool_call' && payload.name === 'exec';
27
+ if (!customExec && !['exec_command', 'bash', 'shell', 'Bash'].includes(String(payload.name)))
28
+ return [];
29
+ if (typeof raw !== 'string')
30
+ return [];
31
+ if (customExec)
32
+ return customExecCommands(raw);
33
+ try {
34
+ const value = asRecord(JSON.parse(raw));
35
+ const candidate = value?.cmd ?? value?.command;
36
+ return typeof candidate === 'string' ? [candidate] : [];
37
+ }
38
+ catch {
39
+ return [];
40
+ }
41
+ }
42
+ function hasFailedOutcome(value) {
43
+ if (Array.isArray(value))
44
+ return value.some(hasFailedOutcome);
45
+ const payload = asRecord(value);
46
+ if (!payload)
47
+ return false;
48
+ if (payload.type === 'input_text' && typeof payload.text === 'string') {
49
+ try {
50
+ return hasFailedOutcome(JSON.parse(payload.text));
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ }
56
+ if (payload.status === 'failed' || payload.status === 'error' || payload.is_error === true)
57
+ return true;
58
+ if ((typeof payload.exit_code === 'number' && payload.exit_code !== 0) ||
59
+ (typeof payload.exitCode === 'number' && payload.exitCode !== 0) ||
60
+ payload.success === false)
61
+ return true;
62
+ return [payload.output, payload.result, payload.metadata].some(hasFailedOutcome);
63
+ }
64
+ function isCall(payload) {
65
+ return (['function_call', 'custom_tool_call'].includes(String(payload.type)) ||
66
+ (typeof payload.type === 'string' && payload.type.endsWith('_call')));
67
+ }
68
+ function isCallOutcome(payload) {
69
+ return typeof payload.type === 'string' && payload.type.endsWith('_call_output');
70
+ }
71
+ function addDelta(current, previous, target) {
72
+ target.input += Math.max(0, current.input - previous.input);
73
+ target.output += Math.max(0, current.output - previous.output);
74
+ target.cacheRead += Math.max(0, current.cacheRead - previous.cacheRead);
75
+ return {
76
+ input: Math.max(current.input, previous.input),
77
+ output: Math.max(current.output, previous.output),
78
+ cacheRead: Math.max(current.cacheRead, previous.cacheRead),
79
+ cacheCreation: 0,
80
+ };
81
+ }
82
+ function applyRecords(records, facts, subagent, baseline = emptyTokens()) {
83
+ let previous = baseline;
84
+ const calls = new Set();
85
+ const failed = new Set();
86
+ let anonymousFailures = 0;
87
+ let previousCompaction;
88
+ for (const record of records) {
89
+ const payload = asRecord(record.payload);
90
+ if (!subagent && record.type === 'event_msg' && payload?.type === 'user_message')
91
+ facts.userPrompts++;
92
+ if (!subagent && record.type === 'event_msg' && payload?.type === 'agent_message')
93
+ facts.assistantResponses++;
94
+ const compaction = record.type === 'compacted'
95
+ ? 'top-level'
96
+ : record.type === 'event_msg' && payload?.type === 'context_compacted'
97
+ ? 'context-event'
98
+ : undefined;
99
+ if (compaction && previousCompaction !== undefined && previousCompaction !== compaction)
100
+ previousCompaction = undefined;
101
+ else if (compaction) {
102
+ facts.compactions++;
103
+ previousCompaction = compaction;
104
+ }
105
+ else
106
+ previousCompaction = undefined;
107
+ const totals = usage(record);
108
+ if (totals) {
109
+ previous = addDelta(totals, previous, subagent ? facts.subagentTokens : facts.tokens);
110
+ }
111
+ if (record.type !== 'response_item' || !payload)
112
+ continue;
113
+ const id = typeof payload.call_id === 'string'
114
+ ? payload.call_id
115
+ : typeof payload.id === 'string'
116
+ ? payload.id
117
+ : undefined;
118
+ if (isCall(payload)) {
119
+ if (!id || !calls.has(id)) {
120
+ facts.toolCalls++;
121
+ if (subagent)
122
+ facts.subagentToolCalls++;
123
+ if (id)
124
+ calls.add(id);
125
+ if (payload.name === 'advisor')
126
+ facts.advisorCalls++;
127
+ for (const rawCommand of commands(payload))
128
+ applyCommand(rawCommand, facts);
129
+ }
130
+ }
131
+ if ((isCall(payload) || isCallOutcome(payload)) && hasFailedOutcome(payload)) {
132
+ if (id)
133
+ failed.add(id);
134
+ else
135
+ anonymousFailures++;
136
+ }
137
+ }
138
+ facts.failedToolCalls += [...failed].filter((id) => calls.has(id)).length + anonymousFailures;
139
+ }
140
+ export function codexChildren(lines, ownerPath = '/root') {
141
+ const direct = new Map();
142
+ const base = ownerPath.replace(/\/$/, '');
143
+ for (const record of parseLines(lines)) {
144
+ const payload = asRecord(record.payload);
145
+ if (record.type !== 'event_msg' || payload?.type !== 'sub_agent_activity')
146
+ continue;
147
+ const threadId = payload.agent_thread_id;
148
+ const agentPath = payload.agent_path;
149
+ if (typeof threadId !== 'string' || typeof agentPath !== 'string')
150
+ continue;
151
+ const normalized = agentPath.replace(/\/$/, '');
152
+ if (normalized.startsWith(`${base}/`) && !normalized.slice(base.length + 1).includes('/'))
153
+ direct.set(threadId, normalized);
154
+ }
155
+ return [...direct].map(([threadId, agentPath]) => ({ threadId, agentPath }));
156
+ }
157
+ export function codexIdentity(lines) {
158
+ const payload = asRecord(parseLines(lines.filter(Boolean).slice(0, 1))[0]?.payload);
159
+ return {
160
+ ...(typeof payload?.id === 'string' ? { threadId: payload.id } : {}),
161
+ agentPath: typeof payload?.agent_path === 'string' ? payload.agent_path : '/root',
162
+ };
163
+ }
164
+ export function withoutLeadingSessionMetadata(lines) {
165
+ const content = lines.filter(Boolean);
166
+ return parseLines(content.slice(0, 1))[0]?.type === 'session_meta' ? content.slice(1) : content;
167
+ }
168
+ export function computeCodex(lines, subagents = [], baseline) {
169
+ const facts = emptyFacts();
170
+ applyRecords(parseLines(lines), facts, false, baseline);
171
+ for (const subagent of subagents)
172
+ applyRecords(parseLines(subagent.lines), facts, true, subagent.baseline);
173
+ return facts;
174
+ }
@@ -0,0 +1,4 @@
1
+ import type { TranscriptFacts } from './shared.mts';
2
+ export declare function sessionLabel(value: string): string;
3
+ export declare function formatTranscriptFacts(sessionId: string, facts: TranscriptFacts): string;
4
+ export declare const formatUnavailable: (reason: string) => string;
@@ -0,0 +1,22 @@
1
+ const LABEL_CHARACTER = /[^A-Za-z0-9._-]+/g;
2
+ export function sessionLabel(value) {
3
+ return value.replace(/\s+/g, '_').replace(LABEL_CHARACTER, '_').slice(0, 128) || 'transcript';
4
+ }
5
+ export function formatTranscriptFacts(sessionId, facts) {
6
+ return [
7
+ '=== Transcript Facts ===',
8
+ `Session: ${sessionLabel(sessionId)}`,
9
+ `User prompts: ${facts.userPrompts}`,
10
+ `Assistant responses: ${facts.assistantResponses}`,
11
+ `Tool calls: ${facts.toolCalls} (failed: ${facts.failedToolCalls})`,
12
+ `no-mistakes invocations: ${facts.noMistakesInvocations}`,
13
+ `advisor calls: ${facts.advisorCalls}`,
14
+ `Push commands attempted: ${facts.pushCommandAttempts}`,
15
+ `Compactions: ${facts.compactions}`,
16
+ `Tokens: input=${facts.tokens.input} output=${facts.tokens.output} cache_read=${facts.tokens.cacheRead} cache_creation=${facts.tokens.cacheCreation}`,
17
+ `Subagent tool calls: ${facts.subagentToolCalls}`,
18
+ `Subagent tokens: input=${facts.subagentTokens.input} output=${facts.subagentTokens.output} cache_read=${facts.subagentTokens.cacheRead} cache_creation=${facts.subagentTokens.cacheCreation}`,
19
+ '',
20
+ ].join('\n');
21
+ }
22
+ export const formatUnavailable = (reason) => `=== Transcript Facts ===\nStatus: unavailable (${reason.replace(/\s+/g, ' ').slice(0, 240)})\n`;
@@ -0,0 +1,22 @@
1
+ import { type CodexSegment, type TranscriptFacts } from './shared.mts';
2
+ export type { TokenTotals, TranscriptFacts } from './shared.mts';
3
+ export { codexChildren, codexIdentity } from './codex.mts';
4
+ export { formatTranscriptFacts, formatUnavailable } from './format.mts';
5
+ export type ResolveOptions = {
6
+ sessionId?: string;
7
+ jsonlPath?: string;
8
+ projectsDir?: string;
9
+ codexSessionsDir?: string;
10
+ grokSessionsDir?: string;
11
+ cwd?: string;
12
+ env?: NodeJS.ProcessEnv;
13
+ };
14
+ type TranscriptResolution = {
15
+ path: string;
16
+ sessionId: string;
17
+ } | {
18
+ error: string;
19
+ };
20
+ export declare function resolveTranscriptFile(options: ResolveOptions): TranscriptResolution;
21
+ export declare function computeTranscriptFacts(lines: string[], subagents?: Array<string[] | CodexSegment>): TranscriptFacts;
22
+ export declare function runRetrospectiveTranscript(options: ResolveOptions): Promise<string>;
@@ -0,0 +1,151 @@
1
+ import { existsSync, globSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { basename, dirname, join } from 'node:path';
5
+ import { codexChildren, codexIdentity, computeCodex, withoutLeadingSessionMetadata, } from './codex.mjs';
6
+ import { computeClaude } from './claude.mjs';
7
+ import { formatTranscriptFacts, formatUnavailable, sessionLabel } from './format.mjs';
8
+ import { emptyFacts, emptyTokens, hasMalformedInteriorRecord, parseLines, } from './shared.mjs';
9
+ export { codexChildren, codexIdentity } from './codex.mjs';
10
+ export { formatTranscriptFacts, formatUnavailable } from './format.mjs';
11
+ const SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
12
+ const MALFORMED_INTERIOR = 'malformed interior transcript record';
13
+ function globFrom(root, pattern) {
14
+ return globSync(pattern, { cwd: root }).map((path) => join(root, path));
15
+ }
16
+ export function resolveTranscriptFile(options) {
17
+ if (options.sessionId && !SESSION_ID.test(options.sessionId))
18
+ return { error: 'invalid session id format' };
19
+ if (options.jsonlPath) {
20
+ const filename = basename(options.jsonlPath, '.jsonl');
21
+ const fileSessionId = filename.slice(-36);
22
+ return {
23
+ path: options.jsonlPath,
24
+ sessionId: options.sessionId?.toLowerCase() ??
25
+ (SESSION_ID.test(fileSessionId) ? fileSessionId.toLowerCase() : sessionLabel(filename)),
26
+ };
27
+ }
28
+ const env = options.env ?? process.env;
29
+ const sessionId = options.sessionId ??
30
+ ['CODEX_THREAD_ID', 'CLAUDE_CODE_SESSION_ID', 'CURSOR_SESSION_ID', 'GROK_SESSION_ID']
31
+ .map((key) => env[key])
32
+ .find(Boolean);
33
+ if (!sessionId)
34
+ return {
35
+ error: 'no session id (pass --session-id or set CODEX_THREAD_ID, CLAUDE_CODE_SESSION_ID, CURSOR_SESSION_ID, or GROK_SESSION_ID)',
36
+ };
37
+ if (!SESSION_ID.test(sessionId))
38
+ return { error: 'invalid session id format' };
39
+ const normalizedSessionId = sessionId.toLowerCase();
40
+ const codex = options.codexSessionsDir ?? join(env.CODEX_HOME || join(homedir(), '.codex'), 'sessions');
41
+ const claude = options.projectsDir ?? join(env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), 'projects');
42
+ const grokHome = env.GROK_HOME || join(homedir(), '.grok');
43
+ const grok = options.grokSessionsDir ?? join(grokHome, 'sessions');
44
+ const encodedCwd = encodeURIComponent(options.cwd ?? process.cwd());
45
+ const grokExact = join(grok, encodedCwd, normalizedSessionId, 'updates.jsonl');
46
+ const grokPaths = existsSync(grokExact)
47
+ ? [grokExact]
48
+ : globFrom(grok, `*/${normalizedSessionId}/updates.jsonl`);
49
+ const codexPaths = globFrom(codex, `**/rollout-*-${normalizedSessionId}.jsonl`);
50
+ const claudePaths = globFrom(claude, `*/${normalizedSessionId}.jsonl`);
51
+ const paths = [...grokPaths, ...codexPaths, ...claudePaths].sort();
52
+ if (paths.length > 1)
53
+ return { error: `multiple transcripts found for session ${normalizedSessionId}` };
54
+ return paths[0]
55
+ ? { path: paths[0], sessionId: normalizedSessionId }
56
+ : { error: `no transcript found for session ${normalizedSessionId}` };
57
+ }
58
+ function schema(lines) {
59
+ const records = parseLines(lines);
60
+ const kinds = new Set(records
61
+ .map((record) => record.type === 'user' || record.type === 'assistant'
62
+ ? 'claude'
63
+ : record.type === 'session_meta' ||
64
+ record.type === 'event_msg' ||
65
+ record.type === 'response_item' ||
66
+ record.type === 'compacted'
67
+ ? 'codex'
68
+ : undefined)
69
+ .filter(Boolean));
70
+ return kinds.size === 1 ? [...kinds][0] : undefined;
71
+ }
72
+ export function computeTranscriptFacts(lines, subagents = []) {
73
+ const detected = schema(lines);
74
+ if (!detected)
75
+ return emptyFacts();
76
+ if (detected === 'claude')
77
+ return computeClaude([
78
+ lines,
79
+ ...subagents.map((value) => (Array.isArray(value) ? value : value.lines)),
80
+ ]);
81
+ if (subagents.some(Array.isArray))
82
+ throw new TypeError('Codex subagents must be segmented');
83
+ return computeCodex(lines, subagents);
84
+ }
85
+ async function readLines(path) {
86
+ return (await readFile(path, 'utf8').catch(() => undefined))?.split('\n');
87
+ }
88
+ function childPath(threadId, sessionsDir) {
89
+ if (!SESSION_ID.test(threadId))
90
+ return undefined;
91
+ const paths = globFrom(sessionsDir, `**/rollout-*-${threadId}.jsonl`).sort();
92
+ return paths.length === 1 ? paths[0] : undefined;
93
+ }
94
+ function matchesChildIdentity(lines, threadId, agentPath) {
95
+ const first = parseLines(lines.filter(Boolean).slice(0, 1))[0];
96
+ if (first?.type !== 'session_meta')
97
+ return true;
98
+ const identity = codexIdentity(lines);
99
+ const threadMatches = identity.threadId?.toLowerCase() === threadId.toLowerCase();
100
+ return threadMatches && identity.agentPath.replace(/\/$/, '') === agentPath;
101
+ }
102
+ async function codexSubagents(lines, sessionsDir, ownerPath, visited = new Set()) {
103
+ const result = [];
104
+ for (const edge of codexChildren(lines, ownerPath)) {
105
+ if (visited.has(edge.threadId))
106
+ continue;
107
+ visited.add(edge.threadId);
108
+ const path = childPath(edge.threadId, sessionsDir);
109
+ if (!path || !existsSync(path))
110
+ return undefined;
111
+ const child = await readLines(path);
112
+ if (!child ||
113
+ schema(child) !== 'codex' ||
114
+ hasMalformedInteriorRecord(child) ||
115
+ !matchesChildIdentity(child, edge.threadId, edge.agentPath))
116
+ return undefined;
117
+ const content = withoutLeadingSessionMetadata(child);
118
+ result.push({ lines: content, baseline: emptyTokens() });
119
+ const nested = await codexSubagents(content, sessionsDir, edge.agentPath, visited);
120
+ if (!nested)
121
+ return undefined;
122
+ result.push(...nested);
123
+ }
124
+ return result;
125
+ }
126
+ export async function runRetrospectiveTranscript(options) {
127
+ const resolved = resolveTranscriptFile(options);
128
+ if ('error' in resolved)
129
+ return formatUnavailable(resolved.error);
130
+ const lines = await readLines(resolved.path);
131
+ if (!lines)
132
+ return formatUnavailable('could not read transcript');
133
+ const detected = schema(lines);
134
+ if (!detected)
135
+ return formatUnavailable('unsupported or mixed transcript schema');
136
+ if (hasMalformedInteriorRecord(lines))
137
+ return formatUnavailable(MALFORMED_INTERIOR);
138
+ if (detected === 'claude') {
139
+ const directory = join(dirname(resolved.path), basename(resolved.path, '.jsonl'), 'subagents');
140
+ const subagents = await Promise.all(globFrom(directory, '*.jsonl').sort().map(readLines));
141
+ return formatTranscriptFacts(resolved.sessionId, computeTranscriptFacts(lines, subagents.filter((value) => value !== undefined && schema(value) === 'claude' && !hasMalformedInteriorRecord(value))));
142
+ }
143
+ const identity = codexIdentity(lines);
144
+ const codexHome = (options.env ?? process.env).CODEX_HOME;
145
+ const subagents = await codexSubagents(lines, options.codexSessionsDir ??
146
+ (options.jsonlPath ? dirname(resolved.path) : undefined) ??
147
+ join(codexHome || join(homedir(), '.codex'), 'sessions'), identity.agentPath, new Set([identity.threadId ?? resolved.sessionId].filter((threadId) => SESSION_ID.test(threadId))));
148
+ if (!subagents)
149
+ return formatUnavailable('could not resolve a referenced Codex child transcript');
150
+ return formatTranscriptFacts(resolved.sessionId, computeTranscriptFacts(withoutLeadingSessionMetadata(lines), subagents));
151
+ }
@@ -0,0 +1 @@
1
+ export declare function customExecCommands(raw: string): string[];
@@ -0,0 +1,25 @@
1
+ const EXEC_OBJECT = /tools\.exec_command\(\s*(\{(?:(?:\\.|[^{}"'`\\])|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|`(?:\\.|[^`\\])*`)*\})\s*\)/g;
2
+ const COMMAND_LITERAL = /(?:[,{])\s*(?:cmd|['"]cmd['"])\s*:\s*([`'"])((?:\\.|(?!\1)[^\\])*)\1/;
3
+ const INTERPOLATION = /(^|[^\\])\$\{/;
4
+ function decodeLiteral(quote, value) {
5
+ if (quote === '`' && INTERPOLATION.test(value))
6
+ return undefined;
7
+ return value.replace(/\\([\\'"`bnrtv])/g, (_, character) => ({
8
+ '\\': '\\',
9
+ "'": "'",
10
+ '"': '"',
11
+ '`': '`',
12
+ b: '\b',
13
+ n: '\n',
14
+ r: '\r',
15
+ t: '\t',
16
+ v: '\v',
17
+ })[character]);
18
+ }
19
+ export function customExecCommands(raw) {
20
+ return [...raw.matchAll(EXEC_OBJECT)].flatMap((call) => {
21
+ const match = call[1].match(COMMAND_LITERAL);
22
+ const command = match ? decodeLiteral(match[1], match[2]) : undefined;
23
+ return command === undefined ? [] : [command];
24
+ });
25
+ }
@@ -0,0 +1,31 @@
1
+ export type TokenTotals = {
2
+ input: number;
3
+ output: number;
4
+ cacheRead: number;
5
+ cacheCreation: number;
6
+ };
7
+ export type TranscriptFacts = {
8
+ userPrompts: number;
9
+ assistantResponses: number;
10
+ toolCalls: number;
11
+ failedToolCalls: number;
12
+ noMistakesInvocations: number;
13
+ advisorCalls: number;
14
+ pushCommandAttempts: number;
15
+ compactions: number;
16
+ tokens: TokenTotals;
17
+ subagentToolCalls: number;
18
+ subagentTokens: TokenTotals;
19
+ };
20
+ export type ParsedLine = Record<string, unknown>;
21
+ export type CodexSegment = {
22
+ lines: string[];
23
+ baseline: TokenTotals;
24
+ };
25
+ export declare const emptyTokens: () => TokenTotals;
26
+ export declare const emptyFacts: () => TranscriptFacts;
27
+ export declare function asRecord(value: unknown): ParsedLine | undefined;
28
+ export declare const asNumber: (value: unknown) => number;
29
+ export declare function parseLines(lines: string[]): ParsedLine[];
30
+ export declare function hasMalformedInteriorRecord(lines: string[]): boolean;
31
+ export declare function applyCommand(command: string, facts: TranscriptFacts): void;