oh-my-harness 0.19.0 β†’ 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,46 +2,40 @@
2
2
 
3
3
  # 🐴 oh-my-harness
4
4
 
5
- **Tame your AI coding agents with natural language.**
5
+ **CLAUDE.md is a request. oh-my-harness is enforcement.**
6
+
7
+ One command turns "TDD enforced, block dangerous commands" into hooks that actually **block** your AI coding agent β€” for Claude Code, Codex and Pi at once.
6
8
 
7
9
  [![npm version](https://img.shields.io/npm/v/oh-my-harness.svg)](https://www.npmjs.com/package/oh-my-harness)
8
10
  [![npm downloads](https://img.shields.io/npm/dm/oh-my-harness.svg)](https://www.npmjs.com/package/oh-my-harness)
9
11
  [![CI](https://github.com/kyu1204/oh-my-harness/actions/workflows/ci.yml/badge.svg)](https://github.com/kyu1204/oh-my-harness/actions/workflows/ci.yml)
10
12
  [![License: MIT](https://img.shields.io/github/license/kyu1204/oh-my-harness.svg)](LICENSE)
11
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.7+-blue.svg)](https://www.typescriptlang.org/)
12
- [![Node.js](https://img.shields.io/badge/Node.js-20+-green.svg)](https://nodejs.org/)
13
13
 
14
- > Stop hand-writing CLAUDE.md files. Describe your project, get enforced guardrails.
14
+ <img src="docs/demo.gif" alt="omh init generates hooks; an AI agent's commit, rm -rf and untested edit are blocked" width="900">
15
15
 
16
16
  </div>
17
17
 
18
- ---
19
-
20
- ## 😀 The Problem
21
-
22
- Every AI code agent needs configuration files. Claude Code needs `CLAUDE.md` + hooks. Cursor needs `.cursorrules`. Codex needs `AGENTS.md`. You end up:
18
+ ```bash
19
+ npx oh-my-harness init "React + FastAPI, TDD enforced, lint on save"
20
+ ```
23
21
 
24
- - πŸ“‹ Copy-pasting config files between projects
25
- - πŸ”“ Forgetting to set up TDD enforcement hooks
26
- - πŸ’₯ Agents committing code without running tests
27
- - 🎲 Inconsistent behavior across projects
22
+ That is the whole setup. Your agent now hits a wall when it tries to:
28
23
 
29
- ## ✨ The Solution
24
+ | Agent tries to... | Result |
25
+ |---|---|
26
+ | `git commit` while tests fail | β›” **Blocked** |
27
+ | edit `src/foo.ts` before touching `foo.test.ts` | β›” **Blocked** (TDD guard) |
28
+ | run `rm -rf /`, `chmod -R 777`, or any pattern you list | β›” **Blocked** |
29
+ | write into `node_modules/`, `.next/`, `dist/` | β›” **Blocked** |
30
+ | commit on a branch already merged to main | β›” **Blocked** |
31
+ | save a file | βœ… auto-lint |
32
+ | push a branch | βœ… auto-PR |
30
33
 
31
- ```bash
32
- oh-my-harness init "React + FastAPI fullstack, TDD enforced, lint on save"
33
- ```
34
+ Every decision is logged to `.omh/state/events.jsonl` β€” `omh stats` shows what your agent tried and what got stopped.
34
35
 
35
- That's it. oh-my-harness generates **enforced guardrails** β€” not just instructions, but hooks that actually **block** bad behavior:
36
+ ## Why not just write CLAUDE.md?
36
37
 
37
- - ❌ Commit without tests passing? **Blocked.**
38
- - ❌ Edit source without updating tests first? **Blocked.** _(TDD Guard)_
39
- - ❌ Write to `node_modules/` or `.next/`? **Blocked.**
40
- - ❌ Run `rm -rf /`? **Blocked.**
41
- - ❌ Commit on a merged branch? **Blocked.**
42
- - βœ… Auto-lint on every file save? **Done.**
43
- - βœ… Auto-create PR after push? **Done.**
44
- - πŸ“Š Track all hook events for analytics? **Done.**
38
+ Because agents read instructions and then forget them halfway through a long session. A rule in a markdown file is a suggestion; a `PreToolUse` hook that returns `{"decision":"block"}` is a fact. oh-my-harness writes the markdown **and** the hooks, from one `harness.yaml`, and keeps them in sync (`omh sync --check` fails CI when they drift). Side-by-side with rule-sync tools and git hooks: [docs/comparison.md](docs/comparison.md).
45
39
 
46
40
  ---
47
41
 
@@ -64,135 +58,7 @@ omh diff # Preview what `omh sync` would change
64
58
  omh sync --check # Fail (exit 1) if generated files are out of date β€” CI gate
65
59
  ```
66
60
 
67
- ### πŸ”„ Keeping generated files in sync
68
-
69
- `harness.yaml` is the source of truth, so the committed `CLAUDE.md`, hooks, and
70
- runtime configs can drift if someone edits `harness.yaml` without re-running
71
- `omh sync`. Three commands keep them honest:
72
-
73
- | Command | Use |
74
- |---------|-----|
75
- | `omh sync --check` | CI gate β€” exits non-zero (and lists the files) when generated output is stale, writes nothing |
76
- | `omh diff` | Human preview of exactly what `omh sync` would change |
77
- | `omh doctor --strict` | Health check that also fails on drift (plain `omh doctor` warns) |
78
-
79
- ```yaml
80
- # .github/workflows/ci.yml
81
- - run: npx oh-my-harness sync --check # fails the build if the harness is out of date
82
- ```
83
-
84
- The hook manifest records the oh-my-harness version that generated it, so an
85
- upgrade that changes output is surfaced as a "re-run `omh sync`" hint.
86
-
87
- ### πŸ“ What Gets Generated
88
-
89
- ```text
90
- ~/.omh/
91
- └── config.json # AI provider config (global, not per-project)
92
-
93
- your-project/
94
- β”œβ”€β”€ CLAUDE.md # Claude Code instructions (TDD rules, standards)
95
- β”œβ”€β”€ AGENTS.md # Codex CLI instructions (same managed sections)
96
- β”œβ”€β”€ harness.yaml # Your harness config (source of truth)
97
- β”œβ”€β”€ .omh/ # Single source of truth β€” hooks + state
98
- β”‚ β”œβ”€β”€ hooks/
99
- β”‚ β”‚ β”œβ”€β”€ catalog-branch-guard.sh # Blocks commits on merged branches
100
- β”‚ β”‚ β”œβ”€β”€ catalog-tdd-guard.sh # Enforces test-first workflow
101
- β”‚ β”‚ β”œβ”€β”€ catalog-commit-test-gate.sh # Tests must pass before commit
102
- β”‚ β”‚ β”œβ”€β”€ catalog-path-guard.sh # Protects build outputs
103
- β”‚ β”‚ β”œβ”€β”€ catalog-command-guard.sh # Blocks dangerous commands
104
- β”‚ β”‚ β”œβ”€β”€ catalog-lint-on-save.sh # Auto-lint on save
105
- β”‚ β”‚ └── catalog-auto-pr.sh # Auto-create PR after push
106
- β”‚ β”œβ”€β”€ loop/
107
- β”‚ β”‚ └── worktree/ # gitignored β€” the loop's isolated git worktree (see Loop Engine)
108
- β”‚ β”œβ”€β”€ state/ # gitignored β€” log/runtime data
109
- β”‚ β”‚ β”œβ”€β”€ events.jsonl # Unified hook event log (powers omh stats)
110
- β”‚ β”‚ β”œβ”€β”€ loop/ # run.json (lock + identity), stop flag, runs/<id>/events.jsonl
111
- β”‚ β”‚ └── tdd-edits.json # TDD guard working state
112
- β”‚ └── manifest.json # Generated-files manifest
113
- β”œβ”€β”€ .claude/
114
- β”‚ β”œβ”€β”€ settings.json # Claude permissions + hooks β†’ .omh/hooks/*.sh
115
- β”‚ β”œβ”€β”€ skills/omh-loop/SKILL.md # "run this as a loop" skill
116
- β”‚ └── oh-my-harness.json # Harness init/sync state
117
- └── .codex/
118
- β”œβ”€β”€ config.toml # [features] hooks = true, goals = true
119
- └── hooks.json # Codex hooks β†’ .omh/hooks/*.sh (same scripts)
120
- ```
121
-
122
- ---
123
-
124
- ## βš™οΈ How It Works
125
-
126
- ```text
127
- ~/.omh/config.json β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
128
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
129
- β”‚ β€’ Claude CLI │──▢│ NL Processing │◀── "React + FastAPI
130
- β”‚ β€’ Claude API β”‚ β”‚ (describe your β”‚ TDD enforced"
131
- β”‚ β€’ OpenAI API β”‚ β”‚ β”‚
132
- β”‚ β€’ Gemini API β”‚ β”‚ β”‚
133
- β”‚ β€’ Codex OAuth β”‚ β”‚ β”‚
134
- β”‚ β€’ Codex OAuth β”‚ β”‚ β”‚
135
- β”‚ API β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
136
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
137
- (global AI config) β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
138
- β”‚ Project Detector β”‚ ← Auto-detects language,
139
- β”‚ (14 languages) β”‚ framework, package manager
140
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
141
- β”‚
142
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
143
- β”‚ harness.yaml β”‚ ← Source of truth
144
- β”‚ (editable, git β”‚ (hooks + rules)
145
- β”‚ trackable) β”‚
146
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
147
- β”‚
148
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
149
- β–Ό β–Ό β–Ό
150
- β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
151
- β”‚CLAUDE.md β”‚ β”‚ Hooks β”‚ β”‚settings. β”‚
152
- β”‚ (rules) β”‚ β”‚(enforce) β”‚ β”‚ json β”‚
153
- β”‚ β”‚ β”‚ β”‚ β”‚(perms) β”‚
154
- β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
155
- ```
156
-
157
- ### πŸ” Project Detector
158
-
159
- oh-my-harness automatically detects your project type and injects accurate facts into the LLM prompt:
160
-
161
- | Language | Detection | Commands |
162
- |----------|-----------|----------|
163
- | 🟦 TypeScript/JS | package.json, tsconfig | pnpm/npm/yarn test, eslint, tsc |
164
- | 🐍 Python | pyproject.toml, requirements.txt, Pipfile, manage.py, .python-version | pytest, ruff, black, isort, mypy |
165
- | 🍎 Swift | Package.swift, .xcodeproj | swift test, xcodebuild |
166
- | πŸ¦€ Rust | Cargo.toml | cargo test, cargo clippy |
167
- | 🐹 Go | go.mod | go test, golangci-lint |
168
- | β˜• Java/Kotlin | pom.xml, build.gradle | mvn test, ./gradlew test |
169
- | πŸ’Ž Ruby | Gemfile | bundle exec rspec |
170
- | 🐘 PHP | composer.json | phpunit |
171
- | 🎯 Dart/Flutter | pubspec.yaml | dart test, flutter test |
172
- | ⚑ C/C++ | CMakeLists.txt | cmake, make |
173
- | 🟣 C#/.NET | *.csproj | dotnet test |
174
- | πŸ’§ Elixir | mix.exs | mix test |
175
- | πŸ”· Scala | build.sbt | sbt test |
176
- | ⚑ Zig | build.zig | zig build test |
177
-
178
- ### πŸ€– AI Provider Setup
179
-
180
- oh-my-harness supports multiple AI providers for natural language mode:
181
-
182
- | Provider | Setup | Available Models | Default |
183
- |----------|-------|------------------|---------|
184
- | **Claude CLI** | `claude` command installed | Opus 4.6, Sonnet 4.6, Haiku 4.5 | βœ“ |
185
- | **Claude API** | Set `ANTHROPIC_API_KEY` | Opus 4.6, Sonnet 4.6, Haiku 4.5 | Sonnet 4.6 |
186
- | **OpenAI API** | Set `OPENAI_API_KEY` | GPT-5.5, GPT-5.4, GPT-5.4-mini, GPT-5.4-nano, GPT-4.1, GPT-4.1-mini, o3, o4-mini | GPT-5.5 |
187
- | **Gemini API** | Set `GOOGLE_API_KEY` | Gemini 2.5 Pro, Gemini 2.5 Flash, Gemini 2.5 Flash Lite, Gemini 3.1 Pro Preview | Gemini 2.5 Pro |
188
- | **Codex OAuth** | `codex` command installed + `codex login`; runs `codex exec` | GPT-5.5, GPT-5.4, GPT-5.4-mini | GPT-5.5 |
189
- | **Codex OAuth API** | `omh config` device-code login; imports `~/.codex/auth.json` once if present, then uses `~/.omh` | GPT-5.5, GPT-5.4, GPT-5.4-mini | GPT-5.5 |
190
-
191
- Configuration is saved to `~/.omh/config.json` and selected via interactive UI on first use:
192
-
193
- ```bash
194
- omh init # will prompt for AI provider selection and model choice
195
- ```
61
+ πŸ“– Deeper: [what gets generated & CI drift check](docs/generated-files.md) Β· [how it works, project detector, AI providers](docs/how-it-works.md)
196
62
 
197
63
  ---
198
64
 
@@ -268,91 +134,7 @@ on a non-blocking block (e.g. `lint-on-save`) is reported and ignored.
268
134
 
269
135
  ## πŸ” Autonomous Loop Engine
270
136
 
271
- Once `omh init`/`omh sync` has run (with `loop.enabled: true`, the default),
272
- any agent session (Claude Code, Codex, Pi) can be told **"run this as a loop"** β€” the generated `omh-loop` skill turns that
273
- into a fully set-up autonomous loop, no manual wiring:
274
-
275
- ```text
276
- you: "ship the remaining Phase B tasks as a loop"
277
- β”‚
278
- β–Ό omh-loop skill (the session becomes the ARCHITECT)
279
- 1. writes the `loop.ledger` β€” default WORKPLAN.md; goal gates + task checkboxes
280
- 2. writes `loop.workOrders`/*.md β€” default docs/work-orders; one exact work order per task
281
- 3. runs `omh loop start` β€” a detached supervisor; with `isolate: true` (default), in its own git worktree
282
- 4. attaches monitoring β€” omh loop status, then tail -f .omh/state/loop/runs/<id>/events.jsonl
283
- β”‚
284
- β–Ό loop (fresh headless session per iteration, cheap model)
285
- pick next unchecked task β†’ implement its work order exactly β†’ run its
286
- acceptance commands β†’ tick checkbox + commit β†’ repeat until the sentinel
287
- ```
288
-
289
- The design follows a battle-tested pattern from real autonomous runs: state
290
- lives in **files** (ledger + git log), never in a conversation. Each iteration
291
- is a fresh headless session (`claude -p` / `codex exec --dangerously-bypass-hook-trust` / `pi --print --no-session`)
292
- on an explicit cheap model, so token use stays flat.
293
-
294
- ### Why it doesn't fall over
295
-
296
- Every guard below exists because the failure actually happened somewhere:
297
-
298
- | Guard | Failure it prevents |
299
- |-------|---------------------|
300
- | Sentinel = fixed string, whole line, clean exit only | a crashed turn (or a turn merely *mentioning* the sentinel) ending the loop as "complete" |
301
- | Three **separate** backoffs β€” usage limit / empty-output failed turn / consecutive `BLOCKED` | a loop once spun **266 iterations** doing nothing because waiting-on-a-human was treated like a failure |
302
- | `loop-guard` hook blocks the loop writing its own work orders (Edit/Write **and** Bash redirection/`cd`) | self-approval: the loop authoring the spec it then implements |
303
- | `loop-guard` blocks architect-only paths, listed **by name** | an abstract "don't improvise" is ignored; a named path is obeyed |
304
- | Worktree isolation (`isolate: true`, default) | the loop and the architect fighting over the same working tree |
305
- | Ledger seeded once, re-seeded only when the main-tree copy is newer | the loop's progress being rolled back β€” or a second goal reusing the first goal's ledger |
306
-
307
- ### Configuration
308
-
309
- On by default. Everything is optional in `harness.yaml`:
310
-
311
- ```yaml
312
- loop:
313
- enabled: true # false removes all loop assets on next sync
314
- ledger: WORKPLAN.md # single source of truth
315
- workOrders: docs/work-orders # architect-written task specs
316
- model: sonnet # cheap implementation model (always explicit)
317
- sentinel: OMH_GOAL_COMPLETE # whole-line completion signal
318
- interval: 120 # seconds between iterations
319
- blockedBackoff: 1800 # backoff once stallStreak blocked/idle turns pile up
320
- limitBackoff: 1800 # backoff after a provider usage limit
321
- emptyBackoff: 300 # backoff after a crashed (empty-output / timed-out) turn
322
- stallStreak: 3 # consecutive blocked/idle turns before blockedBackoff
323
- turnTimeout: 7200 # hard per-turn timeout (seconds); the turn is killed past this
324
- architectOnly: [] # paths the loop must never touch (name them!)
325
- isolate: true # run in .omh/loop/worktree on branch omh-loop
326
- runtime: claude # claude | codex | pi
327
- ```
328
-
329
- ### Operating it
330
-
331
- ```bash
332
- omh loop start # preflight, then a detached supervisor (the skill does this for you)
333
- omh loop status # run id, pid, iteration, last event, events path
334
- tail -f .omh/state/loop/runs/<id>/events.jsonl # progress / blocked / idle / limit / crash
335
- omh loop stop [--now] # stop flag + SIGTERM to the whole process group (--now: 1s grace)
336
- omh loop clean [--branch] # remove the worktree and stale state (and optionally the omh-loop branch)
337
- ```
338
-
339
- The supervisor is TypeScript (`src/loop/`), not a generated script: the runtime
340
- is spawned from an argv array (no shell), each turn is judged by the **ledger
341
- diff and git HEAD** rather than by grepping output, and the run lock is a single
342
- `run.json` created atomically with `link(2)`. POSIX only β€” it relies on process
343
- groups and `ps` for identity checks.
344
-
345
- Known limit: the ledger is seeded into the worktree once per goal (by content
346
- hash). Editing the main-tree ledger mid-run does nothing until the next start,
347
- which then re-seeds it β€” add mid-run tasks as new work orders instead, and edit
348
- the worktree's ledger if you must.
349
-
350
- A task the loop cannot finish (needs a human, or 3 failed attempts) is marked
351
- `BLOCKED: <reason>` in the ledger and skipped β€” the loop never idles waiting
352
- for a person. When you unblock it (e.g. fix an architect-only file), the loop
353
- picks it back up after its backoff. When the goal completes in isolation, merge
354
- the `omh-loop` branch and **re-verify** β€” a clean textual merge is not a
355
- semantic one.
137
+ Hand omh a `WORKPLAN.md` and it runs one work order per fresh agent session, in an isolated git worktree, with hard-blocked protocol rules. `omh loop start` / `omh loop status` / `omh loop stop`. Full guide: [docs/loop-engine.md](docs/loop-engine.md).
356
138
 
357
139
  ---
358
140
 
@@ -383,210 +165,14 @@ omh test # Dry-run verify all hooks
383
165
  omh stats # TUI analytics dashboard
384
166
  ```
385
167
 
386
- ### 🧹 `omh uninstall` β€” Safe generated-file cleanup
387
-
388
- `omh uninstall` removes oh-my-harness generated artifacts while preserving user
389
- content in merged files.
390
-
391
- ```bash
392
- omh uninstall --dry-run
393
- omh uninstall -y
394
- omh uninstall -y --purge
395
- ```
396
-
397
- Safety behavior:
398
-
399
- - Prints the same uninstall plan for dry-run and real execution.
400
- - Recommends backing up before execution; use `--skip-backup-warning` only for
401
- automation that already handles backups.
402
- - Keeps `harness.yaml` by default; `--purge` removes it.
403
- - Preserves user content in `CLAUDE.md`, `AGENTS.md`, `.claude/settings.json`,
404
- `.codex/hooks.json`, `.codex/config.toml`, and user Pi extensions.
405
- - Removes only OMH-owned hook commands that point at this project's
406
- `.omh/hooks` directory.
407
- - Warns when `.codex/config.toml` feature flags (`hooks`/`goals`) are removed,
408
- because manually-owned feature settings cannot be distinguished from OMH
409
- generated settings.
410
- - Warns that `.codex/config.toml` comments may be lost when TOML is rewritten.
411
- - Uses backups for modified files and restores them on stop-on-error failures;
412
- `--continue-on-error` records failures and keeps applying independent
413
- operations.
414
-
415
- ### 🩺 `omh doctor`
416
-
417
- ```text
418
- oh-my-harness: running health checks...
419
- βœ“ .claude/oh-my-harness.json found
420
- βœ“ CLAUDE.md exists with intact markers
421
- βœ“ .claude/settings.json is valid
422
- βœ“ All hook scripts are executable
423
- oh-my-harness: all checks passed
424
- ```
425
-
426
- ### πŸ§ͺ `omh test` β€” Dry-Run Verification
427
-
428
- Simulates hook inputs to verify block/allow behavior without entering Claude Code:
429
-
430
- ```text
431
- β”Œ omh test Harness dry-run verification
432
- β”‚
433
- β—‡ Branch guard
434
- β”‚ βœ“ git commit on feat/my-feature β†’ ALLOWED
435
- β”‚
436
- β—‡ TDD Guard
437
- β”‚ βœ“ src/foo.ts without test β†’ BLOCKED
438
- β”‚ βœ“ tests/unit/foo.test.ts β†’ ALLOWED
439
- β”‚ βœ“ README.md β†’ ALLOWED
440
- β”‚
441
- β—‡ File guards
442
- β”‚ βœ“ node_modules/test-file.js β†’ BLOCKED
443
- β”‚ βœ“ dist/test-file.js β†’ BLOCKED
444
- β”‚ βœ“ src/index.ts β†’ ALLOWED
445
- β”‚
446
- β—‡ Command guards
447
- β”‚ βœ“ "rm -rf /" β†’ BLOCKED
448
- β”‚ βœ“ "npm test" β†’ ALLOWED
449
- β”‚
450
- β”” 14/14 checks passed βœ“
451
- ```
452
-
453
- ### πŸ“Š `omh stats` β€” TUI Analytics Dashboard
454
-
455
- Interactive dashboard powered by [ink](https://github.com/vadimdemedes/ink) with 3 views:
456
-
457
- ```text
458
- [1] Overview [2] Timeline [3] Blocks d:filter r:reload q:quit
459
-
460
- Active: 8 Events: 1213 Block rate: 2%
461
-
462
- branch-guard β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 0b/202a
463
- tdd-guard β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 14b/68a
464
- commit-test-gate β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 0b/199a
465
- path-guard β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 4b/76a
466
- command-guard β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 4b/202a
467
-
468
- Dormant (0 hits):
469
- β–‘ lockfile-guard
470
- β–‘ secret-file-guard
471
- ```
472
-
473
- - **Overview** β€” Active blocks with hit bar charts + dormant block detection
474
- - **Timeline** β€” 24-hour heatmap + block rate + peak hour
475
- - **Blocks** β€” Scrollable detail view with params, hits, last block reason
476
-
477
- Keyboard: `1/2/3` views, `↑/↓` scroll, `d` date filter, `r` reload, `q` quit
478
-
479
- ---
480
-
481
- ## πŸ“Š Stateful Hook Logging
482
-
483
- Every hook invocation is recorded in `.omh/state/events.jsonl`:
484
-
485
- ```jsonl
486
- {"ts":"2026-03-18T08:00:00Z","event":"PreToolUse","hook":"catalog-tdd-guard.sh","decision":"block","reason":"TDD β€” foo.test.* ν…ŒμŠ€νŠΈ νŒŒμΌμ„ λ¨Όμ € μˆ˜μ •ν•˜μ„Έμš”"}
487
- {"ts":"2026-03-18T08:00:05Z","event":"PreToolUse","hook":"catalog-command-guard.sh","decision":"allow","reason":""}
488
- ```
489
-
490
- This powers `omh test` live verification and `omh stats` analytics.
491
-
492
- ---
493
-
494
- ## πŸ—οΈ Architecture
495
-
496
- ```text
497
- oh-my-harness/
498
- β”œβ”€β”€ bin/ # CLI entry point
499
- β”œβ”€β”€ src/
500
- β”‚ β”œβ”€β”€ catalog/
501
- β”‚ β”‚ β”œβ”€β”€ blocks/ # 18 building block definitions
502
- β”‚ β”‚ β”œβ”€β”€ types.ts # BuildingBlock, HookEntry schemas
503
- β”‚ β”‚ β”œβ”€β”€ registry.ts # Block discovery & search
504
- β”‚ β”‚ β”œβ”€β”€ template-engine.ts # Handlebars rendering + applyDefaults
505
- β”‚ β”‚ └── converter.ts # HookEntry[] β†’ rendered scripts
506
- β”‚ β”œβ”€β”€ cli/
507
- β”‚ β”‚ β”œβ”€β”€ commands/ # init, doctor, catalog, hook, sync, test, loop (start/run/stop/status/clean)
508
- β”‚ β”‚ β”œβ”€β”€ stats/ # TUI dashboard (ink/React)
509
- β”‚ β”‚ β”‚ β”œβ”€β”€ App.tsx # App shell (tab bar, keyboard nav)
510
- β”‚ β”‚ β”‚ β”œβ”€β”€ data.ts # Data aggregation layer
511
- β”‚ β”‚ β”‚ └── components/ # Overview, Timeline, Blocks views
512
- β”‚ β”‚ β”œβ”€β”€ harness-tester.ts # Hook simulation engine
513
- β”‚ β”‚ β”œβ”€β”€ event-logger.ts # events.jsonl read/write/stats
514
- β”‚ β”‚ β”œβ”€β”€ event-verifier.ts # Event-based verification
515
- β”‚ β”‚ β”œβ”€β”€ tui/ # Interactive provider & model selection
516
- β”‚ β”‚ β”œβ”€β”€ provider-setup.ts # Provider configuration UI
517
- β”‚ β”‚ └── tool-checker.ts # Command executable checks
518
- β”‚ β”œβ”€β”€ loop/ # autonomous loop supervisor (TypeScript, no generated shell)
519
- β”‚ β”‚ β”œβ”€β”€ supervisor.ts # one run: lock β†’ worktree/seed β†’ turns β†’ release
520
- β”‚ β”‚ β”œβ”€β”€ state.ts # run.json link-lock, events, atomic writes
521
- β”‚ β”‚ β”œβ”€β”€ classify.ts # turn verdict from ledger diff + HEAD; wait policy
522
- β”‚ β”‚ β”œβ”€β”€ runtime.ts # argv per runtime, turn execution in its own process group
523
- β”‚ β”‚ β”œβ”€β”€ worktree.ts # git worktree lifecycle, asset sync, ledger seeding
524
- β”‚ β”‚ β”œβ”€β”€ protocol.ts # the rules, rendered into prompt / CLAUDE.md / skill
525
- β”‚ β”‚ └── stop.ts # identity-checked group stop, group sweep
526
- β”‚ β”œβ”€β”€ core/
527
- β”‚ β”‚ β”œβ”€β”€ harness-schema.ts # harness.yaml Zod schema
528
- β”‚ β”‚ β”œβ”€β”€ merged-config.ts # MergedConfig + HooksConfig interfaces
529
- β”‚ β”‚ β”œβ”€β”€ harness-converter-v2.ts # harness.yaml β†’ MergedConfig (catalog pipeline)
530
- β”‚ β”‚ └── generator.ts # Orchestrates all generators
531
- β”‚ β”œβ”€β”€ generators/
532
- β”‚ β”‚ β”œβ”€β”€ claude-md.ts # CLAUDE.md with idempotent markers
533
- β”‚ β”‚ β”œβ”€β”€ hooks.ts # Hook scripts + event logger injection
534
- β”‚ β”‚ β”œβ”€β”€ settings.ts # .claude/settings.json
535
- β”‚ β”‚ └── gitignore.ts # .gitignore updater
536
- β”‚ β”œβ”€β”€ detector/
537
- β”‚ β”‚ β”œβ”€β”€ project-detector.ts # Deterministic project detection
538
- β”‚ β”‚ β”œβ”€β”€ types.ts # ProjectFacts, Detector interface
539
- β”‚ β”‚ └── detectors/ # 14 language detectors
540
- β”‚ β”œβ”€β”€ cli/
541
- β”‚ β”‚ β”œβ”€β”€ tui/ # Interactive provider & model selection
542
- β”‚ β”‚ └── provider-setup.ts # Provider configuration UI
543
- β”‚ └── nl/
544
- β”‚ β”œβ”€β”€ provider-registry.ts # Multi-provider definitions
545
- β”‚ β”œβ”€β”€ config-store.ts # ~/.omh/config.json persistence
546
- β”‚ β”œβ”€β”€ providers/ # Provider implementations
547
- β”‚ β”‚ β”œβ”€β”€ claude-cli.ts
548
- β”‚ β”‚ β”œβ”€β”€ claude-api.ts
549
- β”‚ β”‚ β”œβ”€β”€ openai-api.ts
550
- β”‚ β”‚ └── gemini-api.ts
551
- β”‚ β”œβ”€β”€ parse-intent.ts # LLM prompt integration
552
- β”‚ └── prompt-templates.ts # NL prompt construction
553
- └── tests/ # 900+ tests (unit + integration)
554
- ```
168
+ Details for `uninstall`, `doctor`, `test`, `stats` and hook event logging: [docs/commands.md](docs/commands.md). Internals: [docs/architecture.md](docs/architecture.md) Β· [roadmap](docs/roadmap.md).
555
169
 
556
170
  ---
557
171
 
558
172
  ## πŸ“¦ Requirements
559
173
 
560
174
  - **Node.js** >= 20
561
- - **Claude CLI** (optional, for default NL mode) β€” [Install guide](https://docs.anthropic.com/en/docs/claude-code)
562
- - **API Keys** (optional, for Claude/OpenAI/Gemini API modes) β€” set `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or `GOOGLE_API_KEY`
563
- - **Codex CLI OAuth** (optional, for `codex` CLI-wrapper mode) β€” install `codex` and run `codex login`
564
- - **Codex OAuth API** (optional, experimental direct mode) β€” run `omh config` and choose Codex OAuth API to complete device-code sign-in; credentials are stored under `~/.omh`
565
-
566
- ---
567
-
568
- ## πŸ—ΊοΈ Roadmap
569
-
570
- - [x] `npx oh-my-harness` β€” zero-install usage
571
- - [x] `omh sync` β€” regenerate from harness.yaml
572
- - [x] Building block catalog β€” 18 verified hook templates
573
- - [x] Project detector β€” 14 language auto-detection
574
- - [x] `omh test` β€” dry-run hook verification
575
- - [x] `omh stats` β€” TUI analytics dashboard (ink)
576
- - [x] Stateful hook logging β€” events.jsonl
577
- - [x] TDD Guard β€” enforce test-first workflow
578
- - [x] Multi-provider AI support β€” Claude API, OpenAI, Gemini, Codex OAuth
579
- - [x] Interactive model selection per provider
580
- - [x] GitHub star prompt β€” first-time only
581
- - [x] Codex emitter β€” `AGENTS.md` + `.codex/hooks.json` + `.codex/config.toml`
582
- - [x] Unified `.omh/` layout β€” single source of truth for hooks & state across runtimes
583
- - [x] Pi ([pi.dev](https://pi.dev)) emitter β€” bridge extension (`.pi/extensions/omh-harness.ts`) reusing the same `.omh/hooks/*.sh`
584
- - [x] `ask` mode β€” request approval before executing risky tools (Claude native prompt / Pi `ctx.ui.select`; Codex falls back to block)
585
- - [x] `omh uninstall` β€” remove generated artifacts while preserving user content
586
- - [x] `omh config` β€” view, reconfigure, or reset the saved AI provider (rotate expired keys, switch provider/model)
587
- - [x] Autonomous loop engine β€” `omh-loop` skill, worktree-isolated runner, loop-guard, JSONL monitoring
588
- - [ ] Community harness.yaml registry β€” share and reuse configs
589
- - [ ] `omh modify "change X"` β€” NL config editing
175
+ - **An AI provider** (optional, only for natural-language `omh init`) β€” any one of: the `claude` CLI, an API key for Claude/OpenAI/Gemini/OpenRouter, a ChatGPT subscription via Codex, or a local OpenAI-compatible server (Ollama etc.). See [AI Provider Setup](docs/how-it-works.md#-ai-provider-setup).
590
176
 
591
177
  ---
592
178
 
@@ -63,7 +63,7 @@ fi
63
63
  # λŒ€μ‘ ν…ŒμŠ€νŠΈ 파일 확인 β€” ν™•μž₯자 제거
64
64
  BASENAME=$(basename "\$FILE_PATH" | sed -E 's/\\.[^.]+$//')
65
65
 
66
- REASON="oh-my-harness: TDD β€” \${BASENAME} 에 λŒ€μ‘ν•˜λŠ” ν…ŒμŠ€νŠΈ νŒŒμΌμ„ λ¨Όμ € μˆ˜μ •ν•˜μ„Έμš”"
66
+ REASON="oh-my-harness: TDD guard β€” edit the test for \${BASENAME} first, then the source"
67
67
  if [[ ! -f "\$HISTORY_FILE" ]]; then
68
68
  _log_event "block" "\$REASON"
69
69
  _emit_decision "block" "\$REASON"
@@ -7,7 +7,9 @@ function printSummary(config) {
7
7
  console.log(` method: ${chalk.cyan(config.method)}`);
8
8
  if (config.method === "api") {
9
9
  console.log(` model: ${chalk.cyan(config.model ?? "(default)")}`);
10
- console.log(` api key: ${chalk.dim(maskApiKey(config.apiKey))}`);
10
+ if (config.baseUrl)
11
+ console.log(` base url: ${chalk.cyan(config.baseUrl)}`);
12
+ console.log(` api key: ${chalk.dim(config.apiKey ? maskApiKey(config.apiKey) : "(none)")}`);
11
13
  }
12
14
  else if (config.method === "oauth") {
13
15
  console.log(` model: ${chalk.cyan(config.model ?? "(default)")}`);
@@ -72,10 +74,17 @@ export async function configCommand(options = {}) {
72
74
  console.log(chalk.green("Removed ~/.omh/config.json"));
73
75
  return { exitCode: 0 };
74
76
  }
75
- // Default: show current config (if any), then reconfigure.
77
+ // Default: show current config, then ask before launching setup so a plain
78
+ // `omh config` is a safe read; `--yes` skips the prompt.
76
79
  if (existing) {
77
80
  printSummary(existing);
78
81
  console.log("");
82
+ if (!options.yes) {
83
+ const confirm = options.confirm ?? defaultConfirm;
84
+ const ok = await confirm("Reconfigure the AI provider?");
85
+ if (!ok)
86
+ return { exitCode: 0 };
87
+ }
79
88
  }
80
89
  else {
81
90
  console.log("No AI provider configured yet. Let's set one up.\n");
@@ -0,0 +1,19 @@
1
+ import type { EventEmitter } from "node:events";
2
+ type SpawnLike = (cmd: string, args: string[], opts: {
3
+ detached: boolean;
4
+ stdio: "ignore";
5
+ }) => EventEmitter & {
6
+ unref(): void;
7
+ };
8
+ export declare function browserCommandFor(platform: NodeJS.Platform, url: string): [string, string[]];
9
+ /**
10
+ * Best-effort: open the URL in the OS default browser. Never throws; returns
11
+ * false so callers can tell the user to open it by hand (SSH, containers).
12
+ */
13
+ export declare function openInBrowser(url: string, deps?: {
14
+ spawn?: SpawnLike;
15
+ platform?: NodeJS.Platform;
16
+ }): Promise<boolean>;
17
+ /** OSC 8 terminal hyperlink: stays one clickable link even when soft-wrapped. */
18
+ export declare function hyperlink(url: string): string;
19
+ export {};
@@ -0,0 +1,34 @@
1
+ import { spawn as nodeSpawn } from "node:child_process";
2
+ export function browserCommandFor(platform, url) {
3
+ if (platform === "darwin")
4
+ return ["open", [url]];
5
+ if (platform === "win32")
6
+ return ["cmd", ["/c", "start", "", url]];
7
+ return ["xdg-open", [url]];
8
+ }
9
+ /**
10
+ * Best-effort: open the URL in the OS default browser. Never throws; returns
11
+ * false so callers can tell the user to open it by hand (SSH, containers).
12
+ */
13
+ export function openInBrowser(url, deps = {}) {
14
+ if (!/^https?:\/\//.test(url))
15
+ return Promise.resolve(false);
16
+ const spawn = deps.spawn ?? nodeSpawn;
17
+ const [cmd, args] = browserCommandFor(deps.platform ?? process.platform, url);
18
+ return new Promise((resolve) => {
19
+ try {
20
+ const child = spawn(cmd, args, { detached: true, stdio: "ignore" });
21
+ child.once("error", () => resolve(false));
22
+ child.once("spawn", () => resolve(true));
23
+ child.unref();
24
+ }
25
+ catch {
26
+ resolve(false);
27
+ }
28
+ });
29
+ }
30
+ /** OSC 8 terminal hyperlink: stays one clickable link even when soft-wrapped. */
31
+ export function hyperlink(url) {
32
+ const ESC = String.fromCharCode(27);
33
+ return `${ESC}]8;;${url}${ESC}\\${url}${ESC}]8;;${ESC}\\`;
34
+ }