How the oh-my-codex Planning Gate Enforces PRD and Test-Spec Before Ralph Execution
The oh-my-codex planning gate blocks ralph and team execution modes unless both a Product Requirements Document (PRD) matching prd-*.md and a test specification matching test-spec-*.md exist in the .omx/plans directory, automatically redirecting underspecified requests to the ralplan planning mode instead.
The Yeachan-Heo/oh-my-codex repository implements a strict "plan-first" workflow that prevents the Ralph persistent execution loop from starting without proper documentation. This enforcement relies on a three-layer validation system that scans for planning artifacts, intercepts execution keywords, and renders real-time status overlays.
Artifact Discovery and Validation
The first layer operates in src/planning/artifacts.ts, where the readPlanningArtifacts function scans the workspace for required documentation. It searches the .omx/plans directory for files matching the regular expressions /^prd-.*\.md$/i and /^test-?spec-.*\.md$/i, returning paths to any discovered PRD and test-spec files.
// src/planning/artifacts.ts
export function readPlanningArtifacts(cwd: string): PlanningArtifacts {
const plansDir = omxPlansDir(cwd);
return {
plansDir,
specsDir: join(cwd, '.omx', 'specs'),
prdPaths: readMatchingPaths(plansDir, /^prd-.*\.md$/i),
testSpecPaths: readMatchingPaths(plansDir, /^test-?spec-.*\.md$/i),
deepInterviewSpecPaths: readMatchingPaths(
join(cwd, '.omx', 'specs'),
/^deep-interview-.*\.md$/i,
),
};
}
The isPlanningComplete function then validates that both arrays contain at least one entry, returning true only when both PRD and test-spec artifacts exist.
export function isPlanningComplete(artifacts: PlanningArtifacts): boolean {
return artifacts.prdPaths.length > 0 && artifacts.testSpecPaths.length > 0;
}
Without these files, the gate remains closed regardless of which execution keyword the user invokes.
Gate Evaluation and Keyword Replacement
The second layer resides in src/hooks/keyword-detector.ts within the applyRalplanGate function. This hook monitors for execution keywords—specifically ralph and team—and evaluates whether the current prompt is underspecified for immediate execution.
When isPlanningComplete returns false, the gate strips the execution keywords from the array and injects ralplan, forcing the user into planning mode before any autonomous work begins.
// src/hooks/keyword-detector.ts
export function applyRalplanGate(
keywords: string[],
text: string,
options: ApplyRalplanGateOptions = {},
) {
// ... early-exit checks omitted ...
const executionKeywords = keywords.filter(k => EXECUTION_GATE_KEYWORDS.has(k));
if (!executionKeywords.length) return { keywords, gateApplied: false, gatedKeywords: [] };
// Check if planning is complete
const planningComplete = isPlanningComplete(readPlanningArtifacts(options.cwd ?? process.cwd()));
// If incomplete, replace execution keywords with ralplan
if (!planningComplete) {
const filtered = keywords.filter(k => !EXECUTION_GATE_KEYWORDS.has(k));
if (!filtered.includes('ralplan')) filtered.push('ralplan');
return { keywords: filtered, gateApplied: true, gatedKeywords: executionKeywords };
}
return { keywords, gateApplied: false, gatedKeywords: [] };
}
This interception happens before the Ralph execution loop starts, ensuring that ralplan runs instead of ralph or team when documentation is absent.
Overlay Rendering and Visual Feedback
The third layer provides immediate visual feedback through src/hooks/agents-overlay.ts. The readRalphPlanningArtifacts function checks artifact status, while generateOverlay injects a gate status line into every session header.
// src/hooks/agents-overlay.ts
async function readRalphPlanningArtifacts(
cwd: string,
): Promise<{ hasPrd: boolean; hasTestSpec: boolean; complete: boolean }> {
const artifacts = readPlanningArtifacts(cwd);
return {
hasPrd: artifacts.prdPaths.length > 0,
hasTestSpec: artifacts.testSpecPaths.length > 0,
complete: isPlanningComplete(artifacts),
};
}
// Inside generateOverlay()
const planningArtifacts = await readRalphPlanningArtifacts(cwd);
if (ralphActive) {
const status = planningArtifacts.complete
? '**Ralph Ralplan‑First Gate:** UNLOCKED'
: '**Ralph Ralplan‑First Gate:** BLOCKED';
sections.push({ key: 'ralph_gate', text: status, optional: true });
}
When BLOCKED, the overlay displays the required glob patterns (prd-*.md and test-spec-*.md) so users know exactly which files to create.
End-to-End Verification
The repository includes comprehensive tests in src/hooks/__tests__/agents-overlay.test.ts that verify both gate states. One test confirms the BLOCKED status appears when artifacts are missing, while another validates the UNLOCKED state after creating the required files.
// src/hooks/__tests__/agents-overlay.test.ts
it('adds blocked ralph planning gate when PRD/test spec are missing', async () => {
const sessionId = 'ralph-gate-blocked';
const overlay = await generateOverlay(tempDir, sessionId);
assert.match(overlay, /\*\*Ralph Ralplan-First Gate:\*\* BLOCKED/);
assert.match(overlay, /`prd-\*\.md`/);
assert.match(overlay, /`test-spec-\*\.md`/);
});
it('unlocks ralph planning gate when PRD and test spec exist', async () => {
const sessionId = 'ralph-gate-unlocked';
// Files created: prd-issue-259.md and test-spec-issue-259.md
const overlay = await generateOverlay(tempDir, sessionId);
assert.match(overlay, /\*\*Ralph Ralplan-First Gate:\*\* UNLOCKED/);
});
To satisfy the gate manually, create the required files in .omx/plans:
# Create PRD artifact
echo "# PRD: Feature Implementation" > .omx/plans/prd-feature-001.md
# Create test specification artifact
echo "# Test Spec: Feature Implementation" > .omx/plans/test-spec-feature-001.md
Once both files exist, isPlanningComplete returns true, the overlay switches to UNLOCKED, and ralph or team keywords proceed to the execution loop.
Summary
- Artifact scanning occurs in
src/planning/artifacts.ts, which validates the presence ofprd-*.mdandtest-spec-*.mdfiles usingisPlanningComplete. - Keyword interception happens in
src/hooks/keyword-detector.tsviaapplyRalplanGate, replacingralphandteamwithralplanwhen planning is incomplete. - Visual enforcement renders in
src/hooks/agents-overlay.ts, displaying BLOCKED or UNLOCKED status based onreadRalphPlanningArtifactsresults. - Test coverage in
src/hooks/__tests__/agents-overlay.test.tsguarantees the gate behaves correctly in both locked and unlocked states.
Frequently Asked Questions
What file patterns does the oh-my-codex planning gate require?
The gate searches for files matching prd-*.md (case-insensitive) and test-spec-*.md (or testspec-*.md due to the optional hyphen in the regex /^test-?spec-.*\.md$/i) within the .omx/plans directory. Both patterns must match at least one file for isPlanningComplete to return true according to the source analysis.
How does the gate differentiate between Ralph and Team execution modes?
The applyRalplanGate function in src/hooks/keyword-detector.ts treats both ralph and team as execution keywords defined in EXECUTION_GATE_KEYWORDS. When planning is incomplete, both keywords are filtered from the array and replaced with ralplan, ensuring neither autonomous mode starts without documentation.
What happens if I try to run Ralph while the gate is blocked?
The system intercepts the request through applyRalplanGate and redirects to ralplan mode instead. The agent overlay displays BLOCKED status alongside the required file patterns, forcing you to create the PRD and test-spec artifacts before the ralph execution loop can begin.
Where does the planning gate check occur in the codebase?
The validation spans three specific locations: src/planning/artifacts.ts for filesystem scanning, src/hooks/keyword-detector.ts for keyword interception logic, and src/hooks/agents-overlay.ts for visual status rendering in the user interface.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →