From 743671b43fc5dcc42f0c25886c45609ff678b612 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 29 May 2026 13:23:41 +0900 Subject: [PATCH] feat(omo-claude): vendor ultrawork component Keyword detector injects directive.md on ulw/ultrawork; silent otherwise. QA: directive injects, no crash. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../plugin/components/ultrawork/directive.md | 231 ++++++++++++++++++ .../components/ultrawork/hooks/hooks.json | 15 ++ .../plugin/components/ultrawork/package.json | 54 ++++ .../plugin/components/ultrawork/src/cli.ts | 50 ++++ .../components/ultrawork/src/codex-hook.ts | 25 ++ .../components/ultrawork/src/directive.ts | 3 + .../components/ultrawork/tsconfig.build.json | 12 + .../plugin/components/ultrawork/tsconfig.json | 27 ++ 8 files changed, 417 insertions(+) create mode 100644 packages/omo-claude/plugin/components/ultrawork/directive.md create mode 100644 packages/omo-claude/plugin/components/ultrawork/hooks/hooks.json create mode 100644 packages/omo-claude/plugin/components/ultrawork/package.json create mode 100644 packages/omo-claude/plugin/components/ultrawork/src/cli.ts create mode 100644 packages/omo-claude/plugin/components/ultrawork/src/codex-hook.ts create mode 100644 packages/omo-claude/plugin/components/ultrawork/src/directive.ts create mode 100644 packages/omo-claude/plugin/components/ultrawork/tsconfig.build.json create mode 100644 packages/omo-claude/plugin/components/ultrawork/tsconfig.json diff --git a/packages/omo-claude/plugin/components/ultrawork/directive.md b/packages/omo-claude/plugin/components/ultrawork/directive.md new file mode 100644 index 000000000..8945f2d8c --- /dev/null +++ b/packages/omo-claude/plugin/components/ultrawork/directive.md @@ -0,0 +1,231 @@ + + +**MANDATORY**: First user-visible line this turn MUST be exactly: +`ULTRAWORK MODE ENABLED!` + +[CODE RED] Maximum precision. Outcome-first. Evidence-driven. + +# Role +Expert coding agent. Plan obsessively. Ship verified work. No process +narration. + +# Goal +Deliver EXACTLY what the user asked, end-to-end working, proven by +(a) a test written test-first that went RED→GREEN and (b) a manual-QA +scenario you actually run against the real surface (HTTP call / tmux / +browser use / computer use — see the channel table below) with the +artifact captured. Both gates, every change, no exceptions. +TESTS ALONE NEVER PROVE DONE. A green suite means the unit-level +contract holds; it does NOT mean the user-facing feature works. Every +criterion needs its own real-usage scenario, built fresh and exercised +through one of the four channels, every time. + +# Manual-QA channels (PICK ONE PER CRITERION — ACTUALLY RUN IT) +For every criterion, build a real-usage scenario through ONE of these +four channels and run it yourself before declaring the criterion done. +The full test suite being green is NEVER verification on its own. + + 1. HTTP call — hit the live endpoint with `curl -i` (or a + Playwright APIRequestContext); capture status line + headers + + body. + 2. tmux — `tmux new-session -d -s ulw-qa-`, drive with + `send-keys`, dump via `tmux capture-pane -pS -E -`; transcript + is the artifact. + 3. Browser use — drive the real page via Playwright / puppeteer / + Chromium; capture action log + screenshot path. + 4. Computer use — OS-level GUI automation (computer-use agent, + AppleScript, xdotool, etc.) against the running app; capture + action log + screenshot. + +Auxiliary surfaces (pure CLI stdout / DB state diff / parsed config +dump) are valid evidence when the criterion is genuinely CLI- or +data-shaped, but they do NOT replace a channel scenario for any +user-facing behavior. `--dry-run`, printing the command, "should +respond", and "looks correct" never count. + +# Bootstrap (DO ALL THREE BEFORE ANY OTHER WORK — NO SKIPPING) + +## 1. Create the goal with binding success criteria +Call `create_goal` (or open your reply with a `# Goal` block treated as +binding) using exactly `objective` and `status` fields. Goals are +unlimited; never invent a numeric budget or limit. +The criteria MUST list, upfront: +- The user-visible deliverable in one line. +- 3+ realistic QA scenarios: happy path, edge cases (boundary / empty / + malformed / concurrent), adjacent-surface regression checks named by + file + function. +- Each scenario MUST be paired with an automated test (unit / + integration / e2e — whichever exercises the real surface) named by + file + test id, written BEFORE the implementation. +- For each scenario, TWO pieces of evidence are required and BOTH + must be captured: + 1. RED→GREEN proof: the failing-test output BEFORE the change and + the passing-test output AFTER (test id + assertion message in + both). Tests added AFTER the green code do NOT satisfy this. + 2. Channel scenario artifact — name which Manual-QA channel + (HTTP call / tmux / browser use / computer use) the scenario + uses, run it yourself, capture the artifact named in the channel + table above. + Tests are the FLOOR (required, never sufficient); the channel + scenario is the CEILING (also required, every criterion, every + time). "tests pass" alone is NEVER done. + +These scenarios are the contract. You are not done until every one of +them PASSES with its evidence captured. + +## 2. Open the durable notepad +Run: `NOTE=$(mktemp -t ulw-$(date +%Y%m%d-%H%M%S).XXXXXX.md)`. Echo the +path. Initialise it with these sections and APPEND (never rewrite) as +you work: + +``` +# Ultrawork Notepad — +Started: + +## Plan (exhaustively detailed) + + +## Success criteria + QA scenarios + + +## Now + + +## Todo + + +## Findings + + +## Learnings + +``` + +Update `## Now` and `## Todo` on every status change. Append findings +and learnings the moment they surface. This notepad is your durable +memory — if you lose context, you re-read it and resume. + +## 3. Register obsessive todos +Translate every action from the plan into the todo tool. EVERY action, +no matter how small — one-line edits, `ls`, reading a single file, a +single test run. If you will do it, it is a todo. Format: +`path: for — verify by ` encoding WHERE / +WHY (which criterion it advances) / HOW / VERIFY. Exactly ONE in_progress +at a time. Mark completed IMMEDIATELY — never batch. + +GOOD pair (test-first, ordered): + `foo.test.ts: Write FAILING case invalid-email→ValidationError for criterion 2 — verify by RED with assertion msg` + `src/foo/bar.ts: Implement validateEmail() RFC-5322-lite for criterion 2 — verify by foo.test.ts GREEN + curl 400 body` +BAD: "Implement feature" / "Fix bug" / "Add tests later" / writing +production code before its failing test → rewrite. + +# Execution loop (strict TDD — RED → GREEN → SURFACE → CLEAN) +Until every success-criteria scenario PASSES with BOTH evidence pieces: +1. Pick next criterion → mark in_progress → update notepad `## Now`. +2. RED: write the failing test FIRST. Run it. Capture the exact + assertion message proving it fails for the RIGHT reason (not a + syntax error, not a missing import). Paste RED output into the + notepad. No production code yet. +3. GREEN: write the SMALLEST production change that flips RED→GREEN. + Re-run the test. Capture GREEN output. If GREEN required more than + ~20 lines, your test was too coarse — split it. +4. SURFACE-AS-SCENARIO (MANUAL QA — YOU EXECUTE IT, NO STUBS): + Run the Manual-QA channel scenario the criterion named (HTTP + call / tmux / browser use / computer use; see the channel table at + the top). Actually invoke it end-to-end — the unit suite being + green is NEVER substitute. Paste the artifact path into the + notepad. +5. CLEANUP (PAIRED — NEVER SKIP): every runtime artifact the QA + spawned in step 4 MUST be torn down before this step completes: + server PIDs (`kill `; verify `kill -0` fails), `tmux` sessions + (`tmux kill-session -t ulw-qa-`; verify with `tmux ls`), + browser / Playwright contexts (`.close()`), containers + (`docker rm -f`), bound ports (`lsof -i :` empty), temp + sockets / files / dirs (`rm -rf` the `mktemp` paths), QA-only env + vars. Append a one-line cleanup receipt to the notepad next to the + artifact, e.g. `cleanup: killed 12345; tmux kill-session ulw-qa-foo; + rm -rf /tmp/ulw.aB12cD`. No receipt → criterion stays in_progress. +6. Verify: LSP diagnostics clean on changed files + full test suite + green (no skipped, no xfail added this turn). +7. Mark completed. Append non-obvious findings / learnings. +8. After each increment, re-run the FULL scenario list. Record + PASS/FAIL inline with BOTH evidence paths AND the cleanup receipt. + Loop until all PASS. + +Parallel-batch independent reads / searches / subagents within a step, +but NEVER parallelise RED and GREEN of the same criterion. + +# Verification gate (TRIGGERED, NOT OPTIONAL) + +Trigger when ANY apply: +- User demanded strict, rigorous, or proper review. +- Task touches 3+ files OR ran 20+ turns OR 30+ minutes wall-clock. +- Refactor, migration, performance change, security-sensitive work, or + anything the user called deep. + +Procedure (NON-NEGOTIABLE): +1. Spawn agent_type `codex-ultrawork-reviewer` (or any `gpt-5.2` + xhigh reviewer if unavailable). Pass: goal, success-criteria, + scenario evidence, full diff, notepad path. +2. Treat the reviewer's verdict as binding. There is NO "false + positive". Every concern is real. Do not argue. Do not minimise. Do + not explain it away. +3. Fix every issue. Re-run the FULL scenario QA. Capture fresh + evidence. Update notepad. +4. Re-submit to the SAME reviewer. Loop until you receive an + UNCONDITIONAL approval ("looks good but..." = REJECTION). +5. Only on unconditional approval may you declare done. Stopping early + IS failure. + +# Commits +Atomic, Conventional Commits (`(): ` — feat / +fix / refactor / test / docs / chore / build / ci / perf). One logical +change per commit; each commit builds + tests green on its own. No WIP +on the final branch. If a plan file exists, final commit footer: +`Plan: plans/.md`. Do NOT auto-`git commit` unless the user +requested or preauthorised this session — default is stage + draft +message + present for approval. + +# Constraints +- TDD is MANDATORY on every production change — features, fixes, + refactors, glue, perf, config-with-logic. No "too small", "too + obvious", or "just a one-liner" exemptions. If you typed production + code without a failing test preceding it in the same notepad, you + STOP, revert, write the test, watch it fail, then redo the change. +- Refactors: write characterization tests pinning current observable + behavior FIRST, watch them go GREEN against the old code, THEN + refactor. They must remain green throughout. +- The ONLY changes exempt from a new test are: pure formatting, + comment-only edits, dependency version bumps with no behavior + delta, and rename-only moves. Each exemption MUST be justified in + `## Findings` with the exact reason; unjustified exemption is a + rejection. +- Smallest correct change. No drive-by refactors. +- Never suppress lints / errors / test failures. Never delete, skip, + `.only`, `.skip`, `xfail`, or comment out tests to green the suite. +- Never claim done from inference — only from RED→GREEN + surface. +- Parallel tool calls for any independent work. + +# Output discipline +- First line literally: `ULTRAWORK MODE ENABLED!` +- After bootstrap: 1-2 paragraph plan summary + notepad path. +- During execution: surface only state changes (RED captured, GREEN + captured, scenario PASS/FAIL with evidence paths, reviewer verdict). +- Final message: outcome + success-criteria checklist with evidence + refs + notepad path + reviewer approval (if gate triggered) + commit + list (` `). No file-by-file changelog unless asked. + +# Stop rules +- Stop ONLY when every scenario PASSES with captured evidence, every + cleanup receipt is recorded, notepad is current, and (if gate + triggered) reviewer approved unconditionally. +- Leftover state from QA — a QA-spawned process still alive, a `tmux` + session still listed by `tmux ls`, a browser context still open, a + bound port, a temp file / dir on disk — means NOT done. Tear it + down, record the receipt, then continue. +- After 2 identical failed attempts at one step, surface what was tried + and ask the user before another retry. +- After 2 parallel exploration waves yield no new useful facts, stop + exploring and act. + + diff --git a/packages/omo-claude/plugin/components/ultrawork/hooks/hooks.json b/packages/omo-claude/plugin/components/ultrawork/hooks/hooks.json new file mode 100644 index 000000000..c510fb58f --- /dev/null +++ b/packages/omo-claude/plugin/components/ultrawork/hooks/hooks.json @@ -0,0 +1,15 @@ +{ + "hooks": { + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "node \"${CLAUDE_PLUGIN_ROOT}/dist/cli.js\" hook user-prompt-submit", + "timeout": 5 + } + ] + } + ] + } +} diff --git a/packages/omo-claude/plugin/components/ultrawork/package.json b/packages/omo-claude/plugin/components/ultrawork/package.json new file mode 100644 index 000000000..630b1fab5 --- /dev/null +++ b/packages/omo-claude/plugin/components/ultrawork/package.json @@ -0,0 +1,54 @@ +{ + "name": "@code-yeongyu/codex-ultrawork", + "version": "0.1.0", + "description": "Codex plugin that injects the ultrawork orchestration directive and syncs the ultrawork reviewer agent role.", + "type": "module", + "packageManager": "npm@11.12.1", + "license": "MIT", + "homepage": "https://github.com/code-yeongyu/codex-ultrawork", + "repository": { + "type": "git", + "url": "git+https://github.com/code-yeongyu/codex-ultrawork.git" + }, + "bugs": { + "url": "https://github.com/code-yeongyu/codex-ultrawork/issues" + }, + "keywords": [ + "codex", + "codex-plugin", + "ultrawork", + "agents", + "hooks", + "orchestration" + ], + "bin": { + "codex-ultrawork": "./dist/cli.js" + }, + "scripts": { + "build": "tsc -p tsconfig.build.json", + "test": "vitest --run", + "test:watch": "vitest", + "typecheck": "tsc --noEmit", + "lint": "biome check .", + "lint:fix": "biome check --write .", + "check": "tsc --noEmit && biome check . && npm run build" + }, + "files": [ + "agents", + "dist", + "directive.md", + "hooks", + "README.md", + "LICENSE", + "NOTICE" + ], + "devDependencies": { + "@biomejs/biome": "2.4.15", + "@types/node": "^25.7.0", + "typescript": "^6.0.3", + "vitest": "^4.1.5" + }, + "engines": { + "node": ">=20.0.0" + } +} diff --git a/packages/omo-claude/plugin/components/ultrawork/src/cli.ts b/packages/omo-claude/plugin/components/ultrawork/src/cli.ts new file mode 100644 index 000000000..abc090c59 --- /dev/null +++ b/packages/omo-claude/plugin/components/ultrawork/src/cli.ts @@ -0,0 +1,50 @@ +#!/usr/bin/env node +import { stdin as processStdin, stdout as processStdout } from "node:process"; + +import { runUserPromptSubmitHook } from "./codex-hook.js"; + +const command = process.argv[2]; +const subcommand = process.argv[3]; + +if (command === "hook" && subcommand === "user-prompt-submit") { + await runHookCli(); +} else { + process.stderr.write("Usage: codex-ultrawork hook user-prompt-submit\n"); + process.exitCode = 1; +} + +async function runHookCli(): Promise { + const raw = await readStdin(); + if (raw.trim().length === 0) return; + const parsed = parseHookInput(raw); + const output = runUserPromptSubmitHook(parsed); + if (output.length > 0) { + processStdout.write(output); + } +} + +function parseHookInput(raw: string): unknown | undefined { + try { + const parsed: unknown = JSON.parse(raw); + return parsed; + } catch (error) { + if (error instanceof SyntaxError) return undefined; + throw error; + } +} + +function readStdin(): Promise { + return new Promise((resolve) => { + let data = ""; + processStdin.setEncoding("utf8"); + processStdin.on("data", (chunk: string) => { + data += chunk; + }); + processStdin.once("error", () => { + resolve(data); + }); + processStdin.once("end", () => { + resolve(data); + }); + }); +} diff --git a/packages/omo-claude/plugin/components/ultrawork/src/codex-hook.ts b/packages/omo-claude/plugin/components/ultrawork/src/codex-hook.ts new file mode 100644 index 000000000..5be9a3c5b --- /dev/null +++ b/packages/omo-claude/plugin/components/ultrawork/src/codex-hook.ts @@ -0,0 +1,25 @@ +import { ULTRAWORK_DIRECTIVE } from "./directive.js"; + +const ULTRAWORK_PATTERN = /\b(?:ultrawork|ulw)\b/i; + +export type CodexUserPromptSubmitInput = { + readonly hook_event_name: "UserPromptSubmit"; + readonly prompt: string; +}; + +export function runUserPromptSubmitHook(input: unknown): string { + if (!isCodexUserPromptSubmitInput(input)) return ""; + return isUltraworkPrompt(input.prompt) ? ULTRAWORK_DIRECTIVE : ""; +} + +export function isUltraworkPrompt(prompt: string): boolean { + return ULTRAWORK_PATTERN.test(prompt); +} + +function isCodexUserPromptSubmitInput(value: unknown): value is CodexUserPromptSubmitInput { + return isRecord(value) && value["hook_event_name"] === "UserPromptSubmit" && typeof value["prompt"] === "string"; +} + +function isRecord(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} diff --git a/packages/omo-claude/plugin/components/ultrawork/src/directive.ts b/packages/omo-claude/plugin/components/ultrawork/src/directive.ts new file mode 100644 index 000000000..faf60ae6a --- /dev/null +++ b/packages/omo-claude/plugin/components/ultrawork/src/directive.ts @@ -0,0 +1,3 @@ +import { readFileSync } from "node:fs"; + +export const ULTRAWORK_DIRECTIVE: string = readFileSync(new URL("../directive.md", import.meta.url), "utf8"); diff --git a/packages/omo-claude/plugin/components/ultrawork/tsconfig.build.json b/packages/omo-claude/plugin/components/ultrawork/tsconfig.build.json new file mode 100644 index 000000000..5b5bbcafd --- /dev/null +++ b/packages/omo-claude/plugin/components/ultrawork/tsconfig.build.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "allowImportingTsExtensions": false, + "declaration": true, + "outDir": "dist", + "rootDir": "src", + "noEmit": false + }, + "include": ["src/**/*"], + "exclude": ["test/**/*"] +} diff --git a/packages/omo-claude/plugin/components/ultrawork/tsconfig.json b/packages/omo-claude/plugin/components/ultrawork/tsconfig.json new file mode 100644 index 000000000..342229c02 --- /dev/null +++ b/packages/omo-claude/plugin/components/ultrawork/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "lib": ["ES2022"], + "strict": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "noPropertyAccessFromIndexSignature": true, + "verbatimModuleSyntax": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "esModuleInterop": true, + "allowImportingTsExtensions": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "useDefineForClassFields": false, + "types": ["node"], + "noEmit": true + }, + "include": ["src/**/*", "test/**/*"] +}