import { mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { spawnSync } from 'node:child_process'; import { createHash } from 'node:crypto'; import { tmpdir } from 'node:os'; const readInput = () => { try { const rawInput = readFileSync(0, 'utf8').trim(); return rawInput ? JSON.parse(rawInput) : {}; } catch { return {}; } }; const runGit = (args) => { const result = spawnSync('git', args, { encoding: 'utf8' }); return result.status === 0 ? result.stdout.split('\n').filter(Boolean) : []; }; const getStatePath = (input) => { const turnId = String(input.turn_id ?? 'unknown').replace(/[^a-zA-Z0-9_.-]/g, '_'); const cwdHash = createHash('sha256').update(process.cwd()).digest('hex').slice(0, 16); return join(tmpdir(), 'codex-fastboard-hooks', cwdHash, `${turnId}.json`); }; const isTypeScriptSource = (file) => /^src\/.+\.tsx?$/.test(file); const getDirtyTypeScriptFiles = () => { const files = [ ...runGit(['diff', '--name-only', '--diff-filter=ACMR']), ...runGit(['diff', '--cached', '--name-only', '--diff-filter=ACMR']), ...runGit(['ls-files', '--others', '--exclude-standard']), ]; return [...new Set(files)].filter(isTypeScriptSource).sort(); }; const getFileState = (file) => { try { const stat = statSync(file); return { mtimeMs: stat.mtimeMs, size: stat.size }; } catch { return null; } }; const input = readInput(); const statePath = getStatePath(input); const snapshot = Object.fromEntries( getDirtyTypeScriptFiles().map((file) => [file, getFileState(file)]), ); mkdirSync(dirname(statePath), { recursive: true }); writeFileSync(statePath, JSON.stringify(snapshot, null, 2));