์‹œ๋ฆฌ์ฆˆ: oh-my-codex ์•„ํ‚คํ…์ฒ˜ ํ•ด๋ถ€

์ด ์‹œ๋ฆฌ์ฆˆ๋Š” OpenAI Codex CLI ํ™•์žฅ ๋Ÿฐํƒ€์ž„์ธ oh-my-codex(OMX)์˜ ๋‚ด๋ถ€ ๊ตฌ์กฐ๋ฅผ ๋‹จ๊ณ„๋ณ„๋กœ ํ•ด๋ถ€ํ•˜๋Š” ๊ณผ์ •์ด๋‹ค.

ํŽธ๋‚ด์šฉํ•ต์‹ฌ
0ํŽธOverview3-Plane ์•„ํ‚คํ…์ฒ˜, OMC์™€์˜ ์ฐจ์ด, ์ „์ฒด ํ๋ฆ„
1ํŽธCodex CLI FoundationCodex CLI ์ž์ฒด์˜ ๊ตฌ์กฐ์™€ ํ™•์žฅ ํฌ์ธํŠธ
2ํŽธOMX IntegrationOMX๊ฐ€ Codex์— ์–ด๋–ป๊ฒŒ ์—ฐ๊ฒฐ๋˜๋‚˜
3ํŽธSkill System์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์–ด๋–ป๊ฒŒ ์ •์˜ํ•˜๋‚˜
4ํŽธPrompt & Agent System์—์ด์ „ํŠธ๋Š” ๋ญ๊ณ  ์–ด๋–ป๊ฒŒ ์„ ํƒ๋˜๋‚˜
5ํŽธMCP Servers์–ด๋–ค MCP ๋„๊ตฌ๋ฅผ ์“ธ ์ˆ˜ ์žˆ๋‚˜
6ํŽธ (๋ณธ๋ฌธ)State & Lifecycle์ƒํƒœ๋ฅผ ์–ด๋–ป๊ฒŒ ์œ ์ง€ํ•˜๋‚˜
7ํŽธTeam OrchestrationTeam ๋ชจ๋“œ๋Š” ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋‚˜
8ํŽธNative & SparkRust ๋„ค์ดํ‹ฐ๋ธŒ ๋„๊ตฌ๋Š” ๋ญ”๊ฐ€

  • State & Lifecycle์€ OMX์˜ ๋ชจ๋“œ ์ƒํƒœ ๊ด€๋ฆฌ, ์„ธ์…˜ ์ƒ๋ช…์ฃผ๊ธฐ, ๊ณ„ํš ๊ฒŒ์ดํŠธ ์ „๋žต
  • .omx/ ๋””๋ ‰ํ† ๋ฆฌ์— ๋ชจ๋“œ ์ƒํƒœยท์„ธ์…˜ ๋ฉ”ํƒ€ยท๊ณ„ํš ์‚ฐ์ถœ๋ฌผยท๋กœ๊ทธ๋ฅผ ์˜์†ํ™”ํ•˜๋ฉฐ, PID ๊ธฐ๋ฐ˜ stale ๊ฐ์ง€์™€ atomic write๋กœ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์žฅํ•˜๋Š” ์ƒํƒœ ์ธํ”„๋ผ
  • 5ํŽธ์˜ MCP ์„œ๋ฒ„๊ฐ€ โ€œ์–ด๋–ป๊ฒŒ ์ฝ๊ณ  ์“ฐ๋Š”์ง€โ€ ๋ฅผ ๋‹ค๋ค˜๋‹ค๋ฉด, ์ด ํŒŒํŠธ๋Š” โ€œ์–ธ์ œ, ๋ฌด์—‡์„, ์–ด๋–ค ๊ทœ์น™์œผ๋กœโ€ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š”์ง€๋ฅผ ๋‹ค๋ฃจ๋Š” ๊ตฌ์กฐ

ํ•ด๋‹น ๊ฐœ๋…์ด ํ•„์š”ํ•œ ์ด์œ 

  • 5ํŽธ์—์„œ MCP ์„œ๋ฒ„๋กœ ์ƒํƒœ๋ฅผ ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Œ
  • ํ•˜์ง€๋งŒ โ€œ์–ธ์ œ ์ƒํƒœ๋ฅผ ๊ธฐ๋กํ•˜๊ณ , ๋ชจ๋“œ ์ „ํ™˜์€ ์–ด๋–ป๊ฒŒ ๋˜๋ฉฐ, ์„ธ์…˜ ๊ฐ„ ์ •๋ณด๋ฅผ ์–ด๋–ป๊ฒŒ ๋ณด์กดํ•˜๋Š”์ง€โ€ ๊ฐ€ ๋น ์ ธ ์žˆ์Œ
  • ์ด ํŒŒํŠธ๋Š” ๋ชจ๋“œ ์ƒํƒœ ๋จธ์‹ , ๋ฐฐํƒ€์ /ํ•ฉ์„ฑ ๊ทœ์น™, Ralph planning gate, ์„ธ์…˜ ๊ด€๋ฆฌ๋ฅผ ๋‹ค๋ฃจ์–ด ๊ทธ ๊ฐ„๊ทน์„ ๋ฉ”์›€

AS-IS (OMC โ€” Hook ๊ธฐ๋ฐ˜ ์ข…๋ฃŒ ์ฐจ๋‹จ)

sequenceDiagram
    autonumber
    participant CC as Claude Code
    participant SH as persistent-mode.mjs (Stop Hook)
    participant ST as .omc/state/

    CC->>CC: ์ž‘์—… ์™„๋ฃŒ ์‹œ๋„
    CC->>SH: Stop Hook ์ด๋ฒคํŠธ ๋ฐœ์ƒ
    SH->>ST: ํ™œ์„ฑ ๋ชจ๋“œ ํ™•์ธ
    alt ํ™œ์„ฑ ๋ชจ๋“œ ์กด์žฌ
        SH-->>CC: ์ข…๋ฃŒ ์ฐจ๋‹จ (stdout JSON ์ฃผ์ž…)
        Note over CC: Hook์ด ๊ฒฐ์ •๋ก ์ ์œผ๋กœ ์ข…๋ฃŒ๋ฅผ ์ฐจ๋‹จ
    else ํ™œ์„ฑ ๋ชจ๋“œ ์—†์Œ
        SH-->>CC: ์ข…๋ฃŒ ํ—ˆ์šฉ
    end

TO-BE (OMX โ€” AGENTS.md Continuation ์ง€์นจ)

sequenceDiagram
    autonumber
    participant CX as Codex CLI (LLM)
    participant AM as AGENTS.md
    participant ST as .omx/state/

    CX->>CX: ์ž‘์—… ์™„๋ฃŒ ์‹œ๋„
    CX->>AM: Continuation ์ง€์นจ ํ™•์ธ
    Note over AM: "no pending work, features working,<br/>tests passing, zero known errors,<br/>verification evidence collected. If not, continue."
    CX->>ST: state_read(mode) โ€” ํ™œ์„ฑ ๋ชจ๋“œ ํ™•์ธ
    alt ๋ฏธ์™„๋ฃŒ ์ž‘์—… ์กด์žฌ
        CX->>CX: LLM์ด ์ž๋ฐœ์ ์œผ๋กœ ์ž‘์—… ๊ณ„์†
        Note over CX: ํ™•๋ฅ ๋ก ์  โ€” LLM ํŒ๋‹จ์— ์˜์กด
    else ์™„๋ฃŒ ์กฐ๊ฑด ์ถฉ์กฑ
        CX->>ST: state_write(active: false, completed_at: ...)
    end

ํ•ต์‹ฌ ์ฐจ์ด: OMC๋Š” Stop Hook์œผ๋กœ ๊ฒฐ์ •๋ก ์ ์œผ๋กœ ์ข…๋ฃŒ๋ฅผ ์ฐจ๋‹จํ•˜์ง€๋งŒ, OMX๋Š” AGENTS.md Continuation ์ง€์นจ์œผ๋กœ LLM์ด ์ž๋ฐœ์ ์œผ๋กœ ๊ณ„์†ํ• ์ง€ ํŒ๋‹จํ•œ๋‹ค. ์ „ ๊ณ„์ธต์ด ํ™•๋ฅ ๋ก ์ ์ธ OMX ์•„ํ‚คํ…์ฒ˜์˜ ์ผ๊ด€๋œ ์„ค๊ณ„๋‹ค.

.omx/ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ

.omx/
โ”œโ”€โ”€ state/                          # ๋ชจ๋“œยท์„ธ์…˜ ์ƒํƒœ
โ”‚   โ”œโ”€โ”€ {mode}-state.json           # ๋ชจ๋“œ๋ณ„ ์ƒํƒœ (autopilot, ralph, team ๋“ฑ)
โ”‚   โ”œโ”€โ”€ session.json                # ํ˜„์žฌ ์„ธ์…˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ
โ”‚   โ”œโ”€โ”€ hud-state.json              # HUD ๋ฉ”ํŠธ๋ฆญ (์„ธ์…˜ ์‹œ์ž‘ ์‹œ ๋ฆฌ์…‹)
โ”‚   โ”œโ”€โ”€ skill-active-state.json     # ํ˜„์žฌ ํ™œ์„ฑ ์Šคํ‚ฌ ์ถ”์ 
โ”‚   โ”œโ”€โ”€ ralph-progress.json         # Ralph ์‹คํ–‰ ๋ ˆ์ €
โ”‚   โ””โ”€โ”€ sessions/
โ”‚       โ””โ”€โ”€ {sessionId}/            # ์„ธ์…˜ ์Šค์ฝ”ํ”„ ์ƒํƒœ
โ”‚           โ””โ”€โ”€ {mode}-state.json
โ”œโ”€โ”€ plans/                          # ๊ณ„ํš ์‚ฐ์ถœ๋ฌผ (Ralph planning gate)
โ”‚   โ”œโ”€โ”€ prd-*.md                    # PRD ๋ฌธ์„œ
โ”‚   โ””โ”€โ”€ test-spec-*.md              # ํ…Œ์ŠคํŠธ ์ŠคํŽ™
โ”œโ”€โ”€ specs/                          # ์ŠคํŽ™ ํŒŒ์ผ
โ”‚   โ””โ”€โ”€ deep-interview-*.md         # Deep interview ์‚ฐ์ถœ๋ฌผ
โ”œโ”€โ”€ logs/                           # ์„ธ์…˜ ํžˆ์Šคํ† ๋ฆฌยท์ผ์ผ ๋กœ๊ทธ
โ”‚   โ”œโ”€โ”€ session-history.jsonl       # ์ข…๋ฃŒ๋œ ์„ธ์…˜ ์•„์นด์ด๋ธŒ
โ”‚   โ”œโ”€โ”€ omx-YYYY-MM-DD.jsonl        # ์ผ์ผ ์ด๋ฒคํŠธ ๋กœ๊ทธ
โ”‚   โ””โ”€โ”€ turns-*.jsonl               # ํ„ด ๋กœ๊ทธ (trace ์„œ๋ฒ„์šฉ)
โ”œโ”€โ”€ metrics.json                    # ์„ธ์…˜ ๋ฉ”ํŠธ๋ฆญ (ํ† ํฐ, ํ„ด ์ˆ˜)
โ”œโ”€โ”€ project-memory.json             # ํ”„๋กœ์ ํŠธ ๋ฉ”๋ชจ๋ฆฌ (ํฌ๋กœ์Šค ์„ธ์…˜)
โ”œโ”€โ”€ notepad.md                      # ๋…ธํŠธํŒจ๋“œ
โ”œโ”€โ”€ setup-scope.json                # ์„ค์น˜ scope ๊ธฐ๋ก (user/project)
โ””โ”€โ”€ .gitignore                      # .omx/ ์ „์ฒด๋ฅผ gitignore ์ฒ˜๋ฆฌ

omx setup ์‹œ state/, plans/, logs/ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , .gitignore์— .omx/ ์—”ํŠธ๋ฆฌ๊ฐ€ ์ถ”๊ฐ€๋œ๋‹ค.

๋ชจ๋“œ ์ƒํƒœ ๋จธ์‹  โ€” ๋ฐฐํƒ€์  vs ํ•ฉ์„ฑ

7๊ฐœ ๋ชจ๋“œ ์ •์˜ (์†Œ์Šค: src/modes/base.ts)

export type ModeName =
  'autopilot' | 'autoresearch' | 'ralph' | 'ultrawork' |
  'team' | 'ultraqa' | 'ralplan';

๋ฐฐํƒ€์ (Exclusive) ๋ชจ๋“œ โ€” ๋™์‹œ ์‹คํ–‰ ๋ถˆ๊ฐ€

const EXCLUSIVE_MODES: ModeName[] = ['autopilot', 'autoresearch', 'ralph', 'ultrawork'];

๋ฐฐํƒ€์  ๋ชจ๋“œ ์‹œ์ž‘ ์‹œ assertModeStartAllowed()๊ฐ€ ๋‹ค๋ฅธ ๋ฐฐํƒ€์  ๋ชจ๋“œ์˜ ํ™œ์„ฑ ์ƒํƒœ๋ฅผ ํ™•์ธํ•œ๋‹ค. ์ถฉ๋Œ ์‹œ "Cannot start {mode}: {other} is already active. Run cancel first." ์—๋Ÿฌ๋ฅผ ๋˜์ง„๋‹ค.

๋ชจ๋“œ๋ฐฐํƒ€/ํ•ฉ์„ฑ๋น„๊ณ 
autopilot๋ฐฐํƒ€์ ์ „์ฒด ์ž๋™ํ™” ํŒŒ์ดํ”„๋ผ์ธ
ralph๋ฐฐํƒ€์ ๊ฒ€์ฆ ๋ฃจํ”„ (๋‚ด๋ถ€์ ์œผ๋กœ ultrawork ์œ„์ž„ ๊ฐ€๋Šฅ)
ultrawork๋ฐฐํƒ€์ ๋ณ‘๋ ฌ ์‹คํ–‰
autoresearch๋ฐฐํƒ€์ ์ž๋™ ๋ฆฌ์„œ์น˜
teamํ•ฉ์„ฑN worker ๋ณ‘๋ ฌ ์กฐ์œจ
ultraqaํ•ฉ์„ฑQA ์‚ฌ์ดํด
ralplanํ•ฉ์„ฑํ•ฉ์˜ ๊ธฐ๋ฐ˜ ๊ณ„ํš (planning gate)

3ํŽธ Skill Composition๊ณผ์˜ ๊ด€๊ณ„: ralph๊ฐ€ ultrawork๋ฅผ โ€œ๊ฐ์‹ธ๋Š”โ€ 3๊ณ„์ธต ํ•ฉ์„ฑ์€ ๋ชจ๋“œ ์ˆ˜์ค€์˜ ๋™์‹œ ํ™œ์„ฑ์ด ์•„๋‹ˆ๋‹ค. Ralph ๋ชจ๋“œ๊ฐ€ ํ™œ์„ฑ์ผ ๋•Œ ๋‚ด๋ถ€์ ์œผ๋กœ ultrawork์˜ ๋ณ‘๋ ฌ ์œ„์ž„์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด์ง€, ๋‘ ๋ชจ๋“œ๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ๋™์‹œ์— active ์ƒํƒœ๊ฐ€ ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋‹ค.

์ƒํƒœ ์ „์ด ๋‹ค์ด์–ด๊ทธ๋žจ

stateDiagram-v2
    [*] --> inactive

    inactive --> active: state_write(active: true)

    state active {
        [*] --> starting
        starting --> executing: phase ๋ณ€๊ฒฝ
        executing --> verifying: ์ž‘์—… ์™„๋ฃŒ, ๊ฒ€์ฆ ์‹œ์ž‘
        verifying --> fixing: ๊ฒ€์ฆ ์‹คํŒจ
        fixing --> executing: ์ˆ˜์ • ํ›„ ์žฌ์‹คํ–‰
        verifying --> complete: ๊ฒ€์ฆ ํ†ต๊ณผ
        executing --> failed: ์—๋Ÿฌ ๋ฐœ์ƒ
        fixing --> failed: ์—๋Ÿฌ ๋ฐœ์ƒ
    }

    active --> completed: state_write(active: false, completed_at: ...)
    active --> cancelled: cancel ์Šคํ‚ฌ ์‹คํ–‰

    completed --> [*]
    cancelled --> [*]

๋ชจ๋“œ ๋ผ์ดํ”„์‚ฌ์ดํด ๊ทœ์น™

AGENTS.md <state_management> ์„น์…˜์— ๋ช…์‹œ๋œ 4๋‹จ๊ณ„ ํ•„์ˆ˜ ๊ทœ์น™:

๋‹จ๊ณ„๋™์ž‘์˜ˆ์‹œ
1. ์‹œ์ž‘state_write โ€” ๋ชจ๋“œ ์ƒํƒœ ๊ธฐ๋กstate_write({mode: "ralph", active: true, started_at: ...})
2. ๋ณ€๊ฒฝํŽ˜์ด์ฆˆ/์ดํ„ฐ๋ ˆ์ด์…˜ ๋ณ€๊ฒฝ ์‹œ ์—…๋ฐ์ดํŠธstate_write({current_phase: "verifying", iteration: 2})
3. ์™„๋ฃŒcompleted_at ๋งˆํ‚น, active: falsestate_write({active: false, completed_at: "2026-..."})
4. ์ทจ์†Œ์ •๋ฆฌ ํ›„ ์ƒํƒœ ํด๋ฆฌ์–ดstate_clear({mode: "ralph"})

Ralph Phase Contract (์†Œ์Šค: src/ralph/contract.ts)

Ralph ๋ชจ๋“œ๋Š” 7๊ฐœ ์ •๊ทœ phase๋ฅผ ๊ฐ€์ง€๋ฉฐ, ๋ ˆ๊ฑฐ์‹œ ๋ณ„์นญ์ด ์ž๋™ ์ •๊ทœํ™”๋œ๋‹ค.

export const RALPH_PHASES = [
  'starting', 'executing', 'verifying', 'fixing',
  'complete', 'failed', 'cancelled'
] as const;
๋ ˆ๊ฑฐ์‹œ ๋ณ„์นญ์ •๊ทœํ™” ๋Œ€์ƒ
start, startedstarting
execution, executeexecuting
verify, verificationverifying
fixfixing
completedcomplete
fail, errorfailed
cancelcancelled

ํ„ฐ๋ฏธ๋„ phase: complete, failed, cancelled โ€” ์ด ์ƒํƒœ์— ๋„๋‹ฌํ•˜๋ฉด ๋ชจ๋“œ๊ฐ€ ๋น„ํ™œ์„ฑํ™”๋œ๋‹ค.

Mode Runtime Context (์†Œ์Šค: src/state/mode-state-context.ts)

๋ชจ๋“œ๊ฐ€ ํ™œ์„ฑํ™”๋  ๋•Œ TMUX pane ID๋ฅผ ์ž๋™ ์บก์ฒ˜ํ•˜์—ฌ ์ƒํƒœ์— ์ €์žฅํ•œ๋‹ค.

export function withModeRuntimeContext<T>(existing, next, options?) {
  // active ์ „ํ™˜ ์‹œ ํ™˜๊ฒฝ๋ณ€์ˆ˜์—์„œ TMUX_PANE ์บก์ฒ˜
  // ์ตœ์ดˆ 1ํšŒ๋งŒ ์ €์žฅ (์ดํ›„ ๋ฎ์–ด์“ฐ์ง€ ์•Š์Œ)
  // tmux_pane_id, tmux_pane_set_at ๊ธฐ๋ก
}

์ด ์ •๋ณด๋Š” team ๋ชจ๋“œ์—์„œ ์–ด๋–ค pane์ด ์–ด๋–ค ๋ชจ๋“œ๋ฅผ ์‹คํ–‰ ์ค‘์ธ์ง€ ์ถ”์ ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

Session ๊ด€๋ฆฌ (์†Œ์Šค: src/hooks/session.ts)

์„ธ์…˜ ์ƒ๋ช…์ฃผ๊ธฐ

sequenceDiagram
    autonumber
    participant H as ์‚ฌ๋žŒ
    participant CLI as omx launch
    participant SS as session.ts
    participant SF as .omx/state/session.json
    participant LG as .omx/logs/

    H->>CLI: omx launch
    CLI->>SS: writeSessionStart(cwd, sessionId)
    SS->>SF: session.json ์ƒ์„ฑ (pid, started_at, cwd)
    SS->>LG: omx-YYYY-MM-DD.jsonl์— session_start ๊ธฐ๋ก
    SS->>SS: resetSessionMetrics() โ€” metrics.json ์ดˆ๊ธฐํ™”

    Note over CLI: ์„ธ์…˜ ์ง„ํ–‰ ์ค‘...

    CLI->>SS: writeSessionEnd(cwd, sessionId)
    SS->>LG: session-history.jsonl์— ์•„์นด์ด๋ธŒ (started_at, ended_at)
    SS->>SF: session.json ์‚ญ์ œ
    SS->>LG: omx-YYYY-MM-DD.jsonl์— session_end ๊ธฐ๋ก

SessionState ์ธํ„ฐํŽ˜์ด์Šค

export interface SessionState {
  session_id: string;
  started_at: string;
  cwd: string;
  pid: number;
  platform?: NodeJS.Platform;
  pid_start_ticks?: number;    // Linux: /proc/PID/stat ํ•„๋“œ 20
  pid_cmdline?: string;        // Linux: /proc/PID/cmdline
}

Stale ์„ธ์…˜ ๊ฐ์ง€ โ€” PID ๊ธฐ๋ฐ˜ (์‹œ๊ฐ„ ์ œํ•œ ์—†์Œ)

export function isSessionStale(state: SessionState): boolean {
  // 1. PID liveness: process.kill(pid, 0)
  // 2. Linux ์ „์šฉ: /proc/PID/stat์˜ start ticks ๋น„๊ต
  //    โ†’ PID๊ฐ€ ์žฌ์‚ฌ์šฉ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ (๊ฐ™์€ ๋ฒˆํ˜ธ, ๋‹ค๋ฅธ ํ”„๋กœ์„ธ์Šค)
  // 3. macOS/Windows: PID liveness๋งŒ ํ™•์ธ
}

OMC์™€์˜ ์ฐจ์ด: OMC๋Š” 2์‹œ๊ฐ„ ํƒ€์ž„์•„์›ƒ์œผ๋กœ stale์„ ํŒ๋‹จํ–ˆ์ง€๋งŒ, OMX๋Š” PID ์ƒ์กด ์—ฌ๋ถ€๋งŒ ํ™•์ธํ•œ๋‹ค. ์†Œ์Šค ์ฃผ์„: โ€œNo age-based threshold: staleness is determined by PID liveness/identity. Long-running sessions (>2h) are legitimate and should not be reaped.โ€

์„ธ์…˜ ๋ฉ”ํŠธ๋ฆญ ์ดˆ๊ธฐํ™” (resetSessionMetrics)

์„ธ์…˜ ์‹œ์ž‘ ์‹œ .omx/metrics.json์„ ๋ฆฌ์…‹ํ•œ๋‹ค:

{
  "total_turns": 0,
  "session_turns": 0,
  "last_activity": "2026-03-19T...",
  "session_input_tokens": 0,
  "session_output_tokens": 0,
  "session_total_tokens": 0
}

Ralph Planning Gate (์†Œ์Šค: src/planning/artifacts.ts)

๊ฒŒ์ดํŠธ ์กฐ๊ฑด

const PRD_PATTERN = /^prd-.*\.md$/i;
const TEST_SPEC_PATTERN = /^test-?spec-.*\.md$/i;
 
export function isPlanningComplete(artifacts: PlanningArtifacts): boolean {
  return artifacts.prdPaths.length > 0 && artifacts.testSpecPaths.length > 0;
}

์–‘์ชฝ ๋ชจ๋‘ ์กด์žฌํ•ด์•ผ ๊ฒŒ์ดํŠธ ํ•ด์ œ:

  • .omx/plans/prd-*.md โ€” ์ตœ์†Œ 1๊ฐœ
  • .omx/plans/test-spec-*.md (๋˜๋Š” testspec-*.md) โ€” ์ตœ์†Œ 1๊ฐœ

๊ฒŒ์ดํŠธ ๋™์ž‘ ํ๋ฆ„

graph TD
    RS["Ralph ํ™œ์„ฑ ์ƒํƒœ"]
    RS --> CHECK["isPlanningComplete() ํ™•์ธ"]
    CHECK --> |"PRD + test-spec ๋ชจ๋‘ ์กด์žฌ"| UNLOCK["UNLOCKED โ€” ๊ตฌํ˜„ ์‹œ์ž‘ ํ—ˆ์šฉ"]
    CHECK --> |"ํ•˜๋‚˜๋ผ๋„ ์—†์Œ"| BLOCK["BLOCKED โ€” ๊ตฌํ˜„ ์ฐจ๋‹จ"]
    BLOCK --> RP["$ralplan์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ<br/>๊ณ„ํš ๋จผ์ € ์ˆ˜ํ–‰"]
    RP --> |"๊ณ„ํš ์‚ฐ์ถœ๋ฌผ ์ƒ์„ฑ"| CHECK

    style UNLOCK fill:#d4edda
    style BLOCK fill:#f8d7da

์Šน์ธ๋œ ์‹คํ–‰ ํžŒํŠธ ์ถ”์ถœ

readApprovedExecutionLaunchHint()๊ฐ€ ์ตœ์‹  PRD ๋งˆํฌ๋‹ค์šด์—์„œ ์‹คํ–‰ ๋ช…๋ น์–ด๋ฅผ ํŒŒ์‹ฑํ•œ๋‹ค:

// PRD ๋‚ด ํŒจํ„ด ๋งค์นญ: "omx team 3:executor refactor API"
// โ†’ { workerCount: 3, agentRole: "executor", taskDescription: "refactor API" }

์ด๋ฅผ ํ†ตํ•ด planning gate ํ†ต๊ณผ ํ›„ ์ž๋™์œผ๋กœ ์ ์ ˆํ•œ ์‹คํ–‰ ๋ชจ๋“œ๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค.

Skill Active State โ€” ์Šคํ‚ฌ ํ™œ์„ฑํ™” ์ถ”์ 

skill-active-state.json (์†Œ์Šค: src/hooks/keyword-detector.ts)

export interface SkillActiveState {
  version: 1;
  active: boolean;
  skill: string;           // ํ™œ์„ฑ ์Šคํ‚ฌ ์ด๋ฆ„
  keyword: string;         // ํŠธ๋ฆฌ๊ฑฐํ•œ ํ‚ค์›Œ๋“œ
  phase: SkillActivePhase; // 'planning' | 'executing' | 'reviewing' | 'completing'
  activated_at: string;
  updated_at: string;
  source: 'keyword-detector';
  session_id?: string;
  input_lock?: DeepInterviewInputLock;  // Deep Interview ์ „์šฉ
}

recordSkillActivation()์ด ์‚ฌ์šฉ์ž ์ž…๋ ฅ์—์„œ ํ‚ค์›Œ๋“œ๋ฅผ ๊ฐ์ง€ํ•  ๋•Œ๋งˆ๋‹ค ์ด ํŒŒ์ผ์„ ์—…๋ฐ์ดํŠธํ•œ๋‹ค. ๊ฐ™์€ ์Šคํ‚ฌ/ํ‚ค์›Œ๋“œ๊ฐ€ ์ด๋ฏธ ํ™œ์„ฑ์ด๋ฉด activated_at์„ ๋ณด์กดํ•˜๊ณ , ์ƒˆ ์Šคํ‚ฌ์ด๋ฉด ์ƒˆ ํƒ€์ž„์Šคํƒฌํ”„๋ฅผ ๊ธฐ๋กํ•œ๋‹ค.

Deep Interview Input Lock

3ํŽธ์—์„œ ๋‹ค๋ฃฌ input lock์˜ ์ƒํƒœ ๊ด€๋ฆฌ ์ธก๋ฉด:

export interface DeepInterviewInputLock {
  active: boolean;
  scope: 'deep-interview-auto-approval';
  acquired_at: string;
  released_at?: string;
  exit_reason?: 'success' | 'error' | 'abort' | 'handoff';
  blocked_inputs: string[];  // ['yes', 'y', 'proceed', 'continue', 'ok', ...]
  message: string;
}
  • ํš๋“: deep-interview ์Šคํ‚ฌ ํ™œ์„ฑํ™” ์‹œ createDeepInterviewInputLock()
  • ํ•ด์ œ: cancel ํ‚ค์›Œ๋“œ ๊ฐ์ง€ ์‹œ ๋˜๋Š” ์ธํ„ฐ๋ทฐ ์™„๋ฃŒ ์‹œ releaseDeepInterviewInputLock(exit_reason)
  • ์ƒํƒœ ํŒŒ์ผ: skill-active-state.json์˜ input_lock ํ•„๋“œ์— ์˜์†ํ™”

OMC ์ƒํƒœ ๊ด€๋ฆฌ์™€์˜ ๋น„๊ต

ํ•ญ๋ชฉOMCOMX
์ƒํƒœ ๋””๋ ‰ํ† ๋ฆฌ.omc/state/.omx/state/
์ข…๋ฃŒ ์ฐจ๋‹จpersistent-mode.mjs Stop Hook (๊ฒฐ์ •๋ก ์ )AGENTS.md Continuation ์ง€์นจ (ํ™•๋ฅ ๋ก ์ )
Stale ๊ฐ์ง€2์‹œ๊ฐ„ ํƒ€์ž„์•„์›ƒPID liveness (์‹œ๊ฐ„ ์ œํ•œ ์—†์Œ)
Stale ํ”Œ๋žซํผ๋ฒ”์šฉLinux: /proc/PID/stat ํ”„๋กœ์„ธ์Šค ๋™์ผ์„ฑ ๊ฒ€์ฆ
Notepad.omc/notepad.md (3์„น์…˜)omx_memory MCP ์„œ๋ฒ„์˜ notepad_* ๋„๊ตฌ
Project Memory.omc/project-memory.jsonomx_memory MCP ์„œ๋ฒ„์˜ project_memory_* ๋„๊ตฌ
์„ธ์…˜ ์Šค์ฝ”ํ•‘.omc/state/sessions/{id}/.omx/state/sessions/{id}/ (๋™์ผ ํŒจํ„ด)
๋ชจ๋“œ ๋ฐฐํƒ€์„ฑ์Šคํ‚ฌ ์ˆ˜์ค€ ๊ด€๋ฆฌassertModeStartAllowed() ์ฝ”๋“œ ์ˆ˜์ค€ ๊ฐ•์ œ
์ƒํƒœ ์›์ž์„ฑ์ง์ ‘ ํŒŒ์ผ ์“ฐ๊ธฐatomic write (tmp + rename) + write-lock queue
Planning gate์—†์ŒisPlanningComplete() โ€” PRD + test-spec ํ•„์ˆ˜

์ฐธ๊ณ  ๋ฌธ์„œ