transcripts.ts
93 lines · scripts/resolvers/transcripts.ts at 3f79d81
1/**
2 * Transcript resolvers for /context-restore's no-checkpoint fallback
3 * (context-restore/sections/transcripts.md.tmpl).
4 *
5 * {{TRANSCRIPT_SOURCES}} → the table of transcript stores for THIS host
6 * (its `transcripts` row from hosts/<name>.ts, or
7 * a none row) plus the Capy row (none; the harness
8 * keeps no transcript on the machine). Only the
9 * rendering host's paths appear: a Codex render
10 * never names ~/.claude/.
11 * {{TRANSCRIPT_DISCOVERY}} → the bash block that lists THIS host's transcript
12 * files for the current workspace only, inside the
13 * window, and prints `TRANSCRIPTS: none (...)` on
14 * Capy, on a host with no declared store, or when
15 * the store is missing.
16 *
17 * hosts/*.ts declares which hosts have a store (`transcripts.root` and
18 * `layout` feed the table); the per-host discovery body lives here, keyed by
19 * host name, and a host with no declaration gets the none branch. The prose
20 * never hand-writes a path. test/context-restore-transcripts.test.ts checks
21 * that the table and the block agree for every host and runs the block
22 * against fixture transcripts.
23 */
24import { getHostConfig } from '../../hosts/index';
25import type { TemplateContext } from './types';
27export const CAPY_NO_TRANSCRIPTS = 'TRANSCRIPTS: none (Capy keeps no session transcript on this machine; use the shared record and git)';
29export function generateTranscriptSources(ctx: TemplateContext): string {
30 const host = getHostConfig(ctx.host);
31 const row = host.transcripts
32 ? `| ${host.displayName} | \`${host.transcripts.root}\` | ${host.transcripts.layout} |`
33 : `| ${host.displayName} | none | this host declares no transcript store gstack may read; use the shared record and git |`;
34 return [
35 '| Host | Transcripts | How this workspace\'s files are told apart |',
36 '|---|---|---|',
37 row,
38 '| Capy (any model) | none | the harness keeps no transcript on the machine; say so and use the shared record and git |',
39 ].join('\n');
40}
42export function generateTranscriptDiscovery(ctx: TemplateContext): string {
43 const host = getHostConfig(ctx.host);
44 const slugBin = `${ctx.paths.binDir}/gstack-slug`;
45 const head = [
46 'TRANSCRIPT_DAYS="${TRANSCRIPT_DAYS:-7}"',
47 `PROJECT_ROOT=$(${slugBin} --get PROJECT_ROOT 2>/dev/null) || PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)`,
48 'echo "TRANSCRIPT_WINDOW: last $TRANSCRIPT_DAYS days"',
49 'echo "TRANSCRIPT_PROJECT: $PROJECT_ROOT"',
50 'if [ -n "${GSTACK_CAPY:-}" ]; then',
51 ` echo "${CAPY_NO_TRANSCRIPTS}"`,
52 ];
53 let body: string[];
54 if (!host.transcripts) {
55 body = [
56 'else',
57 ` echo "TRANSCRIPTS: none (${host.displayName} declares no transcript store gstack may read; use the shared record and git)"`,
58 'fi',
59 ];
60 } else if (ctx.host === 'claude') {
61 body = [
62 'else',
63 ' TRANSCRIPT_DIR="$HOME/.claude/projects/$(printf \'%s\' "$PROJECT_ROOT" | sed \'s/[^A-Za-z0-9]/-/g\')"',
64 ' if [ -d "$TRANSCRIPT_DIR" ]; then',
65 ' TRANSCRIPT_COUNT=0',
66 ' while IFS= read -r f; do TRANSCRIPT_COUNT=$((TRANSCRIPT_COUNT + 1)); echo "TRANSCRIPT: $f"; done < <(find "$TRANSCRIPT_DIR" -maxdepth 1 -name \'*.jsonl\' -mtime -"$TRANSCRIPT_DAYS" 2>/dev/null | sort)',
67 ' echo "TRANSCRIPTS: $TRANSCRIPT_DIR ($TRANSCRIPT_COUNT files in the last $TRANSCRIPT_DAYS days)"',
68 ' else',
69 ' echo "TRANSCRIPTS: none ($TRANSCRIPT_DIR is missing; this workspace has no Claude Code transcripts)"',
70 ' fi',
71 'fi',
72 ];
73 } else if (ctx.host === 'codex') {
74 body = [
75 'else',
76 ' TRANSCRIPT_DIR="$HOME/.codex/sessions"',
77 ' if [ -d "$TRANSCRIPT_DIR" ]; then',
78 ' TRANSCRIPT_COUNT=0',
79 ' while IFS= read -r f; do',
80 ' head -c 4096 "$f" 2>/dev/null | grep -qF -- "\\"cwd\\":\\"$PROJECT_ROOT\\"" || continue',
81 ' TRANSCRIPT_COUNT=$((TRANSCRIPT_COUNT + 1)); echo "TRANSCRIPT: $f"',
82 ' done < <(find "$TRANSCRIPT_DIR" -type f -name \'rollout-*.jsonl\' -mtime -"$TRANSCRIPT_DAYS" 2>/dev/null | sort)',
83 ' echo "TRANSCRIPTS: $TRANSCRIPT_DIR ($TRANSCRIPT_COUNT files whose cwd is this workspace, in the last $TRANSCRIPT_DAYS days)"',
84 ' else',
85 ' echo "TRANSCRIPTS: none ($TRANSCRIPT_DIR is missing; this machine has no Codex transcripts)"',
86 ' fi',
87 'fi',
88 ];
89 } else {
90 throw new Error(`hosts/${ctx.host}.ts declares a transcript store but scripts/resolvers/transcripts.ts has no discovery body for it`);
91 }
92 return ['```bash', ...head, ...body, '```'].join('\n');
93}