vouchington-tooling 0.0.22 → 0.1.1

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 (49) 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 +196 -0
  4. package/dist/browser-session-runner/index.d.mts +4 -0
  5. package/dist/browser-session-runner/index.mjs +68 -0
  6. package/dist/browser-session-runner/output.d.mts +8 -0
  7. package/dist/browser-session-runner/output.mjs +24 -0
  8. package/dist/browser-session-runner/process-group.d.mts +5 -0
  9. package/dist/browser-session-runner/process-group.mjs +23 -0
  10. package/dist/browser-session-runner/result.d.mts +2 -0
  11. package/dist/browser-session-runner/result.mjs +11 -0
  12. package/dist/browser-session-runner/tail-queue.d.mts +8 -0
  13. package/dist/browser-session-runner/tail-queue.mjs +30 -0
  14. package/dist/browser-session-runner/tail.d.mts +1 -0
  15. package/dist/browser-session-runner/tail.mjs +12 -0
  16. package/dist/browser-session-runner/types.d.mts +70 -0
  17. package/dist/browser-session-runner/types.mjs +1 -0
  18. package/dist/browser-session-runner/watchdog.d.mts +10 -0
  19. package/dist/browser-session-runner/watchdog.mjs +38 -0
  20. package/dist/cli/commands/retrospective-transcript.d.mts +1 -0
  21. package/dist/cli/commands/retrospective-transcript.mjs +31 -0
  22. package/dist/cli/index.mjs +3 -0
  23. package/dist/cli/parse.d.mts +3 -0
  24. package/dist/cli/parse.mjs +2 -0
  25. package/dist/cli/usage.d.mts +1 -1
  26. package/dist/cli/usage.mjs +2 -0
  27. package/dist/index.d.mts +6 -0
  28. package/dist/index.mjs +3 -0
  29. package/dist/retrospective-transcript/claude.d.mts +2 -0
  30. package/dist/retrospective-transcript/claude.mjs +67 -0
  31. package/dist/retrospective-transcript/codex-segment.d.mts +2 -0
  32. package/dist/retrospective-transcript/codex-segment.mjs +73 -0
  33. package/dist/retrospective-transcript/codex.d.mts +10 -0
  34. package/dist/retrospective-transcript/codex.mjs +170 -0
  35. package/dist/retrospective-transcript/format.d.mts +4 -0
  36. package/dist/retrospective-transcript/format.mjs +22 -0
  37. package/dist/retrospective-transcript/index.d.mts +22 -0
  38. package/dist/retrospective-transcript/index.mjs +157 -0
  39. package/dist/retrospective-transcript/javascript-command.d.mts +1 -0
  40. package/dist/retrospective-transcript/javascript-command.mjs +25 -0
  41. package/dist/retrospective-transcript/shared.d.mts +31 -0
  42. package/dist/retrospective-transcript/shared.mjs +213 -0
  43. package/dist/vitest-diagnostics/directory.d.mts +12 -0
  44. package/dist/vitest-diagnostics/directory.mjs +44 -0
  45. package/dist/vitest-diagnostics/index.d.mts +22 -0
  46. package/dist/vitest-diagnostics/index.mjs +123 -0
  47. package/dist/vitest-diagnostics/read-file.d.mts +2 -0
  48. package/dist/vitest-diagnostics/read-file.mjs +37 -0
  49. package/package.json +16 -1
@@ -47,6 +47,8 @@ export function parseCli(argv) {
47
47
  return { kind: 'stage-review-payload', args: rest };
48
48
  if (command === 'http-origin')
49
49
  return parseHttpOrigin(rest);
50
+ if (command === 'retrospective-transcript')
51
+ return { kind: 'retrospective-transcript', args: rest };
50
52
  if (command === 'gha-artifacts-cleanup')
51
53
  return parseGhaArtifactsCleanup(rest);
52
54
  if (command !== undefined && SCRIPT_COMMANDS.has(command)) {
@@ -1,2 +1,2 @@
1
- export declare const USAGE = "Usage: vouchington <command> [options]\n\nCommands:\n runner-port-policy Print or validate a runner port policy\n with-host-lock Run a command under a host-wide lock\n gha-runtime-audit Audit successful GitHub Actions job runtimes\n gha-output Write a collision-safe multiline GITHUB_OUTPUT record\n gha-needs-results Fail if required GitHub Actions job results failed\n download-with-diagnostics Download a URL and report HTTP status on failure\n host-pressure-diagnostics Print a bounded host memory/OOM/PSI snapshot\n allocate-browser-safe-ports Allocate Fetch-safe localhost ports\n diagnose-port-collision Capture bounded localhost port diagnostics\n prepare-trivy-db Download the Trivy vulnerability database\n gha-artifacts-cleanup Delete classified GitHub Actions artifacts\n http-origin Validate an optional HTTP(S) origin\n vitest-blob-manifest Stamp a vitest-blob-manifest:v1 identity file\n pnpm-install Install a pnpm workspace with retry and release-age fail-fast\n check-cache-size Measure a path and decide whether to save a GHA cache\n make-shard-matrix Emit a [1..N] GitHub Actions shard matrix\n load-runner-env Overlay a runner env file onto GITHUB_ENV with injection guards\n clean-workspace Reset a persistent-runner workspace with a fork-PR trust gate\n install-github-release Download a checksum-verified GitHub Release binary\n run-with-timeout Run a command with GNU timeout or a Perl fallback\n lint-links Two-pass lychee: internal links fail, external warn\n materialize-pr-context Dump PR title/body/files/diff/comments and #N crawl\n wait-for-apt-locks Wait until apt/dpkg lock files are free\n install-playwright-chromium-arm64 Install Playwright Chromium from browsers.json\n ghcr-package-retention Delete old GHCR package versions past KEEP_MIN\n nuget-central-version Validate a Directory.Packages.props PackageVersion delta\n swift-semantic-equal Compare Swift sources ignoring comments and whitespace\n post-review Post one COMMENT review from a staged payload file\n stage-review-payload Validate a review payload file into a staging directory\n\nOptions:\n -h, --help Show this help\n -v, --version Print the package version\n\nrunner-port-policy\n (no args) Print the shipped policy as JSON\n --file <path> Validate and print a policy file\n --reserved <port> Print true if the port is reserved\n\nwith-host-lock\n --name <family>\n [--slots <n>]\n --timeout-seconds <n>\n [--command-timeout-seconds <n>]\n [--failure-diagnostics <absolute-script>]\n [--on-acquire-timeout fail|run-unlocked]\n -- <command> [args...]\n\ngha-runtime-audit\n [--repository owner/name] Default GITHUB_REPOSITORY\n [--branch main]\n --pr-workflow <name|/regex/> Repeatable\n --push-workflow <name|/regex/> Repeatable\n\ngha-output <name>\ngha-needs-results [label]\ndownload-with-diagnostics <url> <destination> [-- curl-args...]\nhost-pressure-diagnostics\nallocate-browser-safe-ports [count] [--policy path] [--forbidden-ports path]\ndiagnose-port-collision [--ports \"2200 2216\"] [--output-dir PATH]\nprepare-trivy-db\ngha-artifacts-cleanup run --run-id <id> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\ngha-artifacts-cleanup sweep --older-than-hours <n> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\nhttp-origin [--field NAME] [value]\nvitest-blob-manifest <suite> [reports-directory]\npnpm-install --runner-lifecycle persistent|ephemeral|ephemeral-full --install-scripts true|false\ncheck-cache-size <path> <max-bytes> <label>\nmake-shard-matrix <total>\nload-runner-env\nclean-workspace\ninstall-github-release --repo owner/name --version X --asset 'name-{platform}.tar.gz' --bin name [--tag-prefix PREFIX] [--no-checksum] [--checksums-asset NAME] [--version-flag FLAG] [--bin-dir DIR]\nrun-with-timeout <timeout-seconds> <kill-after-seconds> <command...>\nlint-links [--offline] [--config PATH] [--glob PATTERN] [files...]\nmaterialize-pr-context\nwait-for-apt-locks\ninstall-playwright-chromium-arm64 [name:archive...]\nghcr-package-retention <url-encoded-package>...\nnuget-central-version <trusted-props> <candidate-props> <metadata-json> <output-props>\nswift-semantic-equal <base> <head> <file.swift>\npost-review\nstage-review-payload optional|required <source> <destination>\n";
1
+ export declare const USAGE = "Usage: vouchington <command> [options]\n\nCommands:\n runner-port-policy Print or validate a runner port policy\n with-host-lock Run a command under a host-wide lock\n gha-runtime-audit Audit successful GitHub Actions job runtimes\n gha-output Write a collision-safe multiline GITHUB_OUTPUT record\n gha-needs-results Fail if required GitHub Actions job results failed\n download-with-diagnostics Download a URL and report HTTP status on failure\n host-pressure-diagnostics Print a bounded host memory/OOM/PSI snapshot\n allocate-browser-safe-ports Allocate Fetch-safe localhost ports\n diagnose-port-collision Capture bounded localhost port diagnostics\n prepare-trivy-db Download the Trivy vulnerability database\n gha-artifacts-cleanup Delete classified GitHub Actions artifacts\n http-origin Validate an optional HTTP(S) origin\n vitest-blob-manifest Stamp a vitest-blob-manifest:v1 identity file\n pnpm-install Install a pnpm workspace with retry and release-age fail-fast\n check-cache-size Measure a path and decide whether to save a GHA cache\n make-shard-matrix Emit a [1..N] GitHub Actions shard matrix\n load-runner-env Overlay a runner env file onto GITHUB_ENV with injection guards\n clean-workspace Reset a persistent-runner workspace with a fork-PR trust gate\n install-github-release Download a checksum-verified GitHub Release binary\n run-with-timeout Run a command with GNU timeout or a Perl fallback\n lint-links Two-pass lychee: internal links fail, external warn\n materialize-pr-context Dump PR title/body/files/diff/comments and #N crawl\n wait-for-apt-locks Wait until apt/dpkg lock files are free\n install-playwright-chromium-arm64 Install Playwright Chromium from browsers.json\n ghcr-package-retention Delete old GHCR package versions past KEEP_MIN\n nuget-central-version Validate a Directory.Packages.props PackageVersion delta\n swift-semantic-equal Compare Swift sources ignoring comments and whitespace\n post-review Post one COMMENT review from a staged payload file\n stage-review-payload Validate a review payload file into a staging directory\n retrospective-transcript Format facts from Claude-compatible, Codex, or Grok transcripts\n\nOptions:\n -h, --help Show this help\n -v, --version Print the package version\n\nrunner-port-policy\n (no args) Print the shipped policy as JSON\n --file <path> Validate and print a policy file\n --reserved <port> Print true if the port is reserved\n\nwith-host-lock\n --name <family>\n [--slots <n>]\n --timeout-seconds <n>\n [--command-timeout-seconds <n>]\n [--failure-diagnostics <absolute-script>]\n [--on-acquire-timeout fail|run-unlocked]\n -- <command> [args...]\n\ngha-runtime-audit\n [--repository owner/name] Default GITHUB_REPOSITORY\n [--branch main]\n --pr-workflow <name|/regex/> Repeatable\n --push-workflow <name|/regex/> Repeatable\n\ngha-output <name>\ngha-needs-results [label]\ndownload-with-diagnostics <url> <destination> [-- curl-args...]\nhost-pressure-diagnostics\nallocate-browser-safe-ports [count] [--policy path] [--forbidden-ports path]\ndiagnose-port-collision [--ports \"2200 2216\"] [--output-dir PATH]\nprepare-trivy-db\ngha-artifacts-cleanup run --run-id <id> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\ngha-artifacts-cleanup sweep --older-than-hours <n> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\nhttp-origin [--field NAME] [value]\nvitest-blob-manifest <suite> [reports-directory]\npnpm-install --runner-lifecycle persistent|ephemeral|ephemeral-full --install-scripts true|false\ncheck-cache-size <path> <max-bytes> <label>\nmake-shard-matrix <total>\nload-runner-env\nclean-workspace\ninstall-github-release --repo owner/name --version X --asset 'name-{platform}.tar.gz' --bin name [--tag-prefix PREFIX] [--no-checksum] [--checksums-asset NAME] [--version-flag FLAG] [--bin-dir DIR]\nrun-with-timeout <timeout-seconds> <kill-after-seconds> <command...>\nlint-links [--offline] [--config PATH] [--glob PATTERN] [files...]\nmaterialize-pr-context\nwait-for-apt-locks\ninstall-playwright-chromium-arm64 [name:archive...]\nghcr-package-retention <url-encoded-package>...\nnuget-central-version <trusted-props> <candidate-props> <metadata-json> <output-props>\nswift-semantic-equal <base> <head> <file.swift>\npost-review\nstage-review-payload optional|required <source> <destination>\nretrospective-transcript [--session-id ID] [--jsonl PATH] [--projects-dir PATH] [--codex-sessions-dir PATH] [--grok-sessions-dir PATH]\n";
2
2
  export declare function printUsage(stream?: NodeJS.WritableStream): void;
@@ -30,6 +30,7 @@ Commands:
30
30
  swift-semantic-equal Compare Swift sources ignoring comments and whitespace
31
31
  post-review Post one COMMENT review from a staged payload file
32
32
  stage-review-payload Validate a review payload file into a staging directory
33
+ retrospective-transcript Format facts from Claude-compatible, Codex, or Grok transcripts
33
34
 
34
35
  Options:
35
36
  -h, --help Show this help
@@ -82,6 +83,7 @@ nuget-central-version <trusted-props> <candidate-props> <metadata-json> <output-
82
83
  swift-semantic-equal <base> <head> <file.swift>
83
84
  post-review
84
85
  stage-review-payload optional|required <source> <destination>
86
+ retrospective-transcript [--session-id ID] [--jsonl PATH] [--projects-dir PATH] [--codex-sessions-dir PATH] [--grok-sessions-dir PATH]
85
87
  `;
86
88
  export function printUsage(stream = process.stdout) {
87
89
  stream.write(USAGE);
package/dist/index.d.mts CHANGED
@@ -1,3 +1,5 @@
1
+ export { codexChildren, codexIdentity, computeTranscriptFacts, formatTranscriptFacts, formatUnavailable, resolveTranscriptFile, runRetrospectiveTranscript, } from './retrospective-transcript/index.mts';
2
+ export type { ResolveOptions, TokenTotals, TranscriptFacts, } from './retrospective-transcript/index.mts';
1
3
  export { EphemeralListenerAttemptsExhaustedError, isRunnerReservedPort, listenOnRunnerUnreservedEphemeralPort, loadRunnerPortPolicy, runnerPortPolicy, validateRunnerPortPolicy, } from './runner-port-policy/index.mts';
2
4
  export type { EphemeralListenerOptions, RunnerPortPolicy } from './runner-port-policy/index.mts';
3
5
  export { extractAlterTableAddColumnLocations, extractCreateIndexMetadata, extractCreateTableMetadata, extractDefaultFunction, extractDropIndexMetadata, extractFuncCallArgColumnNames, extractMigrationConstraintMetadata, initSqlAst, lineOfUtf8ByteOffset, MissingSqlAstParserError, parseSql, } from './sql-ast/index.mts';
@@ -20,6 +22,8 @@ export { createArtifactClassifier, parseArtifactPatternsJson, planRunDeletions,
20
22
  export type { ArtifactClassification, ArtifactClassifier, ArtifactPatterns, CleanupRequest, DeletionSummary, } from './gha-artifacts-cleanup/index.mts';
21
23
  export { validateOptionalHttpOrigin } from './http-origin/index.mts';
22
24
  export { boundPendingLine, DEFAULT_MAX_PENDING_LINE_LENGTH, DEFAULT_TRUNCATED_LINE_MARKER, splitCompleteLines, } from './process-line-buffer/index.mts';
25
+ export { ProcessGroupDrainTimeoutError, runBrowserSession, } from './browser-session-runner/index.mts';
26
+ export type { BrowserSessionDeps, BrowserSessionEvent, BrowserSessionExit, BrowserSessionOptions, BrowserSessionOutput, BrowserSessionProcess, BrowserSessionResult, BrowserSessionTerminationReason, BrowserSessionWatchdog, BrowserSessionWatchdogCleanup, BrowserSessionWatchdogController, } from './browser-session-runner/index.mts';
23
27
  export { buildSchemaSnapshot, detectRenamedIndexes, generateSchemaSnapshot, indexShapeKey, readSchemaCatalog, renderSchemaMarkdown, stableStringify, writeSchemaSnapshot, } from './pg-schema-snapshot/index.mts';
24
28
  export type { CatalogQuery, PartitionPolicy, SchemaCatalog, SchemaGrowthMaps, SchemaSnapshot, SchemaTableSnapshot, } from './pg-schema-snapshot/index.mts';
25
29
  export { buildOpenApiDocument, hashContractSchema, nodeToOpenApi, writeOpenApi, } from './openapi-document/index.mts';
@@ -56,3 +60,5 @@ export { normalizeSwiftSource } from './swift-semantic-equal/index.mts';
56
60
  export { isSwiftCodeOffset, parseUniqueSwiftBinaryTargetChecksum, } from './swift-source-offset/index.mts';
57
61
  export { validateResolvedPinDelta } from './swift-resolved-pin-delta/index.mts';
58
62
  export type { ResolvedDocument, ResolvedPin, ValidateResolvedPinDeltaOptions, } from './swift-resolved-pin-delta/index.mts';
63
+ export { DEFAULT_MAX_DIAGNOSTIC_REPORTS, DEFAULT_MAX_FORMATTED_DIAGNOSTIC_REPORTS, formatDiagnosticReportSummaries, HARD_MAX_DIAGNOSTIC_REPORTS, readDiagnosticReportSummaries, summarizeDiagnosticReport, } from './vitest-diagnostics/index.mts';
64
+ export type { DiagnosticReportLimitOptions, DiagnosticReportSummary, } from './vitest-diagnostics/index.mts';
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,2 @@
1
+ import { type CodexSegment } from './shared.mts';
2
+ export declare function segmentCodex(lines: string[]): CodexSegment | undefined;
@@ -0,0 +1,73 @@
1
+ import { asNumber, asRecord, emptyTokens, parseLines, } from './shared.mjs';
2
+ function hasInheritedParent(sessionMeta) {
3
+ const payload = asRecord(sessionMeta.payload);
4
+ return (asRecord(asRecord(payload?.source)?.subagent) !== undefined ||
5
+ typeof payload?.forked_from_id === 'string' ||
6
+ typeof payload?.parent_thread_id === 'string' ||
7
+ (typeof payload?.id === 'string' &&
8
+ typeof payload.session_id === 'string' &&
9
+ payload.id.toLowerCase() !== payload.session_id.toLowerCase()));
10
+ }
11
+ function isOwnedTaskStart(record, timestampMs) {
12
+ if (record.type !== 'event_msg')
13
+ return false;
14
+ const payload = asRecord(record.payload);
15
+ if (payload?.type !== 'task_started' || typeof payload.started_at !== 'number')
16
+ return false;
17
+ return payload.started_at >= 1_000_000_000_000
18
+ ? payload.started_at >= timestampMs
19
+ : payload.started_at >= Math.floor(timestampMs / 1000);
20
+ }
21
+ function usage(record) {
22
+ if (record.type !== 'event_msg')
23
+ return undefined;
24
+ const payload = asRecord(record.payload);
25
+ if (payload?.type !== 'token_count')
26
+ return undefined;
27
+ const totals = asRecord(asRecord(payload?.info)?.total_token_usage);
28
+ if (!totals)
29
+ return undefined;
30
+ return {
31
+ input: asNumber(totals.input_tokens),
32
+ output: asNumber(totals.output_tokens),
33
+ cacheRead: asNumber(totals.cached_input_tokens),
34
+ cacheCreation: 0,
35
+ };
36
+ }
37
+ function retainHighWater(previous, current) {
38
+ return {
39
+ input: Math.max(previous.input, current.input),
40
+ output: Math.max(previous.output, current.output),
41
+ cacheRead: Math.max(previous.cacheRead, current.cacheRead),
42
+ cacheCreation: 0,
43
+ };
44
+ }
45
+ export function segmentCodex(lines) {
46
+ const content = lines.filter(Boolean);
47
+ const sessionMeta = parseLines(content.slice(0, 1))[0];
48
+ if (sessionMeta?.type !== 'session_meta')
49
+ return { lines: content, baseline: emptyTokens() };
50
+ if (!hasInheritedParent(sessionMeta))
51
+ return { lines: content.slice(1), baseline: emptyTokens() };
52
+ const timestamp = asRecord(sessionMeta.payload)?.timestamp;
53
+ const timestampMs = typeof timestamp === 'string' ? Date.parse(timestamp) : Number.NaN;
54
+ if (!Number.isFinite(timestampMs))
55
+ return undefined;
56
+ const ownedIndex = content.findIndex((line, index) => {
57
+ if (index === 0)
58
+ return false;
59
+ const record = parseLines([line])[0];
60
+ return record ? isOwnedTaskStart(record, timestampMs) : false;
61
+ });
62
+ if (ownedIndex === -1)
63
+ return undefined;
64
+ let baseline;
65
+ for (const record of parseLines(content.slice(1, ownedIndex))) {
66
+ const current = usage(record);
67
+ if (current)
68
+ baseline = retainHighWater(baseline ?? emptyTokens(), current);
69
+ }
70
+ if (!baseline)
71
+ return undefined;
72
+ return { lines: content.slice(ownedIndex), baseline };
73
+ }
@@ -0,0 +1,10 @@
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 computeCodex(lines: string[], subagents?: CodexSegment[], baseline?: TokenTotals): TranscriptFacts;
@@ -0,0 +1,170 @@
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 computeCodex(lines, subagents = [], baseline) {
165
+ const facts = emptyFacts();
166
+ applyRecords(parseLines(lines), facts, false, baseline);
167
+ for (const subagent of subagents)
168
+ applyRecords(parseLines(subagent.lines), facts, true, subagent.baseline);
169
+ return facts;
170
+ }
@@ -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,157 @@
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 } from './codex.mjs';
6
+ import { segmentCodex } from './codex-segment.mjs';
7
+ import { computeClaude } from './claude.mjs';
8
+ import { formatTranscriptFacts, formatUnavailable, sessionLabel } from './format.mjs';
9
+ import { emptyFacts, hasMalformedInteriorRecord, parseLines, } from './shared.mjs';
10
+ export { codexChildren, codexIdentity } from './codex.mjs';
11
+ export { formatTranscriptFacts, formatUnavailable } from './format.mjs';
12
+ const SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
13
+ const MALFORMED_INTERIOR = 'malformed interior transcript record';
14
+ function globFrom(root, pattern) {
15
+ return globSync(pattern, { cwd: root }).map((path) => join(root, path));
16
+ }
17
+ export function resolveTranscriptFile(options) {
18
+ if (options.sessionId && !SESSION_ID.test(options.sessionId))
19
+ return { error: 'invalid session id format' };
20
+ if (options.jsonlPath) {
21
+ const filename = basename(options.jsonlPath, '.jsonl');
22
+ const fileSessionId = filename.slice(-36);
23
+ return {
24
+ path: options.jsonlPath,
25
+ sessionId: options.sessionId?.toLowerCase() ??
26
+ (SESSION_ID.test(fileSessionId) ? fileSessionId.toLowerCase() : sessionLabel(filename)),
27
+ };
28
+ }
29
+ const env = options.env ?? process.env;
30
+ const sessionId = options.sessionId ??
31
+ ['CODEX_THREAD_ID', 'CLAUDE_CODE_SESSION_ID', 'CURSOR_SESSION_ID', 'GROK_SESSION_ID']
32
+ .map((key) => env[key])
33
+ .find(Boolean);
34
+ if (!sessionId)
35
+ return {
36
+ error: 'no session id (pass --session-id or set CODEX_THREAD_ID, CLAUDE_CODE_SESSION_ID, CURSOR_SESSION_ID, or GROK_SESSION_ID)',
37
+ };
38
+ if (!SESSION_ID.test(sessionId))
39
+ return { error: 'invalid session id format' };
40
+ const normalizedSessionId = sessionId.toLowerCase();
41
+ const codex = options.codexSessionsDir ?? join(env.CODEX_HOME || join(homedir(), '.codex'), 'sessions');
42
+ const claude = options.projectsDir ?? join(env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude'), 'projects');
43
+ const grokHome = env.GROK_HOME || join(homedir(), '.grok');
44
+ const grok = options.grokSessionsDir ?? join(grokHome, 'sessions');
45
+ const encodedCwd = encodeURIComponent(options.cwd ?? process.cwd());
46
+ const grokExact = join(grok, encodedCwd, normalizedSessionId, 'updates.jsonl');
47
+ const grokPaths = existsSync(grokExact)
48
+ ? [grokExact]
49
+ : globFrom(grok, `*/${normalizedSessionId}/updates.jsonl`);
50
+ const codexPaths = globFrom(codex, `**/rollout-*-${normalizedSessionId}.jsonl`);
51
+ const claudePaths = globFrom(claude, `*/${normalizedSessionId}.jsonl`);
52
+ const paths = [...grokPaths, ...codexPaths, ...claudePaths].sort();
53
+ if (paths.length > 1)
54
+ return { error: `multiple transcripts found for session ${normalizedSessionId}` };
55
+ return paths[0]
56
+ ? { path: paths[0], sessionId: normalizedSessionId }
57
+ : { error: `no transcript found for session ${normalizedSessionId}` };
58
+ }
59
+ function schema(lines) {
60
+ const records = parseLines(lines);
61
+ const kinds = new Set(records
62
+ .map((record) => record.type === 'user' || record.type === 'assistant'
63
+ ? 'claude'
64
+ : record.type === 'session_meta' ||
65
+ record.type === 'event_msg' ||
66
+ record.type === 'response_item' ||
67
+ record.type === 'compacted'
68
+ ? 'codex'
69
+ : undefined)
70
+ .filter(Boolean));
71
+ return kinds.size === 1 ? [...kinds][0] : undefined;
72
+ }
73
+ export function computeTranscriptFacts(lines, subagents = []) {
74
+ const detected = schema(lines);
75
+ if (!detected)
76
+ return emptyFacts();
77
+ if (detected === 'claude')
78
+ return computeClaude([
79
+ lines,
80
+ ...subagents.map((value) => (Array.isArray(value) ? value : value.lines)),
81
+ ]);
82
+ if (subagents.some(Array.isArray))
83
+ throw new TypeError('Codex subagents must be segmented');
84
+ return computeCodex(lines, subagents);
85
+ }
86
+ async function readLines(path) {
87
+ return (await readFile(path, 'utf8').catch(() => undefined))?.split('\n');
88
+ }
89
+ function childPath(threadId, sessionsDir) {
90
+ if (!SESSION_ID.test(threadId))
91
+ return undefined;
92
+ const paths = globFrom(sessionsDir, `**/rollout-*-${threadId}.jsonl`).sort();
93
+ return paths.length === 1 ? paths[0] : undefined;
94
+ }
95
+ function matchesChildIdentity(lines, threadId, agentPath) {
96
+ const first = parseLines(lines.filter(Boolean).slice(0, 1))[0];
97
+ if (first?.type !== 'session_meta')
98
+ return true;
99
+ const identity = codexIdentity(lines);
100
+ const threadMatches = identity.threadId?.toLowerCase() === threadId.toLowerCase();
101
+ return threadMatches && identity.agentPath.replace(/\/$/, '') === agentPath;
102
+ }
103
+ async function codexSubagents(lines, sessionsDir, ownerPath, visited = new Set()) {
104
+ const result = [];
105
+ for (const edge of codexChildren(lines, ownerPath)) {
106
+ if (visited.has(edge.threadId))
107
+ continue;
108
+ visited.add(edge.threadId);
109
+ const path = childPath(edge.threadId, sessionsDir);
110
+ if (!path || !existsSync(path))
111
+ return undefined;
112
+ const child = await readLines(path);
113
+ if (!child ||
114
+ schema(child) !== 'codex' ||
115
+ hasMalformedInteriorRecord(child) ||
116
+ !matchesChildIdentity(child, edge.threadId, edge.agentPath))
117
+ return undefined;
118
+ const segment = segmentCodex(child);
119
+ if (!segment)
120
+ return undefined;
121
+ result.push(segment);
122
+ const nested = await codexSubagents(segment.lines, sessionsDir, edge.agentPath, visited);
123
+ if (!nested)
124
+ return undefined;
125
+ result.push(...nested);
126
+ }
127
+ return result;
128
+ }
129
+ export async function runRetrospectiveTranscript(options) {
130
+ const resolved = resolveTranscriptFile(options);
131
+ if ('error' in resolved)
132
+ return formatUnavailable(resolved.error);
133
+ const lines = await readLines(resolved.path);
134
+ if (!lines)
135
+ return formatUnavailable('could not read transcript');
136
+ const detected = schema(lines);
137
+ if (!detected)
138
+ return formatUnavailable('unsupported or mixed transcript schema');
139
+ if (hasMalformedInteriorRecord(lines))
140
+ return formatUnavailable(MALFORMED_INTERIOR);
141
+ if (detected === 'claude') {
142
+ const directory = join(dirname(resolved.path), basename(resolved.path, '.jsonl'), 'subagents');
143
+ const subagents = await Promise.all(globFrom(directory, '*.jsonl').sort().map(readLines));
144
+ return formatTranscriptFacts(resolved.sessionId, computeTranscriptFacts(lines, subagents.filter((value) => value !== undefined && schema(value) === 'claude' && !hasMalformedInteriorRecord(value))));
145
+ }
146
+ const identity = codexIdentity(lines);
147
+ const root = segmentCodex(lines);
148
+ if (!root)
149
+ return formatUnavailable('could not segment Codex transcript');
150
+ const codexHome = (options.env ?? process.env).CODEX_HOME;
151
+ const subagents = await codexSubagents(root.lines, options.codexSessionsDir ??
152
+ (options.jsonlPath ? dirname(resolved.path) : undefined) ??
153
+ join(codexHome || join(homedir(), '.codex'), 'sessions'), identity.agentPath, new Set([identity.threadId ?? resolved.sessionId].filter((threadId) => SESSION_ID.test(threadId))));
154
+ if (!subagents)
155
+ return formatUnavailable('could not resolve a referenced Codex child transcript');
156
+ return formatTranscriptFacts(resolved.sessionId, computeCodex(root.lines, subagents, root.baseline));
157
+ }
@@ -0,0 +1 @@
1
+ export declare function customExecCommands(raw: string): string[];