# oh-my-opencode — OpenCode Plugin > **HOLD THE FUCK UP. THIS ENTIRE GODDAMN CODEBASE IS BEING RIPPED APART AND REBUILT RIGHT NOW. A MASSIVE MULTI-HARNESS AGENT OS REFACTOR IS IN PROGRESS — WE ARE RESTRUCTURING EVERYTHING TO SUPPORT MULTIPLE AGENT HARNESSES (OPENCODE, CODEX, PI, AND OTHERS). DO NOT TRUST THE STRUCTURE BELOW AS STABLE. READ THE [ROADMAP](./ROADMAP.md) BEFORE YOU TOUCH ANYTHING OR SO HELP ME GOD.** **Generated:** 2026-05-31 | **Commit:** 92bad87d8 | **Branch:** dev | **Release:** v4.5.12 ## OVERVIEW OpenCode plugin (npm: `oh-my-opencode`, dual-published as `oh-my-openagent` during the rename transition) extending OpenCode with 11 agents, 54-61 lifecycle hooks (base / +team-mode) across 57 dirs, 20-39 tools (gated by config flags including team-mode), 3-tier MCP system (built-in + .mcp.json + skill-embedded), Hashline LINE#ID edit tool, IntentGate keyword detector, Team Mode (parallel multi-agent coordination, OFF by default), Boulder feature (boulder-state work tracking + cli/boulder subcommand), configurable agent ordering, and Claude Code compatibility. **Repository contains ~2167 TypeScript files across `src/`, `script/`, `test-support/`, and `packages/web/`, ~313k LOC; `src/` itself has 120 barrel `index.ts` files.** Entry: `src/index.ts` is an 18-line wrapper that delegates to `src/testing/create-plugin-module.ts` `createPluginModule()` → 7-step init. Ships in two editions of one product: **Ultimate** (omo for OpenCode, this plugin) and **Light** (omo for Codex CLI = [`packages/omo-codex/`](file:///Users/yeongyu/local-workspaces/omo/packages/omo-codex/AGENTS.md), distributed as the `lazycodex` alias; see CODEX LIGHT EDITION below). ## STRUCTURE ``` oh-my-opencode/ ├── src/ │ ├── index.ts # Plugin entry; thin wrapper that re-exports `createPluginModule()` from `src/testing/` │ ├── plugin-config.ts # JSONC multi-level config: user → project → defaults (Zod v4) │ ├── plugin-interface.ts # 12 OpenCode hook handlers (+2 wired in src/testing/create-plugin-module.ts) │ ├── create-managers.ts # 4 managers (Tmux, Background, SkillMcp, ConfigHandler) │ ├── create-tools.ts # ToolRegistry composition │ ├── create-hooks.ts # 5-tier hook composition │ ├── agents/ # 11 agents (Sisyphus, Hephaestus, Oracle, Librarian, Explore, Atlas, Prometheus, Metis, Momus, Multimodal-Looker, Sisyphus-Junior) │ ├── hooks/ # ~52 lifecycle hooks across 57 dirs (incl. 5 zauc-mocks + 1 shared + 1 `.sisyphus/` legacy state + 1 `team-session-events/`; 2 unwired WIP: `task-reminder/`, `hashline-edit-diff-enhancer/`) │ ├── tools/ # 13 native tool dirs; LSP + AST-grep now served via built-in MCPs │ ├── features/ # 20 feature modules (incl. team-mode, background-agent, skill-mcp-manager, opencode-skill-loader, tmux-subagent, mcp-oauth, claude-code-plugin-loader, boulder-state, etc.) │ ├── shared/ # 297 utility files (179 non-test); logger → oh-my-opencode.log in os.tmpdir() (50 MB cap, .1/.2 backups) │ ├── config/ # Zod v4 schema system (30 schema files) │ ├── cli/ # CLI: install, run, doctor, mcp-oauth, refresh-model-capabilities, get-local-version, boulder │ ├── mcp/ # 5 built-in MCPs (3 remote + local stdio lsp + ast_grep) │ ├── plugin/ # 12 OpenCode hook handlers + 5-tier hook composition │ ├── plugin-handlers/ # 6-phase config loading pipeline │ ├── openclaw/ # Bidirectional external integration (Discord/Telegram/HTTP/shell + reply listener daemon) │ ├── generated/ # model-capabilities.generated.json (refreshed via build:model-capabilities) │ └── testing/ # Test utilities + `create-plugin-module.ts` (extracted plugin entry factory, 182 LOC) ├── packages/ # 26 sibling pkgs: 11 platform binaries + 3 MCP packages + 9 Core packages + omo-codex (Codex Light) + shared-skills + web │ ├── utils/ # Shared utilities — deep-merge, snake-case, frontmatter, file-utils, etc. │ ├── model-core/ # Model resolution pipeline with ProviderCache DI │ ├── prompts-core/ # Markdown prompt loading + bundled mode prompts (search/analyze/team/hyperplan) │ ├── rules-engine/ # Rule discovery + matching (renamed from rules-core) │ ├── agents-md-core/ # AGENTS.md walk-up discovery + injection │ ├── ast-grep-core/ # ast-grep types, pattern-hints, runner core with injectable spawn │ ├── comment-checker-core/ # apply-patch parser, binary runner with injectable spawn │ ├── hashline-core/ # Hashline edit primitives and diff helpers shared by adapter shims │ ├── boulder-state/ # Work tracking state machine, split storage │ ├── omo-codex/ # Codex CLI Light edition (lazycodex); vendored Codex plugin `omo` + TS installer + telemetry │ ├── git-bash-mcp/ # Windows-only git_bash stdio MCP (Codex edition) │ ├── shared-skills/ # Cross-harness SKILL.md bundle shared by OpenCode + Codex │ └── web/ # Marketing site (Next.js 15 + Cloudflare Workers). Independent package with own bun.lock ├── bin/ # Platform-detection JS shim (oh-my-opencode + oh-my-openagent) ├── script/ # Build/publish automation (singular, not scripts/) ├── docs/ # User-facing docs (guide/, reference/, examples/, legal/, manifesto.md, superpowers/, troubleshooting/) ├── assets/ # oh-my-opencode.schema.json (auto-generated from Zod) ├── signatures/ # CLA signature registry (cla.json) ├── postinstall.mjs # Verifies platform binary + OpenCode version ├── test-setup.ts # Bun test preload (resets state between tests) ├── bun-test.d.ts # Custom bun:test type augmentations ├── .opencode/ # Project-scope skills + commands (skills/, command/) + background-tasks state ├── .agents/ # Mirrored project-scope skills + commands (recent migration target) ├── .omo/ # AI agent workspace (run-continuation/, plans/, tasks/, notepads/) └── .local-ignore/ # Dev-only test fixtures + PR worktrees ``` ## INITIALIZATION FLOW ``` pluginModule.server(input, options) ├─→ installAgentSortShim() # patches Array.prototype.{toSorted,sort} for canonical agent ordering ├─→ initConfigContext() # opencode-vs-openagent layout flag ├─→ detectExternalSkillPlugin() # warn on conflicts ├─→ injectServerAuthIntoClient() # auth headers into shared SDK client ├─→ loadPluginConfig() # JSONC parse → user/project merge → Zod validate → migrate ├─→ initializeOpenClaw() # if openclaw config present ├─→ checkTeamModeDependencies() # if team_mode.enabled ├─→ createManagers() # TmuxSessionManager, BackgroundManager, SkillMcpManager, ConfigHandler ├─→ createTools() # SkillContext + AvailableCategories + ToolRegistry ├─→ createHooks() # 5-tier: Session + ToolGuard + Transform + Continuation + Skill └─→ createPluginInterface() # 12 OpenCode hook handlers → PluginInterface ``` ## 14 OPENCODE HOOK HANDLERS 12 wired in [`src/plugin-interface.ts`](file:///Users/yeongyu/local-workspaces/omo/src/plugin-interface.ts) + 2 wired directly in [`src/testing/create-plugin-module.ts`](file:///Users/yeongyu/local-workspaces/omo/src/testing/create-plugin-module.ts) (`experimental.session.compacting` + `experimental.compaction.autocontinue`). | Handler | OpenCode Hook | Purpose | |---------|---------------|---------| | `config` | `config` | 6-phase pipeline: provider → plugin-components → agents → tools → MCPs → commands | | `tool` | `tool` | 20–39 registered tools (config-gated: team-mode +12, task system +4, hashline +1, interactive_bash +1, look_at +1) | | `tool.definition` | `tool.definition` | Per-tool definition transform (applies `todo-description-override`) | | `chat.message` | `chat.message` | First-message variant, session setup, keyword detection (ultrawork/search/analyze/team) | | `chat.params` | `chat.params` | Anthropic effort, think mode, runtime fallback override | | `chat.headers` | `chat.headers` | Copilot `x-initiator` header injection | | `command.execute.before` | `command.execute.before` | Pre-command guards (slash-command interception, etc.) | | `event` | `event` | Session lifecycle (created/deleted/idle/error), openclaw dispatch, runtime fallback | | `tool.execute.before` | `tool.execute.before` | Pre-tool guards (write-existing-guard, label-truncator, rules-injector, prometheus-md-only, …) | | `tool.execute.after` | `tool.execute.after` | Post-tool hooks (output truncator, comment-checker, hashline read-enhancer, json-error-recovery, …) | | `experimental.chat.messages.transform` | `experimental.chat.messages.transform` | Context injection, thinking-block validation, tool-pair validation, keyword detection | | `experimental.chat.system.transform` | `experimental.chat.system.transform` | System-message-level transforms | | `experimental.session.compacting` | `experimental.session.compacting` | Context + todo preservation across compaction | | `experimental.compaction.autocontinue` | `experimental.compaction.autocontinue` | Auto-resume after compaction completes | ## TOOL CATALOG (config-gated) **Always on (20):** `lsp_goto_definition`, `lsp_find_references`, `lsp_symbols`, `lsp_diagnostics`, `lsp_prepare_rename`, `lsp_rename`, `grep`, `glob`, `ast_grep_search`, `ast_grep_replace`, `session_list`, `session_read`, `session_search`, `session_info`, `background_output`, `background_cancel`, `call_omo_agent`, `task` (delegate), `skill`, `skill_mcp`. > Note: `lsp_*` and `ast_grep_*` tool names are now served by built-in MCP servers (`lsp` via `packages/lsp-tools-mcp`, `ast_grep` via `packages/ast-grep-mcp`), preserving existing names through OpenCode MCP namespacing. **Conditional:** `look_at` (+1, multimodal-looker not disabled), `interactive_bash` (+1, `tmux` binary available on PATH via `isInteractiveBashEnabled()`), `task_create`/`task_get`/`task_list`/`task_update` (+4, `experimental.task_system`), `edit` (+1, `hashline_edit`), `team_create`/`team_delete`/`team_shutdown_request`/`team_approve_shutdown`/`team_reject_shutdown`/`team_send_message`/`team_task_create`/`team_task_list`/`team_task_update`/`team_task_get`/`team_status`/`team_list` (+12, `team_mode.enabled`). ## TEAM MODE OFF by default. Parallel multi-agent coordination, modeled after Claude Code Agent Teams. Enable via `team_mode.enabled` in `.opencode/oh-my-opencode.jsonc` or user config; restart OpenCode after change. Full schema in [`src/config/schema/team-mode.ts`](file:///Users/yeongyu/local-workspaces/omo/src/config/schema/team-mode.ts) (11 fields): ```jsonc { "team_mode": { "enabled": true, "tmux_visualization": false, "max_parallel_members": 4, // 1..8 "max_members": 8, // 1..8 hard cap "max_messages_per_run": 10000, "max_wall_clock_minutes": 120, "max_member_turns": 500, "base_dir": null, // override default ~/.omo/teams or /.omo/teams "message_payload_max_bytes": 32768, // ≥1024 "recipient_unread_max_bytes": 262144, // ≥1024 "mailbox_poll_interval_ms": 3000 // ≥500 } } ``` Teams live as directories under `~/.omo/teams/{name}/config.json` (user) or `/.omo/teams/{name}/config.json` (project; project beats user on collisions). Members declared as `kind: "subagent_type"` (direct agent) or `kind: "category"` (routed through `sisyphus-junior`). **Member eligibility** (from [`AGENT_ELIGIBILITY_REGISTRY`](file:///Users/yeongyu/local-workspaces/omo/src/features/team-mode/types.ts)): - `eligible`: sisyphus, atlas, sisyphus-junior - `conditional`: hephaestus (lacks `teammate: "allow"` permission by default — apply D-36 in `tool-config-handler.ts` or use `subagent_type: "sisyphus"` instead) - `hard-reject`: oracle, librarian, explore, multimodal-looker, metis, momus, prometheus (rejected at parse — use `task`/delegate-task) **Storage layout** (`~/.omo/teams/{name}/`): `config.json` (spec), `state.json` (runtime), `mailbox/` (messages), `tasklist.jsonl` (tasks), `worktrees/` (per-member git worktrees). **Implementation:** [`src/features/team-mode/`](file:///Users/yeongyu/local-workspaces/omo/src/features/team-mode/AGENTS.md). User docs: [`docs/guide/team-mode.md`](file:///Users/yeongyu/local-workspaces/omo/docs/guide/team-mode.md). ## CODEX LIGHT EDITION (omo-codex / lazycodex) oh-my-openagent ships in two editions of one product. **Ultimate** = this OpenCode plugin (omo for OpenCode). **Light** = omo for the OpenAI Codex CLI, vendored under [`packages/omo-codex/`](file:///Users/yeongyu/local-workspaces/omo/packages/omo-codex/AGENTS.md). "omo in Codex" / "omo for Codex" = **lazycodex**, and the public GitHub repo [`code-yeongyu/lazycodex`](https://github.com/code-yeongyu/lazycodex) IS this: a thin distribution layer over `omo-codex` (site lazycodex.ai; "Codex for no-brainers, just prompt with `ultrawork`"; Codex edition "coming June 2026", currently OpenCode-only). - **Package:** `@oh-my-opencode/omo-codex` (private, v0.1.0): "Codex harness adapter. Vendored Codex plugin namespace `omo` + TypeScript installer + telemetry." Plugin bundle pkg = `@sisyphuslabs/omo-codex-plugin`. Reuses `@oh-my-opencode/utils` + `@oh-my-opencode/shared-skills`. - **Marketplace identity (precision):** Codex sees marketplace `sisyphuslabs`, plugin `omo`, enabled as `omo@sisyphuslabs`. `lazycodex` is ONLY the repo/npm/bin alias, never the marketplace name. - **Alias mechanics:** root `package.json` maps `lazycodex` to `bin/oh-my-opencode.js` (1 of 4 bin aliases: `oh-my-opencode`, `oh-my-openagent`, `omo`, `lazycodex`, all the same compiled CLI). `bunx lazycodex install` is exactly `bunx omo install --platform=codex`. Routing: `src/cli/cli-program.ts` (lazycodex defaults platform to codex), `bin/platform.js` (lazycodex resolves the `oh-my-openagent` platform family). `src/cli/star-request.ts` stars both repos. - **Disambiguation:** the `lazycodex` npm package (this repo's CLI republished by `publish.yml` with a name/version rewrite) is NOT `lazycodex-ai` (the `code-yeongyu/lazycodex` repo's own published npm package, currently `lazycodex-ai@0.2.1`). - **Components (8):** `comment-checker`, `git-bash`, `lsp`, `rules`, `start-work-continuation`, `telemetry`, `ultrawork`, `ulw-loop`, wired to Codex events `SessionStart`/`UserPromptSubmit`/`PreToolUse`/`PostToolUse`/`PostCompact`/`Stop`/`SubagentStop`. No agent orchestration, no `team_*`, no built-in MCPs beyond LSP, no hashline. - **Install:** `bunx omo install --platform=codex` (or `bunx lazycodex install`, or `--platform=both`) copies the plugin to `~/.codex/plugins/cache/sisyphuslabs/omo//`, links agent TOMLs via `~/.codex/.tmp/marketplaces/sisyphuslabs/plugins/omo/`, enables `omo@sisyphuslabs` in `~/.codex/config.toml`, links component CLIs into `~/.local/bin`. Windows: Git Bash preflight (`winget install --id Git.Git`). Installer: [`src/cli/install-codex/`](file:///Users/yeongyu/local-workspaces/omo/src/cli/install-codex/) + `packages/omo-codex/scripts/install*.mjs`. - **Deploy / publish** ([`.github/workflows/publish.yml`](file:///Users/yeongyu/local-workspaces/omo/.github/workflows/publish.yml), manual dispatch): - `publish_lazycodex` (default **true**) publishes the npm alias `lazycodex`: rewrites root `package.json` name to `lazycodex` + version to the release + optionalDeps `oh-my-opencode-*` to `oh-my-openagent-*`, skips when `registry.npmjs.org/lazycodex/${VERSION}` exists, publishes `--access public --provenance --tag latest`, then restores `package.json`. - `sync_lazycodex_marketplace` (default **false**, needs secret `LAZYCODEX_SYNC_TOKEN`) checks out `code-yeongyu/lazycodex`, builds the plugin + ast-grep-mcp + lsp-tools-mcp, runs [`script/sync-lazycodex-marketplace.ts`](file:///Users/yeongyu/local-workspaces/omo/script/sync-lazycodex-marketplace.ts) ` `, then `git push origin HEAD:main`. - **Sync mechanism is file copy + commit push, NOT a git subtree:** `marketplace.json` to `.agents/plugins/marketplace.json`; `plugin/` to `plugins/omo/`; bundles `ast-grep-mcp` + `lsp-tools-mcp` `dist/cli.js` to `plugins/omo/components/*/dist/`; rewrites `.mcp.json` paths; validates via `script/lazycodex-marketplace-validation.ts`. Root `package.json` `files` ships `packages/omo-codex/{marketplace.json,plugin,plugin/.codex-plugin,scripts}`. First-publish playbook: [`docs/reference/lazycodex-npm-reservation.md`](file:///Users/yeongyu/local-workspaces/omo/docs/reference/lazycodex-npm-reservation.md). CI gate: `bun run test:codex` (ci.yml `codex-compatibility`, ubuntu/macos/windows). - **Telemetry:** event `omo_codex_daily_active` (once per UTC day per machine, id `sha256("omo-codex:"+hostname)`); opt-out `OMO_CODEX_DISABLE_POSTHOG=1` / `OMO_CODEX_SEND_ANONYMOUS_TELEMETRY=0` (global flags also disable). Full internals: [`packages/omo-codex/AGENTS.md`](file:///Users/yeongyu/local-workspaces/omo/packages/omo-codex/AGENTS.md). ## MULTI-LEVEL CONFIG ``` Walked configs (closer wins): /.opencode/oh-my-openagent.json[c] (legacy: oh-my-opencode.json[c]) ↓ merged onto User config: ~/.config/opencode/oh-my-openagent.json[c] (Windows: %APPDATA%\opencode\) ↓ falls back to Defaults (Zod safeParse fills omitted fields) ``` - `agents`, `categories`, `claude_code`: deep merged recursively (prototype-pollution safe) - `disabled_*` arrays: Set union (concatenated + deduplicated) - All other fields: override replaces base value - `mcp_env_allowlist`: **user-only** for security; walked configs cannot extend it - `migrateConfigFile()` rewrites legacy keys (idempotent via `_migrations` tracking + timestamped backups) Schema autocomplete: `"$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-openagent/dev/assets/oh-my-opencode.schema.json"` ## THREE-TIER MCP SYSTEM | Tier | Source | Loader | Mechanism | |------|--------|--------|-----------| | 1. Built-in | `src/mcp/` | `createBuiltinMcps()` | 3 remote HTTP + 2 local stdio MCPs (`lsp`, `ast_grep`) | | 2. Claude Code | `.mcp.json` (project + user) | `claude-code-mcp-loader` | `${VAR}` env expansion (allowlist via `mcp_env_allowlist`) | | 3. Skill-embedded | SKILL.md YAML frontmatter | `SkillMcpManager` (per-session) | stdio + HTTP, OAuth 2.0 + PKCE + DCR step-up | ## WHERE TO LOOK | Task | Location | Notes | |------|----------|-------| | Add new agent | `src/agents/` + `src/agents/builtin-agents/` | `createXXXAgent` factory + `mode: "primary" \| "subagent" \| "all"` | | Add new hook | `src/hooks/{name}/` + register in `src/plugin/hooks/create-*-hooks.ts` | Pick the right tier (Session/ToolGuard/Transform/Continuation/Skill) | | Add new tool | `src/tools/{name}/` + register in `src/plugin/tool-registry.ts` | Factory `createXXXTool` (most) or direct `ToolDefinition` (interactive_bash) | | Add new feature module | `src/features/{name}/` | Standalone module wired into `plugin/` layer | | Add new MCP (tier 1) | `src/mcp/` + register in `createBuiltinMcps()` | Remote HTTP or local stdio | | Add new built-in skill | `src/features/builtin-skills/skills/{name}.ts` + register in `skills.ts` | Implement `BuiltinSkill` interface | | Add new command | `src/features/builtin-commands/` | Templates in `templates/` | | Modify ultrawork prompts | `packages/prompts-core/prompts/ultrawork/*.md` | `src/hooks/keyword-detector/ultrawork/*.ts` are loader shims; keep `index.ts` and `source-detector.ts` routing stable | | Add new CLI subcommand | `src/cli/cli-program.ts` | Commander.js subcommand | | Add new doctor check | `src/cli/doctor/checks/` | Register in `checks/index.ts` | | Modify config schema | `src/config/schema/` + add to `OhMyOpenCodeConfigSchema` | Zod v4; auto-included in `assets/oh-my-opencode.schema.json` after `bun run build:schema` | | Add new category | `src/tools/delegate-task/constants.ts` | `DEFAULT_CATEGORIES` + `CATEGORY_MODEL_REQUIREMENTS` | | Add new team-mode tool | `src/features/team-mode/tools/` + register in `src/plugin/tool-registry.ts` `teamModeToolsRecord` | Gated on `team_mode.enabled` | | Reactive provider error recovery | `src/hooks/runtime-fallback/` | Distinct from `model-fallback` (proactive, chat.params) | | External notifications | `src/openclaw/` | Bidirectional: outbound (event → HTTP/shell), inbound (Discord/Telegram daemon → tmux send-keys) | | Skill-embedded MCP | `src/features/skill-mcp-manager/` | Tier-3 MCPs (per-session, stdio + HTTP) | ## ARCHITECTURE INVARIANTS - **Canonical agent order:** Sisyphus → Hephaestus → Prometheus → Atlas. Enforced by `installAgentSortShim()` (patches `Array.prototype.toSorted`/`.sort` narrowly when the array contains ≥2 canonical core agents). See [`src/plugin-handlers/AGENTS.md`](file:///Users/yeongyu/local-workspaces/omo/src/plugin-handlers/AGENTS.md) for the full history of why this exists. - **Hashline edit + read pairing:** Every `Read` tool output is tagged with `LINE#ID` content hashes; `hashline_edit` validates the hash before applying. Stale hash → reject. - **5-tier hook composition:** Session (24) + ToolGuard (16) + Transform (5) + Continuation (7) + Skill (2) = 54 base. With `team_mode.enabled`: +1 ToolGuard (`team-tool-gating`), +2 Transform (`team-mode-status-injector`, `team-mailbox-injector`), +4 direct event handlers in `src/plugin/event.ts` (`team-session-events/*`) = 61 total. Composed by `createCoreHooks()` + `createContinuationHooks()` + `createSkillHooks()`. - **Per-session MCP isolation:** Tier-3 MCP clients keyed by `${sessionID}:${skillName}:${serverName}` so the same skill in two sessions does not share state. - **Two fallback systems:** `model-fallback` (proactive, chat.params) vs `runtime-fallback` (reactive, session.error). They operate independently — no direct integration. - **OpenClaw bidirectional:** Outbound dispatchers fire on session events; inbound daemon polls Discord/Telegram and `send-keys` replies into the tracked tmux pane. - **Internal message injection is dangerous:** OpenCode의 stupid한 설계로 플러그인이 `session.prompt` / `session.promptAsync` 같은 메인 세션 메시지 API를 통해 메인 시스템을 망가뜨릴 수 있다. - Root cause to remember: OpenCode `promptAsync` returns before the prompt is durably accepted, and later failures can arrive as `session.error`. Multiple OMO hooks/tools can observe the same idle/error/completion edge and inject the same internal message into a live parent session. - Treat every `session.prompt` / `session.promptAsync` call as a write to shared session state. Production code may call them only inside `src/shared/prompt-async-gate.ts`; all other routes must use `dispatchInternalPrompt({ mode: "async" | "sync", ... })` or a proven equivalent gate. - Required gate semantics: reserve per session before dispatch, check active session state, keep a short post-dispatch hold, release only on intentional abort/recovery paths, and restore optimistic task/loop state when dispatch is skipped or fails later. - Forbidden patterns: raw prompt calls outside the shared gate, `postDispatchHoldMs: 0`, no-session fallback to raw prompt, and new internal message routes without duplicate-injection regression tests. - Tests must pin both the shared invariant and the route behavior: update the static raw-prompt audit, then add route-specific tests proving concurrent/live/idle/error triggers collapse to one dispatch. Cover background completion wakes, fallback retries, team mailbox live delivery, recovery continuations, CLI run resumes, Claude Code hook injections, and sync/background subagent prompts. ## CONVENTIONS - **Runtime:** Bun only (1.3.12 in CI). Never npm/yarn/pnpm. - **TypeScript:** strict mode, ESNext, bundler moduleResolution, `bun-types` (never `@types/node`). - **Tests:** Bun test (`bun:test`), co-located `*.test.ts`, given/when/then style — nested `describe` with `#given`/`#when`/`#then` prefixes, or inline `// given` / `// when` / `// then` comments. Never Arrange-Act-Assert comments. - **CI tests:** plain `bun test` runs the root Bun suite in one process; no sharding or split isolation runner. - **Test setup:** `test-setup.ts` preloaded via `bunfig.toml` resets session/cache state between tests. - **Factory pattern:** `createXXX()` for all tools, hooks, agents. - **File naming:** kebab-case for files and directories. - **Module structure:** `index.ts` barrel exports, **no catch-all files** (`utils.ts`, `helpers.ts`, `service.ts` banned), 200 LOC soft limit per file. - **Imports:** relative within a module, barrel imports across modules (`import { log } from "./shared"`). **No path aliases in `src/`** — never `@/`. `packages/web/` is the only exception: it uses `@/*` (Next.js convention) and has its own tsconfig. - **Config format:** JSONC with comments + trailing commas, Zod v4 validation, snake_case keys. - **Dual package:** `oh-my-opencode` + `oh-my-openagent` published simultaneously during the rename transition. - **Comments:** AI slop comment patterns blocked by `comment-checker` hook (binary: `@code-yeongyu/comment-checker`). Use `// @allow` to bypass single line, `// comment-checker-disable-file` at file top to bypass file. Sparingly. ## ANTI-PATTERNS (BLOCKING) - Never `as any`, `@ts-ignore`, `@ts-expect-error`. - Never suppress lint/type errors. - Never add emojis to code/comments unless user explicitly asks. - Never commit unless explicitly requested. - Never run `bun publish` directly — use the GitHub Actions workflow. - Never modify `package.json` `version` locally — handled by publish workflow. - Never write to existing files without reading them first (`write-existing-file-guard`). - Never use `background_cancel(all=true)` — cancel by `taskId` individually. - Never delete a failing test to make a build green. Fix the code. - Never em dashes / en dashes / AI filler ("simply", "obviously", "clearly", "moreover", "furthermore") in generated content. - Never create catch-all files (`utils.ts`, `helpers.ts`, `service.ts`). - Never empty catch blocks `catch(e) {}`. - Never test with Arrange-Act-Assert comments — use given/when/then. - Never dump business logic into `index.ts` — barrel exports only. - Prometheus may ONLY edit `.md` files (enforced by `prometheus-md-only` hook); FORBIDDEN paths: `src/`, `package.json`, config files. ## COMMANDS ```bash bun test # Root Bun test suite in one process bun run build # Build plugin (ESM bundle + .d.ts + cli bundle + schema generation) bun run build:all # Build + 11 platform binaries bun run build:schema # Regenerate assets/oh-my-opencode.schema.json bun run build:model-capabilities # Refresh shared/model-capabilities cache from models.dev bun run typecheck # tsgo --noEmit (uses @typescript/native-preview, NOT tsc) bun run clean # rm -rf dist bunx oh-my-opencode install # Interactive setup wizard bunx oh-my-opencode doctor # Health diagnostics (4 categories: System / Config / Tools / Models) bunx oh-my-opencode run # Non-interactive session (auto-completes when todos done + no bg tasks) bunx oh-my-opencode mcp-oauth login # Tier-3 MCP OAuth (PKCE + DCR) ``` ## CI/CD | Workflow | Trigger | Purpose | |----------|---------|---------| | `ci.yml` | push/PR to master/dev | Tests, typecheck, build, codex-compatibility (`bun run test:codex`, ubuntu/macos/windows), auto-commit schema on master push, draft "next" release on dev push (blocks master-targeting PRs) | | `publish.yml` | manual dispatch | Test, typecheck, preflight-trust (OIDC verify 24 packages), dual npm publish (`oh-my-opencode` + `oh-my-openagent`) + `lazycodex` npm alias (`publish_lazycodex`, default on) + optional Codex marketplace sync to `code-yeongyu/lazycodex` (`sync_lazycodex_marketplace`, default off, needs `LAZYCODEX_SYNC_TOKEN`), platform binaries, GitHub release, merge to master | | `publish-platform.yml` | called by publish.yml | 11 platform binaries via `bun compile` (darwin/linux/windows) | | `sisyphus-agent.yml` | @mention or manual dispatch | AI agent handles issues/PRs | | `refresh-model-capabilities.yml` | weekly cron / dispatch | Refresh model capabilities from models.dev API | | `cla.yml` | issue_comment / PR | CLA assistant for contributors | | `lint-workflows.yml` | push/PR touching `.github/workflows/**` | actionlint only (`shellcheck=""` disables shellcheck) | | `web-ci.yml` | push/PR to master/dev touching `packages/web/**`, `docs/**`, or the workflow file itself | format-check, lint, type-check, next build, opennextjs-cloudflare build | | `web-deploy.yml` | push to master/dev touching `packages/web/**`, `docs/**`, or the workflow file itself, OR manual dispatch | Cloudflare Workers deploy via `cloudflare/wrangler-action@v3` (requires `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` secrets) | ## PR MERGE POLICY - **PRs into `dev` MUST use merge commits.** - Use `gh pr merge --merge --delete-branch` after CI, review-work, and Cubic pass. - **NEVER squash merge or rebase merge** PRs in this repository, even if a generic workflow, skill, or GitHub default suggests it. - If another instruction says `--squash` or `--rebase`, this repo-level rule overrides it. ## NOTES - **Logger:** writes `oh-my-opencode.log` to the OS temp dir (`/tmp` on Linux, `/var/folders/.../T/` on macOS, `%TEMP%` on Windows — i.e. Node's `os.tmpdir()`). Rotated at 50 MB; previous segments live at `.1` and `.2` (oldest dropped). - **Background tasks:** 5 concurrent per `${providerID}/${modelID}` key by default (configurable via `background_task.modelConcurrency` / `providerConcurrency`); FIFO queue when slots full. - **Plugin load timeout:** 10s for Claude Code plugin discovery. - **Model fallback:** per-agent chains in `src/shared/model-requirements.ts`. **There is no single global priority.** - **Two fallback systems:** `model-fallback` (proactive, chat.params, hardcoded chains) vs `runtime-fallback` (reactive, session.error, configurable per-category/agent). - **Config migration:** idempotent via `_migrations` tracking, atomic writes with timestamped backups. - **Build:** `bun build` (ESM) + `tsc --emitDeclarationOnly`, externals: `@ast-grep/napi`, `zod`. - **CI tests:** root tests run through plain `bun test`; `packages/web/**` has its own package-level CI workflow. - **120 barrel `index.ts` files** establish module boundaries. - **Architecture rules** enforced via the `rules-injector` hook reading `.omo/rules/*.md`. As of v4.2.0 only `test-discipline.md` ships; legacy `modular-code-enforcement.md` was retired. - **Windows builds:** run on `windows-latest` (not cross-compiled) to avoid Bun segfaults. - **Platform binaries:** detect AVX2 + libc family at runtime, fallback to baseline if needed. - **IntentGate (`keyword-detector`):** classifies user intent (`ultrawork`/`ulw`, `search`, `analyze`, `team`) and injects mode-specific prompts. - **Hashline edit:** every `Read` output tagged with `LINE#ID` content hashes (chars from `ZPMQVRWSNKTXJBYH`); edits reject on hash mismatch. - **zauc-mocks pattern:** 9 directories named `zauc-mocks-*` (5 in `src/hooks/`, 2 in `src/tools/`, 1 each in `src/mcp/` and `src/shared/`) hold `mock.module()` setup that must load alphabetically before the tests that consume those mocked modules. The `zauc-` prefix is purely a sort-order hack for `bun:test` discovery; these are NOT hooks/tools. - **Test discipline meta-audits:** two files (`src/shared/mock-module-lifecycle-audit.test.ts` and `src/shared/prompt-async-route-audit.test.ts`) parse the entire codebase via the TS compiler API and FAIL the suite when an architectural invariant is violated (`mock.module()` without restore, raw `session.promptAsync` outside the gate). - **Docs:** see [`docs/guide/`](file:///Users/yeongyu/local-workspaces/omo/docs/guide/) for user-facing guides (overview, installation, orchestration, agent-model-matching, team-mode), [`docs/reference/`](file:///Users/yeongyu/local-workspaces/omo/docs/reference/) for CLI/configuration/features reference. v4.2.0+ adds [`CHANGELOG.md`](file:///Users/yeongyu/local-workspaces/omo/CHANGELOG.md), [`docs/reference/known-issues.md`](file:///Users/yeongyu/local-workspaces/omo/docs/reference/known-issues.md), [`docs/reference/prompt-async-gate-rfc.md`](file:///Users/yeongyu/local-workspaces/omo/docs/reference/prompt-async-gate-rfc.md), and [`docs/reference/release-process.md`](file:///Users/yeongyu/local-workspaces/omo/docs/reference/release-process.md). - **Rules files** (auto-injected by `rules-injector` hook): [`.omo/rules/modular-code-enforcement.md`](file:///Users/yeongyu/local-workspaces/omo/.omo/rules/modular-code-enforcement.md) + [`.omo/rules/test-discipline.md`](file:///Users/yeongyu/local-workspaces/omo/.omo/rules/test-discipline.md) (forbids `setTimeout(resolve, N)` / `await sleep(N)` in tests unless time IS the SUT). Scans `.omo/rules/`, `.claude/rules/`, `.cursor/rules/`, `.github/instructions/`, plus `.github/copilot-instructions.md` and `.mdc` files. - **Process cleanup:** Background-agent error handlers are now log-only — no force-exit on transient errors. Opt out entirely via `OMO_DISABLE_PROCESS_CLEANUP=1` env var. - **First-prompt watchdog:** `src/hooks/runtime-fallback/first-prompt-watchdog.ts` (206 LOC) detects subagent sessions producing no progress within 90s and triggers fallback / abort. - **ParentWakeNotifier:** Background-agent parent-wake state extracted to `src/features/background-agent/parent-wake-notifier.ts` (587 LOC) with dependency-injected client and enqueue callback. - **Workspace migration:** Runtime state migrated from `.sisyphus/` → `.omo/`. Legacy `.sisyphus/` still exists during transition; `src/shared/legacy-workspace-migration.ts` copies it forward on first load. - **CI nuance:** PRs targeting `master` are hard-blocked — they MUST target `dev`. CI auto-commits schema changes on master push and creates a draft "next" release on dev push.