# How the oh-my-codex Planning Gate Enforces PRD and Test-Spec Before Ralph Execution

> Learn how the oh-my-codex planning gate ensures PRD and test-spec files exist before ralph execution, redirecting incomplete requests to ralplan automatically.

- Repository: [Bellman/oh-my-codex](https://github.com/Yeachan-Heo/oh-my-codex)
- Tags: how-to-guide
- Published: 2026-04-03

---

**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`](https://github.com/Yeachan-Heo/oh-my-codex/blob/main/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.

```ts
// 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.

```ts
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`](https://github.com/Yeachan-Heo/oh-my-codex/blob/main/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.

```ts
// 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`](https://github.com/Yeachan-Heo/oh-my-codex/blob/main/src/hooks/agents-overlay.ts). The `readRalphPlanningArtifacts` function checks artifact status, while `generateOverlay` injects a gate status line into every session header.

```ts
// 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`](https://github.com/Yeachan-Heo/oh-my-codex/blob/main/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.

```ts
// 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`:

```bash

# 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`](https://github.com/Yeachan-Heo/oh-my-codex/blob/main/src/planning/artifacts.ts), which validates the presence of `prd-*.md` and `test-spec-*.md` files using `isPlanningComplete`.
- **Keyword interception** happens in [`src/hooks/keyword-detector.ts`](https://github.com/Yeachan-Heo/oh-my-codex/blob/main/src/hooks/keyword-detector.ts) via `applyRalplanGate`, replacing `ralph` and `team` with `ralplan` when planning is incomplete.
- **Visual enforcement** renders in [`src/hooks/agents-overlay.ts`](https://github.com/Yeachan-Heo/oh-my-codex/blob/main/src/hooks/agents-overlay.ts), displaying **BLOCKED** or **UNLOCKED** status based on `readRalphPlanningArtifacts` results.
- **Test coverage** in [`src/hooks/__tests__/agents-overlay.test.ts`](https://github.com/Yeachan-Heo/oh-my-codex/blob/main/src/hooks/__tests__/agents-overlay.test.ts) guarantees 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`](https://github.com/Yeachan-Heo/oh-my-codex/blob/main/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`](https://github.com/Yeachan-Heo/oh-my-codex/blob/main/src/planning/artifacts.ts) for filesystem scanning, [`src/hooks/keyword-detector.ts`](https://github.com/Yeachan-Heo/oh-my-codex/blob/main/src/hooks/keyword-detector.ts) for keyword interception logic, and [`src/hooks/agents-overlay.ts`](https://github.com/Yeachan-Heo/oh-my-codex/blob/main/src/hooks/agents-overlay.ts) for visual status rendering in the user interface.