์ด ๋ฌธ์๋ 2ํธ Hook System์ ํ์ ๊ฐ๋ ์ผ๋ก, 18๊ฐ Hook ์คํฌ๋ฆฝํธ ์ค
keyword-detector.mjs๋ฅผ ๋ค๋ฃฌ๋ค.
- keyword-detector.mjs๋
UserPromptSubmitHook์์ ์ฌ์ฉ์ ์ ๋ ฅ์ ๋ฐ์ ๋งค์ง ํค์๋๋ฅผ ๊ฐ์งํ๊ณ , ํด๋น Skill ํธ์ถ ์ง์๋ฅผ Claude context์ ์ฃผ์ ํ๋ ์คํฌ๋ฆฝํธ - 5๋จ๊ณ ํ์ดํ๋ผ์ธ: Guard โ Extract โ Sanitize โ Match โ Output
- 14๊ฐ ํค์๋๋ฅผ ๊ฐ์งํ๋ฉฐ, ๋ณต์ ํค์๋ ์ถฉ๋ ์ ์ฐ์ ์์ ๊ธฐ๋ฐ์ผ๋ก ํด๊ฒฐ
์์ฑ์ผ: 2026-03-14 ์์ค: keyword-detector.mjs
ํด๋น ๊ฐ๋ ์ด ํ์ํ ์ด์
- ์ฌ์ฉ์๊ฐ โultraworkโ, โralphโ ๊ฐ์ ๋งค์ง ํค์๋๋ฅผ ์ ๋ ฅํ๋ฉด OMC๊ฐ ์ด๋ฅผ ๊ฐ์งํด์ ์ ์ ํ Skill์ ๋ก๋ํด์ผ ํ๋ค
- Claude Code ์์ฒด์๋ ํค์๋ ๊ฐ์ง ๊ธฐ๋ฅ์ด ์์ผ๋ฏ๋ก,
UserPromptSubmitHook์ ์ด ์คํฌ๋ฆฝํธ๋ฅผ ๋ฑ๋กํ์ฌ ์ฒ๋ฆฌํ๋ค - ์ด ์คํฌ๋ฆฝํธ๋ hooks.json์์
UserPromptSubmit์ด๋ฒคํธ์ ์ฒซ ๋ฒ์งธ ์คํฌ๋ฆฝํธ๋ก ๋ฑ๋ก๋์ด ์์ด, ๋ชจ๋ ์ฌ์ฉ์ ์ ๋ ฅ์ ๋ํด ๊ฐ์ฅ ๋จผ์ ์คํ๋๋ค
AS-IS
sequenceDiagram autonumber participant U as User participant CC as Claude Code U->>CC: "ultrawork refactor the API" CC->>CC: "ultrawork"๋ฅผ ์ผ๋ฐ ํ ์คํธ๋ก ์ฒ๋ฆฌ CC-->>U: ํค์๋ ๋ฌด์, ์ผ๋ฐ ์๋ต
TO-BE
sequenceDiagram autonumber participant U as User participant CC as Claude Code box OMC participant KD as keyword-detector.mjs end U->>CC: "ultrawork refactor the API" CC->>KD: stdin: { prompt, cwd, session_id } KD->>KD: sanitize โ "ultrawork" ๋งค์นญ KD->>KD: activateState(.omc/state/ultrawork-state.json) KD-->>CC: { additionalContext: "[MAGIC KEYWORD: ULTRAWORK] Skill ํธ์ถ ์ง์" } CC->>CC: Skill tool ํธ์ถ โ ultrawork SKILL.md ๋ก๋
์ ์ฒด ํ์ดํ๋ผ์ธ (ํฐ ๊ทธ๋ฆผ)
graph TD A["stdin: JSON"] --> B["1. Guard"] B -->|"DISABLE_OMC=1 ๋๋<br/>OMC_TEAM_WORKER"| X1["{ continue: true } ์ฆ์ ๋ฐํ"] B -->|"ํต๊ณผ"| C["2. Extract"] C["extractPrompt()"] --> D["3. Sanitize"] D["sanitizeForKeywordDetection()"] --> E["4. Match"] E --> F{"๋งค์นญ ๊ฒฐ๊ณผ"} F -->|"0๊ฐ"| X2["{ continue: true } ํจ์ค"] F -->|"1๊ฐ ์ด์"| G["deduplicate โ resolveConflicts()"] G --> H["5. Output"] H --> H1["cancel โ clearStateFiles + Skill ํธ์ถ"] H --> H2["ralph/ultrawork ๋ฑ โ activateState + Skill ํธ์ถ"] H --> H3["ultrathink/tdd ๋ฑ โ ๋ฉ์์ง ์ง์ ์ฃผ์ (Skill ํธ์ถ ์์)"]
1. Guard โ ์คํ ์กฐ๊ฑด ๊ฒ์ฌ
์คํฌ๋ฆฝํธ ์ต์๋จ์์ ์คํ ์ฌ๋ถ๋ฅผ ํ๋จํ๋ 3๊ฐ์ง ๊ฐ๋:
// Guard 1: ํ๊ฒฝ๋ณ์๋ก ์์ ๋นํ์ฑํ
if (process.env.DISABLE_OMC === '1' || _skipHooks.includes('keyword-detector')) {
return { continue: true };
}
// Guard 2: Team Worker ๋ด๋ถ์์๋ ํค์๋ ๊ฐ์ง ๊ธ์ง
// โ "team" ํค์๋๊ฐ ๋ค์ ๊ฐ์ง๋๋ฉด ๋ฌดํ ์คํฐ ๋ฃจํ ๋ฐ์
if (process.env.OMC_TEAM_WORKER) {
return { continue: true, suppressOutput: true };
}
// Guard 3: ๋น ์
๋ ฅ
if (!input.trim()) {
return { continue: true, suppressOutput: true };
}Team Worker Guard๋ ์ค์ ๋ฒ๊ทธ ๋ฐฉ์ง๋ฅผ ์ํ ๊ฒ์ด๋ค. Team ๋ชจ๋์์ ์์ฑ๋ worker๊ฐ ์ฌ์ฉ์ ํ๋กฌํํธ๋ฅผ ๋ค์ ๋ฐ์ผ๋ฉด, โteamโ์ด๋ผ๋ ํค์๋๋ฅผ ์ฌ๊ฐ์งํ์ฌ ๋ ๋ค๋ฅธ worker๋ฅผ ์์ฑํ๋ ๋ฌดํ ๋ฃจํ์ ๋น ์ง ์ ์๋ค.
2. Extract โ ํ๋กฌํํธ ์ถ์ถ
extractPrompt()๋ Claude Code๊ฐ ๋ณด๋ด๋ ๋ค์ํ JSON ๊ตฌ์กฐ์์ ์ฌ์ฉ์ ํ
์คํธ๋ฅผ ์ถ์ถํ๋ค:
function extractPrompt(input) {
const data = JSON.parse(input);
if (data.prompt) return data.prompt; // ์ผ๋ฐ ํํ
if (data.message?.content) return data.message.content; // ๋ฉ์์ง ํํ
if (Array.isArray(data.parts)) { // ๋ฉํฐํํธ ํํ
return data.parts.filter(p => p.type === 'text').map(p => p.text).join(' ');
}
return '';
}ํ์ฑ ์คํจ ์ ๋น ๋ฌธ์์ด์ ๋ฐํํ๋ค (fail closed โ ์๋ชป๋ ์ ๋ ฅ์์ false positive ๋ฐฉ์ง).
3. Sanitize โ ์คํ ๋ฐฉ์ง
sanitizeForKeywordDetection()์ ํค์๋๊ฐ ์๋ ๊ณณ์์์ ์คํ์ ๋ฐฉ์งํ๊ธฐ ์ํด 6๋จ๊ณ๋ก ํ
์คํธ๋ฅผ ์ ๋ฆฌํ๋ค:
| ์์ | ์ ๊ฑฐ ๋์ | ์ด์ |
|---|---|---|
| 1 | XML ํ๊ทธ ๋ธ๋ก <tag>...</tag> | ํ๋กฌํํธ ๋ด XML ๊ตฌ์กฐ์์ ํค์๋ ์คํ |
| 2 | Self-closing XML <tag /> | ๋์ผ |
| 3 | URL https://... | URL ๊ฒฝ๋ก์ ํค์๋ ํฌํจ ๊ฐ๋ฅ |
| 4 | ํ์ผ ๊ฒฝ๋ก /foo/bar/baz | ๊ฒฝ๋ก์ ํค์๋ ํฌํจ ๊ฐ๋ฅ |
| 5 | ์ฝ๋ ๋ธ๋ก ```...``` | ์ฝ๋ ์ ํค์๋๋ ์๋ํ ๋ช ๋ น์ด ์๋ |
| 6 | ์ธ๋ผ์ธ ์ฝ๋ `...` | ๋์ผ |
์: ์ฌ์ฉ์๊ฐ "https://example.com/ultrawork/docs๋ฅผ ๋ด" ๋ผ๊ณ ์
๋ ฅํ๋ฉด, URL์ ๋จผ์ ์ ๊ฑฐํ๋ฏ๋ก โultraworkโ๊ฐ ํค์๋๋ก ์คํ๋์ง ์๋๋ค.
4. Match โ ํค์๋ ๋งค์นญ
14๊ฐ ํค์๋ (์ฐ์ ์์ ์)
| ์ฐ์ ์์ | ํค์๋ | ํธ๋ฆฌ๊ฑฐ ํจํด | ์ถ๋ ฅ ๋ฐฉ์ |
|---|---|---|---|
| 1 | cancel | cancelomc, stopomc | state ์ญ์ + Skill ํธ์ถ |
| 2 | ralph | ralph, don't stop, must complete, until done | state ์์ฑ + Skill ํธ์ถ |
| 3 | autopilot | autopilot, build me an app, i want a, e2e this ๋ฑ | state ์์ฑ + Skill ํธ์ถ |
| 4 | ultrawork | ultrawork, ulw, uw | state ์์ฑ + Skill ํธ์ถ |
| 5 | ccg | ccg, claude-codex-gemini | Skill ํธ์ถ |
| 6 | ralplan | ralplan | Skill ํธ์ถ |
| 7 | deep-interview | deep interview, ouroboros | Skill ํธ์ถ |
| 8 | ai-slop-cleaner | ai slop, anti slop + action+smell ์กฐํฉ | Skill ํธ์ถ |
| 9 | tdd | tdd, test first, red green | ๋ฉ์์ง ์ง์ ์ฃผ์ |
| 10 | code-review | code review, review code | ๋ฉ์์ง ์ง์ ์ฃผ์ |
| 11 | security-review | security review, review security | ๋ฉ์์ง ์ง์ ์ฃผ์ |
| 12 | ultrathink | ultrathink, think hard, think deeply | ๋ฉ์์ง ์ง์ ์ฃผ์ |
| 13 | deepsearch | deepsearch, search the codebase, find in code | Skill ํธ์ถ |
| 14 | analyze | deep analyze, deepanalyze | ๋ฉ์์ง ์ง์ ์ฃผ์ |
๋ ๊ฐ์ง ์ถ๋ ฅ ๋ฐฉ์
ํค์๋๋ ๊ฐ์ง ํ ๋ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌ๋๋ค:
Skill ํธ์ถ ๋ฐฉ์ (cancel~deepsearch): createSkillInvocation()์ผ๋ก Claude์๊ฒ โ์ด Skill์ ์คํํ๋ผโ๋ ์ง์๋ฅผ ์ฃผ์
ํ๋ค.
[MAGIC KEYWORD: ULTRAWORK]
You MUST invoke the skill using the Skill tool:
Skill: oh-my-claudecode:ultrawork
๋ฉ์์ง ์ง์ ์ฃผ์
๋ฐฉ์ (tdd, code-review, security-review, ultrathink, analyze): Skill ํธ์ถ ์์ด XML ํ๊ทธ๋ก ๊ฐ์ผ ์ง์ ๋ฉ์์ง๋ฅผ additionalContext์ ์ง์ ์ฃผ์
ํ๋ค.
<tdd-mode>
[TDD MODE ACTIVATED]
Write or update tests first when practical...
</tdd-mode>ai-slop-cleaner์ ํน์ํ ๋งค์นญ ๋ก์ง
๋ค๋ฅธ ํค์๋๋ ๋จ์ ์ ๊ท์์ด์ง๋ง, ai-slop-cleaner๋ 2๊ฐ์ง ์กฐ๊ฑด์ ์กฐํฉ์ผ๋ก ๋งค์นญํ๋ค:
// ๋ฐฉ๋ฒ 1: ๋ช
์์ ํค์๋
/\b(ai[\s-]?slop|anti[\s-]?slop|deslop)\b/
// ๋ฐฉ๋ฒ 2: action + smell ์กฐํฉ
action: clean, refactor, simplify, dedupe, prune ...
smell: slop, duplicate, dead code, over-abstraction, tech debt ...
// "duplicated code๋ฅผ clean upํด์ค" โ action(cleanup) + smell(duplicated) โ ๋งค์นญ5. Output โ ์ถฉ๋ ํด๊ฒฐ๊ณผ ์ต์ข ์ถ๋ ฅ
resolveConflicts() โ ์ฐ์ ์์ ํด๊ฒฐ
๋ณต์ ํค์๋๊ฐ ๋์์ ๋งค์นญ๋๋ฉด resolveConflicts()๊ฐ ์ฐ์ ์์๋ก ์ ๋ ฌํ๋ค:
cancel > ralph > autopilot > ultrawork > ccg > ralplan > ... > analyze
cancel์ ๋ฐฐํ์ โ ๋ค๋ฅธ ๋ชจ๋ ํค์๋๋ฅผ ๋ฌด์ํ๊ณ cancel๋ง ์คํ- ๋๋จธ์ง๋ ๊ณต์กด ๊ฐ๋ฅ โ ์ฐ์ ์์ ์์ผ๋ก ์ ๋ ฌํ์ฌ ๋ชจ๋ ์คํ
ralph ํ์ฑํ ์ ultrawork ์๋ ์ฐ๋
ralph๊ฐ ๊ฐ์ง๋๊ณ ultrawork๊ฐ ์์ผ๋ฉด, ultrawork state๋ ์๋์ผ๋ก ์์ฑํ๋ค:
if (hasRalph && !hasUltrawork) {
activateState(directory, prompt, 'ultrawork', sessionId);
}ralph๋ โ์์ ์๋ฃ๊น์ง ๋ฉ์ถ์ง ๋งโ์ด๊ณ , ultrawork๋ โ๋ณ๋ ฌ๋ก ์คํํ๋ผโ์ด๋ฏ๋ก ํจ๊ป ์ฌ์ฉํ๋ ๊ฒ์ด ์์ฐ์ค๋ฝ๋ค.
activateState() โ ์ํ ํ์ผ ์์ฑ
ralph, autopilot, ultrawork 3๊ฐ ํค์๋๋ง ์ํ ํ์ผ์ ์์ฑํ๋ค:
.omc/state/sessions/{sessionId}/ultrawork-state.json
{
"active": true,
"started_at": "2026-03-14T...",
"original_prompt": "ultrawork refactor the API",
"session_id": "abc123",
"reinforcement_count": 0
}์ด state ํ์ผ์ ๋์ค์ persistent-mode.cjs (Stop Hook)๊ฐ ์ฝ์ด์ ์ข
๋ฃ๋ฅผ ์ฐจ๋จํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.
createHookOutput() โ Claude Code์ ์ ๋ฌ
์ต์ข ๊ฒฐ๊ณผ๋ ํญ์ ๊ฐ์ ํฌ๋งท์ผ๋ก stdout์ ์ถ๋ ฅ๋๋ค:
{
"continue": true,
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": "[MAGIC KEYWORD: ULTRAWORK]\nYou MUST invoke the skill..."
}
}์ฐธ๊ณ ๋ฌธ์
- keyword-detector.mjs ์์ค
- OMC - Hook System โ Hook ์์คํ ์ ์ฒด ๊ตฌ์กฐ