SYM-93: add scriptable Codex PR preflight runner (#1245)

Co-authored-by: Steven Enamakel <31011319+senamakel@users.noreply.github.com>
Co-authored-by: WOZCODE <contact@withwoz.com>
Co-authored-by: sanil-23 <sanil@vezures.xyz>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Cyrus Gray <144336577+graycyrus@users.noreply.github.com>
Co-authored-by: CodeGhost21 <164498022+CodeGhost21@users.noreply.github.com>
Co-authored-by: oxoxDev <164490987+oxoxDev@users.noreply.github.com>
Co-authored-by: Mega Mind <146339422+M3gA-Mind@users.noreply.github.com>
Co-authored-by: Gaurang Patel <ptelgm.yt@gmail.com>
Co-authored-by: unn-Known1 <unn-known1@users.noreply.github.com>
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Steven Enamakel <senamakel@users.noreply.github.com>
Co-authored-by: Steven Enamakel's Droid <enamakel.agent@tinyhumans.ai>
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: senamakel-droid <281415773+senamakel-droid@users.noreply.github.com>
Co-authored-by: YellowSnnowmann <167776381+YellowSnnowmann@users.noreply.github.com>
Co-authored-by: Neil <neil@maha.xyz>
Co-authored-by: Neel Mistry <neelmistry@Neels-MacBook-Pro.local>
Co-authored-by: obchain <167975049+obchain@users.noreply.github.com>
Co-authored-by: Jwalin Shah <jshah1331@gmail.com>
This commit is contained in:
Jwalin Shah
2026-05-05 11:15:31 -07:00
committed by GitHub
co-authored by Steven Enamakel WOZCODE sanil-23 Claude Opus 4.7 Cyrus Gray CodeGhost21 oxoxDev Mega Mind Gaurang Patel unn-Known1 Steven Enamakel github-actions[bot] <github-actions[bot]@users.noreply.github.com> Cursor Agent Steven Enamakel Steven Enamakel's Droid google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> senamakel-droid YellowSnnowmann Neil Neel Mistry obchain Jwalin Shah
parent ea74183e4f
commit 932703f5f2
3 changed files with 167 additions and 0 deletions
@@ -4,6 +4,14 @@ Use this checklist for Codex web sessions, Linear-launched implementation agents
## Required Preflight
Run the scriptable preflight wrapper (recommended):
```bash
node scripts/codex-pr-preflight.mjs --strict-path --lightweight
```
Use `--lightweight` when you only need environment/repo checks plus changed-surface validation recommendations (it skips heavier runtime validations).
Run this before editing files:
```bash
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env node
import fs from 'node:fs';
import path from 'node:path';
import { execSync } from 'node:child_process';
const REQUIRED_FILES = ['AGENTS.md', 'docs/src/README.md', 'Cargo.toml', 'app/package.json'];
const APP_PATTERNS = [/^app\//, /^docs\//];
const ROOT_RUST_PATTERNS = [/^src\//, /^tests\//, /^Cargo\.toml$/, /^Cargo\.lock$/];
const TAURI_PATTERNS = [/^app\/src-tauri\//];
function hasPattern(files, patterns) {
return files.some((file) => patterns.some((pattern) => pattern.test(file)));
}
function runGit(command, repoRoot) {
return execSync(command, { cwd: repoRoot, encoding: 'utf8' }).trim();
}
function parseArgs(argv) {
return {
lightweight: argv.includes('--lightweight'),
strictPath: argv.includes('--strict-path'),
expectedPath: process.env.CODEX_EXPECT_REPO_PATH || '/workspace/openhuman',
};
}
function runCheck(label, ok, details = '') {
return { label, ok, details };
}
function summarize(checks) {
const failed = checks.filter((check) => !check.ok);
for (const check of checks) {
const prefix = check.ok ? 'PASS' : 'FAIL';
const details = check.details ? ` :: ${check.details}` : '';
console.log(`[${prefix}] ${check.label}${details}`);
}
return failed.length;
}
function recommendations(changedFiles, lightweight) {
const lines = [];
if (hasPattern(changedFiles, APP_PATTERNS)) {
lines.push('pnpm --filter openhuman-app format:check');
lines.push('pnpm typecheck');
lines.push('pnpm --dir app exec vitest run <changed-test-files> --config test/vitest.config.ts');
}
if (hasPattern(changedFiles, ROOT_RUST_PATTERNS)) {
lines.push('cargo fmt --manifest-path Cargo.toml --all --check');
if (!lightweight) lines.push('pnpm debug rust <test-filter>');
}
if (hasPattern(changedFiles, TAURI_PATTERNS)) {
lines.push('cargo fmt --manifest-path app/src-tauri/Cargo.toml --all --check');
}
return [...new Set(lines)];
}
function main() {
const options = parseArgs(process.argv.slice(2));
const repoRoot = process.cwd();
const checks = [];
checks.push(runCheck('working directory exists', fs.existsSync(repoRoot), repoRoot));
if (options.strictPath) {
checks.push(runCheck('expected repo path', path.resolve(repoRoot) === path.resolve(options.expectedPath), `expected ${options.expectedPath}, got ${repoRoot}`));
}
for (const file of REQUIRED_FILES) {
checks.push(runCheck(`required file: ${file}`, fs.existsSync(path.join(repoRoot, file))));
}
let branch = '';
let remotes = '';
let changed = '';
try {
branch = runGit('git branch --show-current', repoRoot);
checks.push(runCheck('branch naming convention', /^codex\/[A-Z]+-\d+[-a-z0-9]*$/i.test(branch), branch));
} catch (error) {
checks.push(runCheck('branch readable', false, String(error)));
}
try {
remotes = runGit('git remote -v', repoRoot);
if (!remotes) {
checks.push(runCheck('git remote configured (recommended)', true, 'no remotes configured in this checkout'));
} else {
const hasExpectedRemote = /(jwalin-shah\/openhuman|tinyhumansai\/openhuman)/.test(remotes);
checks.push(runCheck('expected git remote present', hasExpectedRemote));
}
} catch (error) {
checks.push(runCheck('git remote readable', false, String(error)));
}
try {
changed = runGit('git diff --name-only --diff-filter=ACMR HEAD', repoRoot);
checks.push(runCheck('changed files readable', true, changed || 'no changed files'));
} catch (error) {
checks.push(runCheck('changed files readable', false, String(error)));
}
const changedFiles = changed ? changed.split('\n').filter(Boolean) : [];
const commandRecommendations = recommendations(changedFiles, options.lightweight);
console.log('\nRecommended validation commands:');
if (commandRecommendations.length === 0) {
console.log('- (none) no changed files detected');
} else {
for (const cmd of commandRecommendations) console.log(`- ${cmd}`);
}
const failures = summarize(checks);
if (failures > 0) process.exit(1);
}
main();
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env node
import { mkdtempSync, mkdirSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
import { execSync } from 'node:child_process';
function run(cmd, cwd) {
return execSync(cmd, { cwd, stdio: 'pipe', encoding: 'utf8' });
}
function makeRepo(branchName) {
const dir = mkdtempSync(path.join(tmpdir(), 'codex-preflight-'));
mkdirSync(path.join(dir, 'docs/src'), { recursive: true });
mkdirSync(path.join(dir, 'app'), { recursive: true });
writeFileSync(path.join(dir, 'AGENTS.md'), '# test\n');
writeFileSync(path.join(dir, 'docs/src/README.md'), 'ok\n');
writeFileSync(path.join(dir, 'Cargo.toml'), '[package]\nname="x"\nversion="0.1.0"\n');
writeFileSync(path.join(dir, 'app/package.json'), '{"name":"x"}\n');
run('git init', dir);
run('git config user.email test@example.com', dir);
run('git config user.name test', dir);
run('git add .', dir);
run('git commit -m init', dir);
run(`git checkout -b ${branchName}`, dir);
run('git remote add origin git@github.com:jwalin-shah/openhuman.git', dir);
return dir;
}
const script = path.resolve('scripts/codex-pr-preflight.mjs');
const passRepo = makeRepo('codex/SYM-93-preflight');
run(`CODEX_EXPECT_REPO_PATH=${passRepo} node ${script} --strict-path --lightweight`, passRepo);
const failRepo = makeRepo('feature/not-codex');
let failed = false;
try {
run(`CODEX_EXPECT_REPO_PATH=${failRepo} node ${script} --strict-path --lightweight`, failRepo);
} catch {
failed = true;
}
if (!failed) {
throw new Error('Expected invalid branch naming to fail preflight');
}
console.log('codex preflight self-test passed');