μ‹œλ¦¬μ¦ˆ: oh-my-claudecode λ‚΄λΆ€ ꡬ쑰뢀터 ν”ŒλŸ¬κ·ΈμΈ κ°œλ°œκΉŒμ§€

이 μ‹œλ¦¬μ¦ˆλŠ” Claude Code ν”ŒλŸ¬κ·ΈμΈ μ‹œμŠ€ν…œμ˜ λ‚΄λΆ€ λ™μž‘μ„ λ‹¨κ³„λ³„λ‘œ ν•΄λΆ€ν•˜κ³ , μ΅œμ’…μ μœΌλ‘œ λ‚˜λ§Œμ˜ ν”ŒλŸ¬κ·ΈμΈμ„ 직접 κ°œλ°œν•˜λŠ” κ²ƒκΉŒμ§€ λ„λ‹¬ν•˜λŠ” 과정이닀.

νŽΈλ‚΄μš©ν•΅μ‹¬
1편Plugin Anatomyplugin.json ν•˜λ‚˜λ‘œ μ‹œμž‘ν•˜λŠ” ν”ŒλŸ¬κ·ΈμΈ λΌˆλŒ€
2편Hook System이벀트 β†’ stdin JSON β†’ 슀크립트 β†’ stdout JSON
3편Skill SystemSKILL.md둜 μ›Œν¬ν”Œλ‘œμš°λ₯Ό μ •μ˜ν•˜λŠ” 방법
4편Agent SystemTask tool둜 μœ„μž„λ˜λŠ” μ‹€μ œ μž‘μ—… μˆ˜ν–‰μž
5편MCP Tools BridgeLSP, AST λ“± μ»€μŠ€ν…€ 도ꡬ μ„œλ²„
6편 (λ³Έλ¬Έ)State & Lifecycle.omc/ μƒνƒœ 관리와 λͺ¨λ“œ 지속
7편Parallel OrchestrationUltrawork, Team, Autopilot, Ralph νŒ¨ν„΄

  • OMC의 State & Lifecycle은 λͺ¨λ“œ μƒνƒœ, μ„Έμ…˜ λ©”λͺ¨λ¦¬, ν”„λ‘œμ νŠΈ 지식을 .omc/ λ””λ ‰ν† λ¦¬μ—μ„œ κ΄€λ¦¬ν•˜λŠ” μƒνƒœ μ˜μ†ν™” μ‹œμŠ€ν…œ
  • keyword-detectorκ°€ λͺ¨λ“œλ₯Ό ν™œμ„±ν™”ν•˜λ©΄ .omc/state/에 μƒνƒœ νŒŒμΌμ„ κΈ°λ‘ν•˜κ³ , Stop hook(persistent-mode)이 μƒνƒœλ₯Ό 읽어 μž‘μ—… μ™„λ£ŒκΉŒμ§€ μ’…λ£Œλ₯Ό μ°¨λ‹¨ν•˜λŠ” ꡬ쑰
  • μ»¨ν…μŠ€νŠΈ μ••μΆ•(Compact) μ‹œ 핡심 정보가 μœ μ‹€λ˜λŠ” 문제λ₯Ό Notepad(3μ„Ήμ…˜ λ©”λͺ¨λ¦¬)와 Project Memory(ν”„λ‘œμ νŠΈ 지식 JSON)둜 ν•΄κ²°ν•˜λŠ” μ••μΆ• λ‚΄μ„± μ•„ν‚€ν…μ²˜

ν•΄λ‹Ή κ°œλ…μ΄ ν•„μš”ν•œ 이유

  • 5편(MCP Tools Bridge)μ—μ„œ state_read, state_write λ“± MCP λ„κ΅¬λ‘œ μƒνƒœλ₯Ό 읽고 μ“Έ 수 있게 λ˜μ—ˆμŒ
  • ν•˜μ§€λ§Œ **β€œμ–Έμ œ μƒνƒœλ₯Ό κΈ°λ‘ν•˜κ³ , μ–Έμ œ 읽고, λͺ¨λ“œ μ „ν™˜μ€ μ–΄λ–»κ²Œ 되며, μ»¨ν…μŠ€νŠΈ μ••μΆ• μ‹œ 정보λ₯Ό μ–΄λ–»κ²Œ λ³΄μ‘΄ν•˜λŠ”μ§€β€**κ°€ ν•„μš”
  • 이 νŒŒνŠΈλŠ” OMC의 μƒνƒœ 관리 μ „λž΅ β€” λͺ¨λ“œ λ ˆμ§€μŠ€νŠΈλ¦¬, μ’…λ£Œ 차단, μ••μΆ• λŒ€μ‘ λ©”λͺ¨λ¦¬μ˜ 전체 생λͺ…μ£ΌκΈ°λ₯Ό λ‹€λ£Έ

AS-IS

sequenceDiagram
    autonumber
    participant U as μ‚¬μš©μž
    participant CC as Claude Code

    U->>CC: "ralph둜 λ¦¬νŒ©ν„°λ§ ν•΄μ€˜"
    CC->>CC: μž‘μ—… μ‹œμž‘
    CC->>CC: 1단계 μ™„λ£Œ
    CC-->>U: 응닡 μ’…λ£Œ (μžλ™)
    Note over U: "아직 μ•ˆ λλ‚¬λŠ”λ°..."
    U->>CC: "κ³„μ†ν•΄μ€˜"
    CC->>CC: 이전 μ»¨ν…μŠ€νŠΈ μœ μ‹€
    Note over CC: 뭘 ν•˜κ³  μžˆμ—ˆλŠ”μ§€<br/>κΈ°μ–΅ λͺ» 함

TO-BE

sequenceDiagram
    autonumber
    participant KD as keyword-detector
    participant ST as .omc/state/
    participant PM as persistent-mode<br/>(Stop hook)
    participant CC as Claude Code

    KD->>ST: ralph-state.json 생성<br/>{ active: true, started_at }
    CC->>CC: μž‘μ—… μ§„ν–‰
    CC->>PM: Stop 이벀트 (응닡 μ’…λ£Œ μ‹œλ„)
    PM->>ST: readModeState("ralph")
    ST-->>PM: { active: true }
    PM-->>CC: { continue: false } β†’ μ’…λ£Œ 차단
    CC->>CC: μž‘μ—… 계속 μ§„ν–‰
    CC->>ST: state_write({ active: false })
    CC->>PM: Stop 이벀트
    PM-->>CC: { continue: true } β†’ μ’…λ£Œ ν—ˆμš©

.omc/ 디렉토리 ꡬ쑰

.omc/
β”œβ”€β”€ state/                          ← λͺ¨λ“œ μƒνƒœ 파일
β”‚   β”œβ”€β”€ ultrawork-state.json
β”‚   β”œβ”€β”€ ralph-state.json
β”‚   β”œβ”€β”€ team-state.json
β”‚   β”œβ”€β”€ autopilot-state.json
β”‚   β”œβ”€β”€ ultraqa-state.json
β”‚   β”œβ”€β”€ skill-active-state.json
β”‚   β”œβ”€β”€ last-tool-error.json        ← μΌμ‹œμ  (60초 TTL)
β”‚   β”œβ”€β”€ subagent-tracking.json      ← μΌμ‹œμ 
β”‚   β”œβ”€β”€ mission-state.json
β”‚   β”œβ”€β”€ checkpoints/                ← PreCompact 체크포인트
β”‚   β”œβ”€β”€ shared-memory/              ← μ—μ΄μ „νŠΈ κ°„ 곡유 μ €μž₯μ†Œ
β”‚   β”‚   └── {namespace}/{key}.json
β”‚   └── sessions/                   ← μ„Έμ…˜λ³„ 격리
β”‚       └── {sessionId}/
β”‚           β”œβ”€β”€ {mode}-state.json
β”‚           β”œβ”€β”€ cancel-signal.json
β”‚           └── {mode}-stop-breaker.json
β”œβ”€β”€ sessions/                       ← μ„Έμ…˜ μ’…λ£Œ μ‹œ μš”μ•½ 기둝
β”‚   └── {sessionId}.json
β”œβ”€β”€ notepad.md                      ← μ••μΆ• λ‚΄μ„± λ©”λͺ¨λ¦¬ (3μ„Ήμ…˜)
β”œβ”€β”€ project-memory.json             ← ν”„λ‘œμ νŠΈ 지식
β”œβ”€β”€ notepads/                       ← ν”Œλžœλ³„ μœ„μ¦ˆλ€ λ…ΈνŠΈνŒ¨λ“œ
β”‚   └── {planName}/
β”‚       β”œβ”€β”€ learnings.md
β”‚       β”œβ”€β”€ decisions.md
β”‚       β”œβ”€β”€ issues.md
β”‚       └── problems.md
β”œβ”€β”€ plans/                          ← κ³„νš λ¬Έμ„œ
β”œβ”€β”€ research/                       ← 쑰사 κ²°κ³Ό
β”œβ”€β”€ drafts/                         ← λ“œλž˜ν”„νŠΈ 파일
β”œβ”€β”€ logs/                           ← 둜그
β”œβ”€β”€ autopilot/                      ← Autopilot μŠ€νŽ™/ν”Œλžœ
└── skills/                         ← μŠ€ν‚¬ μ €μž₯μ†Œ

ensureAllOmcDirs()κ°€ state, plans, research, logs, notepads, draftsλ₯Ό μžλ™ μƒμ„±ν•œλ‹€. setup-init.mjsλŠ” μΆ”κ°€λ‘œ state/checkpoints도 보μž₯.

Mode Registry β€” λͺ¨λ“œ μƒνƒœ λ¨Έμ‹ 

5κ°€μ§€ μ‹€ν–‰ λͺ¨λ“œ (ExecutionMode)

src/hooks/mode-registry/types.ts에 μ •μ˜λœ 곡식 μ‹€ν–‰ λͺ¨λ“œ:

type ExecutionMode = 'autopilot' | 'team' | 'ralph' | 'ultrawork' | 'ultraqa';
stateDiagram-v2
    [*] --> inactive: κΈ°λ³Έ μƒνƒœ
    inactive --> active: ν‚€μ›Œλ“œ 감지 or Skill ν™œμ„±ν™”
    active --> active: μž‘μ—… μ§„ν–‰ 쀑 (Stop hook이 μ’…λ£Œ 차단)
    active --> inactive: μž‘μ—… μ™„λ£Œ or /cancel
    active --> stale: 24μ‹œκ°„ 이상 λΉ„ν™œμ„±

    state active {
        [*] --> running
        running --> verifying: 검증 단계 (ralph)
        verifying --> running: 검증 μ‹€νŒ¨ β†’ μž¬μž‘μ—…
        verifying --> done: 검증 톡과
    }
λͺ¨λ“œμƒνƒœ νŒŒμΌλ°°νƒ€μ ?μ„€λͺ…
autopilotautopilot-state.jsonYes (유일)전체 μžλ™ν™”
teamteam-state.jsonNoN worker 병렬
ralphralph-state.jsonNo검증 루프
ultraworkultrawork-state.jsonNo병렬 μ‹€ν–‰
ultraqaultraqa-state.jsonNoQA 사이클
  • 배타적 λͺ¨λ“œ: EXCLUSIVE_MODES 배열에 autopilot만 포함. canStartMode()κ°€ isModeActiveInAnySession()으둜 μ „ μ„Έμ…˜μ„ κ²€μ‚¬ν•˜μ—¬ λ™μ‹œ μ‹€ν–‰ 차단
  • ν•©μ„± κ°€λŠ₯ λͺ¨λ“œ: team, ralph, ultrawork, ultraqaλŠ” λ™μ‹œ ν™œμ„±ν™” κ°€λŠ₯

μ‹€ν–‰ λͺ¨λ“œκ°€ μ•„λ‹Œ μƒνƒœ 도ꡬ λͺ¨λ“œ

state-tools.ts의 STATE_TOOL_MODESμ—λŠ” μ‹€ν–‰ λͺ¨λ“œ 5개 외에 μΆ”κ°€ λͺ¨λ“œκ°€ μžˆλ‹€:

λͺ¨λ“œμ„±κ²©λΉ„κ³ 
ralplanμ‹€ν–‰ μ „ κ³„νš λͺ¨λ“œStop hookμ—μ„œ 별도 checkRalplan()으둜 처리 (μ΅œλŒ€ 30회 κ°•ν™”, 45λΆ„ TTL)
omc-teamsteam λͺ¨λ“œμ˜ μŠ€ν‚¬ μƒνƒœskill-stateμ—μ„œ protection 'none'으둜 등둝
deep-interview인터뷰 λͺ¨λ“œskill-stateμ—μ„œ protection 'heavy' (10회 κ°•ν™”, 30λΆ„ TTL)

이듀은 ExecutionMode νƒ€μž…μ— ν¬ν•¨λ˜μ§€ μ•ŠμœΌλ©°, Mode Registryκ°€ μ•„λ‹Œ 각자의 λ©”μ»€λ‹ˆμ¦˜μœΌλ‘œ μƒνƒœλ₯Ό κ΄€λ¦¬ν•œλ‹€.

λͺ¨λ“œ ν™œμ„±ν™” 경둜

graph TD
    UP["UserPromptSubmit"] --> KD["keyword-detector.mjs"]
    KD -->|"ralph, autopilot,<br/>ultrawork, ultraqa"| ACT["λͺ¨λ“œ ν™œμ„±ν™”"]
    KD -->|"ralplan gate 적용"| RG{"μœ νš¨ν•œ ν”„λ‘¬ν”„νŠΈ?<br/>(15단어 이상,<br/>νŒŒμΌκ²½λ‘œΒ·ν•¨μˆ˜λͺ… λ“±)"}
    RG -->|"No"| RP["ralplan으둜 λŒ€μ²΄"]
    RG -->|"Yes"| ACT

    SK["Skill 직접 호좜"] -->|"/team, /ralph λ“±"| ACT

    Note1["team ν‚€μ›Œλ“œ κ°μ§€λŠ”<br/>λͺ…μ‹œμ μœΌλ‘œ λΉ„ν™œμ„±ν™”λ¨<br/>(never-match regex)"]
    style Note1 fill:#ffd,stroke:#aa0

keyword-detectorκ°€ μ‹€ν–‰ ν‚€μ›Œλ“œλ₯Ό κ°μ§€ν•˜λ˜, applyRalplanGate()둜 ν”„λ‘¬ν”„νŠΈ ν’ˆμ§ˆμ„ κ²€μ¦ν•œλ‹€. 15단어 미만이고 ꡬ체적 μ‹ ν˜Έ(파일 경둜, ν•¨μˆ˜λͺ… λ“±)κ°€ μ—†μœΌλ©΄ μ‹€ν–‰ λͺ¨λ“œ λŒ€μ‹  ralplan(κ³„νš λͺ¨λ“œ)으둜 λŒ€μ²΄λœλ‹€.

team λͺ¨λ“œμ˜ ν‚€μ›Œλ“œ κ°μ§€λŠ” (?!x)x(μ ˆλŒ€ λ§€μΉ­ λΆˆκ°€ μ •κ·œμ‹)둜 μ„€μ •λ˜μ–΄ μŠ€ν‚¬ 직접 호좜(/team)으둜만 ν™œμ„±ν™” κ°€λŠ₯ν•˜λ‹€.

Stale State λ°©μ§€

clearStaleSessionDirs()κ°€ μ„Έμ…˜ 디렉토리λ₯Ό μ •λ¦¬ν•œλ‹€:

function clearStaleSessionDirs(
  cwd: string,
  maxAgeMs: number = 24 * 60 * 60 * 1000  // κΈ°λ³Έ 24μ‹œκ°„
): string[]

디렉토리 λ‚΄ κ°€μž₯ μ΅œμ‹  파일의 mtimeMsλ₯Ό κΈ°μ€€μœΌλ‘œ, 24μ‹œκ°„ 이상 κ²½κ³Όν•œ μ„Έμ…˜ 디렉토리λ₯Ό μ œκ±°ν•œλ‹€. 빈 λ””λ ‰ν† λ¦¬λŠ” μ¦‰μ‹œ 정리.

Persistent Mode β€” Stop Hook의 μ’…λ£Œ 차단

μ•„ν‚€ν…μ²˜

hooks.json의 Stop μ΄λ²€νŠΈμ— λ“±λ‘λœ 3개 ν›…:

  1. context-guard-stop.mjs (5초) β€” μ»¨ν…μŠ€νŠΈ κ°€λ“œ
  2. persistent-mode.cjs (10초) β€” 메인 μ’…λ£Œ 차단기
  3. code-simplifier.mjs (5초) β€” μ½”λ“œ λ‹¨μˆœν™”

μš°μ„ μˆœμœ„λ³„ 체크 μˆœμ„œ

checkPersistentModes()κ°€ λ‹€μŒ μˆœμ„œλ‘œ κ²€μ‚¬ν•œλ‹€:

graph TD
    START["Stop 이벀트 μˆ˜μ‹ "] --> CRIT{"Critical Context?<br/>(95%+ λ˜λŠ” 429/401/403<br/>λ˜λŠ” /cancel)"}
    CRIT -->|"Yes"| ALLOW["μ’…λ£Œ ν—ˆμš©<br/>{ continue: true }"]
    CRIT -->|"No"| CANCEL{"cancel-signal.json<br/>쑴재 + 유효?"}
    CANCEL -->|"Yes"| ALLOW
    CANCEL -->|"No"| P1["P1: Ralph 체크"]
    P1 -->|"ν™œμ„±"| BLOCK["μ’…λ£Œ 차단<br/>{ continue: false,<br/>message: '...' }"]
    P1 -->|"λΉ„ν™œμ„±"| P15["P1.5: Autopilot 체크"]
    P15 -->|"ν™œμ„±"| BLOCK
    P15 -->|"λΉ„ν™œμ„±"| P17["P1.7: Team Pipeline<br/>(max 20회, TTL 5λΆ„)"]
    P17 -->|"ν™œμ„±"| BLOCK
    P17 -->|"λΉ„ν™œμ„±"| P18["P1.8: Ralplan<br/>(max 30회, TTL 45λΆ„)"]
    P18 -->|"ν™œμ„±"| BLOCK
    P18 -->|"λΉ„ν™œμ„±"| P2["P2: Ultrawork 체크"]
    P2 -->|"ν™œμ„±"| BLOCK
    P2 -->|"λΉ„ν™œμ„±"| P3["P3: Skill Active State"]
    P3 -->|"ν™œμ„±"| BLOCK
    P3 -->|"λΉ„ν™œμ„±"| ALLOW

응닡 포맷

function createHookOutput(result: PersistentModeResult): {
  continue: boolean;
  message?: string;
} {
  return {
    continue: !result.shouldBlock,
    message: result.message || undefined
  };
}
상황응닡
μ’…λ£Œ 차단{ "continue": false, "message": "<계속 μž‘μ—… ν”„λ‘¬ν”„νŠΈ>" }
μ’…λ£Œ ν—ˆμš©{ "continue": true }

Circuit Breaker (Stop-Breaker)

각 λͺ¨λ“œλŠ” λ¬΄ν•œ 차단을 λ°©μ§€ν•˜κΈ° μœ„ν•œ 회둜 차단기λ₯Ό κ°€μ§„λ‹€:

λͺ¨λ“œμ΅œλŒ€ κ°•ν™” 횟수TTL
Team Pipeline20회5λΆ„
Ralplan30회45λΆ„
Deep-interview10회30λΆ„

Ralph의 특수 λ™μž‘

Ralph λͺ¨λ“œμ—μ„œ state.iteration >= state.max_iterations에 λ„λ‹¬ν•˜λ©΄ μ’…λ£Œν•˜μ§€ μ•Šκ³  ν•œλ„λ₯Ό 10 μΆ”κ°€ ν™•μž₯ν•œλ‹€:

state.max_iterations += 10;
writeRalphState(workingDir, state, sessionId);

Ralph의 μœ μΌν•œ μ’…λ£Œ 방법은 λͺ…μ‹œμ  /oh-my-claudecode:cancel λͺ…령이닀.

Notepad β€” μ»¨ν…μŠ€νŠΈ μ••μΆ• λŒ€μ‘ λ©”λͺ¨λ¦¬

Claude CodeλŠ” μ»¨ν…μŠ€νŠΈ μœˆλ„μš°κ°€ 가득 μ°¨λ©΄ Compact(μ••μΆ•)을 μˆ˜ν–‰ν•œλ‹€. 이전 λŒ€ν™”κ°€ μš”μ•½λ˜λ©΄μ„œ μ„ΈλΆ€ 정보가 μœ μ‹€λœλ‹€.

3μ„Ήμ…˜ ꡬ쑰

.omc/notepad.md의 κ³ μ • ꡬ쑰:

# Notepad
<!-- Auto-managed by OMC. Manual edits preserved in MANUAL section. -->
 
## Priority Context
<!-- ALWAYS loaded. Keep under 500 chars. Critical discoveries only. -->
 
## Working Memory
<!-- Session notes. Auto-pruned after 7 days. -->
 
## MANUAL
<!-- User content. Never auto-pruned. -->
μ„Ήμ…˜μ“°κΈ° λ°©μ‹μ΅œλŒ€ 크기생λͺ…μ£ΌκΈ°
Priority ContextREPLACE (ꡐ체)500자 ꢌμž₯ (hard limit 2000)영ꡬ μœ μ§€, SessionStart μ‹œ 항상 μ£Όμž…
Working MemoryAPPEND (μΆ”κ°€)4000자/ν•­λͺ©7일 ν›„ μžλ™ 정리
ManualAPPEND (μΆ”κ°€)4000자/ν•­λͺ©μžλ™ 정리 μ—†μŒ

전체 파일 크기 μ œν•œ: 8KB (maxTotalSize: 8192)

Working Memory ν•­λͺ©μ€ ### YYYY-MM-DD HH:MM ν—€λ”λ‘œ νƒ€μž„μŠ€νƒ¬ν”„κ°€ λΆ€μ—¬λœλ‹€.

μ••μΆ• λŒ€μ‘ 흐름

sequenceDiagram
    autonumber
    participant CC as Claude Code
    participant PC as pre-compact.mjs
    participant NP as .omc/notepad.md
    participant WD as .omc/notepads/{plan}/
    participant PM as project-memory-precompact.mjs
    participant CC2 as Claude Code (μ••μΆ• ν›„)

    CC->>CC: μ»¨ν…μŠ€νŠΈ μž„κ³„μΉ˜ 도달
    CC->>PC: PreCompact 이벀트
    PC->>PC: 체크포인트 JSON 생성<br/>(.omc/state/checkpoints/)
    PC->>WD: μœ„μ¦ˆλ€ λ…ΈνŠΈ 내보내기<br/>(learnings, decisions, issues)
    PC-->>CC: systemMessage: ν™œμ„± λͺ¨λ“œ + TODO μš”μ•½

    CC->>PM: PreCompact 이벀트
    PM-->>CC: systemMessage: project-memory.json μž¬μ£Όμž…

    CC->>CC: Compact μ‹€ν–‰ (이전 λŒ€ν™” μš”μ•½)

    CC2->>NP: notepad_read("priority")
    NP-->>CC2: Priority Context 볡원
    Note over CC2: μ••μΆ• μ „ 핡심 정보 볡원 성곡

Priority ContextλŠ” SessionStart μ‹œ μžλ™ μ£Όμž…λ˜μ–΄ μ„Έμ…˜μ΄ μ‹œμž‘λ  λ•Œλ§ˆλ‹€ 핡심 정보가 μ»¨ν…μŠ€νŠΈμ— λ‘œλ“œλœλ‹€. PreCompact hook은 체크포인트λ₯Ό μƒμ„±ν•˜κ³  ν”Œλžœλ³„ μœ„μ¦ˆλ€ λ…ΈνŠΈ(notepads/ ν•˜μœ„)λ₯Ό λ‚΄λ³΄λ‚΄λŠ” 역할이닀.

Project Memory β€” ν”„λ‘œμ νŠΈ 지식

데이터 ꡬ쑰

.omc/project-memory.json의 μ‹€μ œ μŠ€ν‚€λ§ˆ (src/hooks/project-memory/types.ts):

{
  "version": "1.0.0",
  "lastScanned": 1710849600000,
  "projectRoot": "/path/to/project",
  "techStack": {
    "languages": ["typescript"],
    "frameworks": ["express"],
    "buildTools": ["esbuild"]
  },
  "build": {
    "buildCommand": "npm run build",
    "testCommand": "npm test",
    "lintCommand": "npm run lint",
    "devCommand": "npm run dev",
    "scripts": {}
  },
  "conventions": {
    "namingStyle": "camelCase",
    "importStyle": "esm",
    "testPattern": "*.test.ts",
    "fileOrganization": "feature-based"
  },
  "structure": {},
  "customNotes": [],
  "directoryMap": {},
  "hotPaths": [],
  "userDirectives": []
}

ν•„λ“œλͺ… 주의: build (buildInfo), conventions (codeConventions)

ν•™μŠ΅ 경둜 3κ°€μ§€

graph TD
    SS["SessionStart<br/>session-start.mjs"] -->|"detectProjectEnvironment()"| PM[".omc/project-memory.json"]
    PT["PostToolUse<br/>project-memory-posttool.mjs"] -->|"learnFromToolOutput()"| PM
    PC["PreCompact<br/>project-memory-precompact.mjs"] -->|"formatContextSummary()"| SYS["systemMessage둜 μž¬μ£Όμž…"]

    subgraph "PostToolUse ν•™μŠ΅ λŒ€μƒ"
        R["Read/Edit/Write β†’ 파일 ν•«νŒ¨μŠ€"]
        G["Glob/Grep β†’ 디렉토리 ν•«νŒ¨μŠ€"]
        B["Bash β†’ λΉŒλ“œ/ν…ŒμŠ€νŠΈ λͺ…λ Ή, ν™˜κ²½ 힌트"]
        U["User λ©”μ‹œμ§€ β†’ μ§€μ‹œμ‚¬ν•­ 감지"]
    end

    PT --> R
    PT --> G
    PT --> B
    PT --> U
  • SessionStart: shouldRescan()이 true(24μ‹œκ°„ κ²½κ³Ό λ˜λŠ” 파일 미쑴재)이면 detectProjectEnvironment()둜 ν”„λ‘œμ νŠΈ μžλ™ 감지
  • PostToolUse: learnFromToolOutput()이 도ꡬ 좜λ ₯μ—μ„œ ν•«νŒ¨μŠ€, λΉŒλ“œ λͺ…λ Ή, ν™˜κ²½ 힌트λ₯Ό μΆ”μΆœ. customNotesλŠ” μ΅œλŒ€ 20개 (초과 μ‹œ κ°€μž₯ 였래된 ν•­λͺ© 제거)
  • PreCompact: formatContextSummary(memory)λ₯Ό systemMessage둜 λ°˜ν™˜ν•˜μ—¬ μ••μΆ• 후에도 ν”„λ‘œμ νŠΈ 지식이 μœ μ§€λ¨. userDirectivesμ—λŠ” β€œμ••μΆ• 후에도 λ°˜λ“œμ‹œ 따라야 함” μ§€μ‹œ 포함

Session Lifecycle 전체 흐름

graph TD
    A["SessionStart"] -->|"session-start.mjs (5s)<br/>project-memory-session.mjs (5s)<br/>setup-init.mjs (30s, matcher: init)<br/>setup-maintenance.mjs (60s, matcher: maintenance)"| B["초기 μ„€μ • μ™„λ£Œ"]
    B --> C["μ‚¬μš©μž μž…λ ₯"]
    C -->|"UserPromptSubmit"| D["keyword-detector.mjs (5s)<br/>skill-injector.mjs (3s)"]
    D --> E["μž‘μ—… μ§„ν–‰"]

    E -->|"PreToolUse"| F["pre-tool-enforcer.mjs (3s)"]
    F --> G["도ꡬ μ‹€ν–‰"]
    G -->|"PostToolUse"| H["post-tool-verifier.mjs (3s)<br/>project-memory-posttool.mjs (3s)"]
    H --> E

    G -->|"PostToolUseFailure"| HF["post-tool-use-failure.mjs (3s)"]
    HF --> E

    E -->|"SubagentStart"| SA["subagent-tracker.mjs start (3s)"]
    SA -->|"SubagentStop"| SB["subagent-tracker.mjs stop (5s)<br/>verify-deliverables.mjs (5s)"]

    E -->|"PreCompact"| I["pre-compact.mjs (10s)<br/>project-memory-precompact.mjs (5s)"]
    I -->|"Compact μ‹€ν–‰"| E

    E -->|"Stop"| J{"persistent-mode.cjs<br/>(10s)"}
    J -->|"continue: false"| K["μ’…λ£Œ 차단 β†’ 계속"]
    K --> E
    J -->|"continue: true"| L["μ’…λ£Œ ν—ˆμš©"]

    L -->|"SessionEnd"| M["session-end.mjs (10s)<br/>λ©”νŠΈλ¦­ 기둝 + μƒνƒœ 정리"]

λ‚˜λ§Œμ˜ ν”ŒλŸ¬κ·ΈμΈμ„ λ§Œλ“ λ‹€λ©΄

μ΅œμ†Œ μƒνƒœ κ΄€λ¦¬λ‘œ β€œμ™„λ£ŒκΉŒμ§€ λ©ˆμΆ”μ§€ μ•ŠλŠ”β€ λͺ¨λ“œλ₯Ό κ΅¬ν˜„ν•˜λŠ” νŒ¨ν„΄:

// μƒνƒœ 기둝 (keyword-detectorμ—μ„œ)
const state = {
  active: true,
  started_at: new Date().toISOString(),
  original_prompt: prompt
};
writeFileSync('.omc/state/my-mode-state.json', JSON.stringify(state));
// μƒνƒœ 읽기 (Stop hookμ—μ„œ)
const state = JSON.parse(readFileSync('.omc/state/my-mode-state.json'));
if (state.active) {
  // μ’…λ£Œ 차단: continue: falseκ°€ 핡심
  console.log(JSON.stringify({ continue: false, message: "μž‘μ—… 계속 μ§„ν–‰" }));
} else {
  console.log(JSON.stringify({ continue: true }));
}

μ°Έκ³  λ¬Έμ„œ

  • μ†ŒμŠ€: ~/Documents/study/oh-my-claudecode/src/hooks/mode-registry/
  • μ†ŒμŠ€: ~/Documents/study/oh-my-claudecode/src/hooks/persistent-mode/
  • μ†ŒμŠ€: ~/Documents/study/oh-my-claudecode/src/hooks/notepad/
  • μ†ŒμŠ€: ~/Documents/study/oh-my-claudecode/src/hooks/project-memory/
  • μ†ŒμŠ€: ~/Documents/study/oh-my-claudecode/src/hooks/pre-compact/