omp packages

@dadwritestech/thisisfine0.2.0

Your agent says done while your app is on fire. thisisfine locks every behaviour you said yes to into a proven check (browser, API or CLI), and won't let the agent finish a turn that breaks one.

dadwritestech Published 11 days ago MIT ~404 downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

The thisisfine mascot: a coffee mug with a padlock on its chest, smiling calmly and raising a tiny cup while the room burns around it

thisisfine

Tests Works with Claude Code, Codex and pi Checks drive a real browser, call your API, or run your CLI Zero runtime dependencies Node 22.18 or newer MIT license GitHub stars Watch the 30-second video

Your agent: done! Your app: on fire Status: this is fine Coffee: still hot Vibes: locked Excuses accepted: 0

Your coding agent says "โœ… Done" while your app is on fire.
thisisfine turns every behaviour you said yes to into a locked, proven check (browser, API or CLI),
and won't let Claude finish a turn that breaks one.

Demo, recorded from a real end-to-end run. You ask Claude for a cart badge; thisisfine proves the check passes now and fails without the change, and the shop shows the badge at 2. You reply 'y, perfect' and promise #1 is locked. Weeks later a 'small cleanup' makes the badge count distinct products, Claude tries to stop and is blocked with '๐Ÿ”ฅ This is NOT fine', Expected 2, Received 1, and the shop shows the badge at 1. Claude's attempt to edit the check is denied, it fixes the app instead, and the turn ends with 'โ˜• This is fine. 1/1 promises kept.'

Install ยท The 30-second story ยท APIs and CLIs ยท How it works ยท What it can't do ยท FAQ


It worked yesterday. You checked it yourself and told Claude "perfect". Today Claude tidied up some code, and the thing you checked is quietly broken. Nobody noticed, because the only record that it ever worked was one word in a chat that's long gone.

thisisfine keeps that word. When you say yes to a behaviour, it becomes a promise: one sentence you agreed with, plus one check that proves it from the outside: in a real browser, over HTTP, or by running your CLI. Before Claude can end a turn, every promise is checked. If one is broken, Claude is sent back to fix the app, with your own words quoted back to it.

The 30-second story

This is a real run of the example shop, the same steps the end-to-end test replays in a real browser.

1. You ask for a cart badge. Claude builds it, writes a check, and thisisfine proves the check can actually fail:

โœ” passes now โœ” fails on HEAD (app still boots)

Lock in promise #1 "Adding a coffee twice shows 2 on the cart badge"? โœ” passes now โœ” fails on HEAD (app still boots)
Reply y to confirm, anything else to skip.

2. You reply y, perfect.

๐Ÿ”’ Locked promise #1 "Adding a coffee twice shows 2 on the cart badge".

3. Every turn after that ends with:

โ˜• This is fine. 1/1 promises kept.

4. Weeks later you ask for "a small cleanup of the cart code". Claude counts distinct products instead of items, which looks reasonable and is wrong. Claude tries to stop, and can't:

๐Ÿ”ฅ This is NOT fine. You broke promise #1 "Adding a coffee twice shows 2 on the cart badge".
   The human confirmed it on Oct 4: "y, perfect"
   Check: .thisisfine/checks/1-cart-badge.spec.ts
   Failure:
     Error: expect(locator).toHaveText(expected) failed
     Locator:  getByTestId('cart-count')
     Expected: "2"
     Received: "1"
   Screenshot: .thisisfine/runs/โ€ฆ/test-failed-1.png

Fix the app so #1 holds again. Do not edit the checks, the ledger, or the config.
When you said yes After the "cleanup"
Fine Coffee Co. shop: two House blends added, cart badge shows 2 The same shop after the refactor: two House blends added, cart badge shows 1

5. Claude fixes the app, not the check, and you get your coffee back: โ˜• This is fine. 1/1 promises kept.

If Claude tries to edit the check instead, it's refused before the edit happens:

thisisfine: this check is promise #1 "Adding a coffee twice shows 2 on the cart badge", which the human locked.
Fix the app, not the check.

APIs and CLIs

Not everything you say "perfect" to is on a web page. A promise can be about an API or a command-line tool too, and the check is written in the language you already use:

Your project The check Runs on
A web app .thisisfine/checks/3-badge.spec.ts Playwright, in a real browser
An HTTP API, in Python .thisisfine/checks/4-expired-token.py pytest, in thisisfine's own venv
A CLI, in Go .thisisfine/checks/5-dry-run/check_test.go go test, in thisisfine's own module

JS/TS projects can promise API and CLI behaviour from a .spec.ts too, through the request and run fixtures. Whatever the language, the check is still black-box: it calls your API over HTTP or runs your CLI as a process, and a check that imports your code is refused. init detects Next.js, Vite, npm scripts, a package bin, Go's cmd/<name>, Django, Flask and FastAPI. .thisisfine/config.json has up to three commands: start (a server), cli (what checks run), and build (runs before either, in both the real tree and the proof's).

A check that drives no browser has no screenshot, so thisisfine records what it sent and what came back instead, and shows you both sides before you say yes. This is the Python API example, from the end-to-end test:

โœ” passes now โœ” fails when sabotaged (expiry check removed) (app still boots)
Evidence now (.thisisfine/runs/โ€ฆ/now/evidence.txt):
  GET /me โ†’ 401
  < {
  <   "error": "token expired"
  < }

  GET /me โ†’ 200
  < {
  <   "user": "alice"
  < }
Evidence without (.thisisfine/runs/โ€ฆ/without/evidence.txt):
  GET /me โ†’ 200
  < {
  <   "user": "alice"
  < }

  GET /me โ†’ 200
  < {
  <   "user": "alice"
  < }

Lock in promise #1 "An expired token gets a 401"? โœ” passes now โœ” fails when sabotaged (expiry check removed) (app still boots)

The check makes two calls: the expired token, then a live one. With the expiry check sabotaged, the first call gets into alice's account too. The evidence keeps the method, path, status and bodies but no headers, so a bearer token stays out of it.

And when a later "cleanup of the auth code" lets expired tokens back in:

๐Ÿ”ฅ This is NOT fine. You broke promise #1 "An expired token gets a 401".
   The human confirmed it on Oct 8: "y"
   Check: .thisisfine/checks/1-expired-token.py
   Failure:
     test_an_expired_token_gets_a_401: assert 200 == 401

The Go CLI example tells the same story about notes add --dry-run: the CLI is built from the commit before the fix, the check fails there, and the evidence shows added: buy milk against would add: buy milk.

Install

You need Claude Code, Node 22.18 or newer, git, and an app that starts with one command (a server, or a CLI). Python checks also need Python 3.9 or newer, and Go checks need Go.

In Claude Code:

/plugin marketplace add dadwritestech/thisisfine
/plugin install thisisfine@thisisfine

Then, in your project:

/promise the cart badge shows how many items are in the cart

The first /promise sets up .thisisfine/ in your repo and installs what checks run on into that folder: Playwright for a web app, a pytest venv for Python, a Go module for Go. Your own package.json, virtualenv and go.mod are untouched. Commit .thisisfine/ so your promises travel with the code.

You don't have to type /promise. When you tell Claude something works ("works!", "perfect", "lgtm"), thisisfine reminds it to offer a promise. That happens at most once every five prompts, so it won't nag.

Using pi or Codex instead? thisisfine is also on npm:

pi install npm:@dadwritestech/thisisfine   # pi
npm install -g @dadwritestech/thisisfine   # Codex, then copy four hooks into .codex/hooks.json

See integrations/pi and integrations/codex for the setup and what each can and can't guarantee. The CLI works on its own too: npx @dadwritestech/thisisfine status. (The command is thisisfine; the package is scoped because npm thinks the plain name is too close to this-is-fine.)

How it works

 you: "perfect"  โ”€โ”€โ–ถ  Claude writes a check  โ”€โ”€โ–ถ  thisisfine proves it  โ”€โ”€โ–ถ  you: "y"  โ”€โ”€โ–ถ  ๐Ÿ”’ locked
                                                  passes now,                              runs before
                                                  fails without the change                 every stop
  1. A promise is one sentence and one check. The sentence is about something a user can see ("Logged-out visitors are sent to /login"), not how it's built. The check is a black-box test in .thisisfine/checks/: Playwright for what users see in a browser, pytest or go test for an API or a CLI.
  2. thisisfine proves the check, not Claude. It starts your app and runs the check on the code as it is now: it must pass on the first try. Then it runs the check on a version without the change, either the previous commit or a temporary copy with a small "sabotage" patch applied. The check must fail there while the app still boots. A check that can't fail is labelled ๐ŸŸก unproven, so you know it's weaker.
  3. Only you can lock it. Your reply goes through a Claude Code hook, not through Claude. A whole-message "yes" (y, yes, ok, lock it, y, perfect, ๐Ÿ‘, haan, โ€ฆ) locks the promise. Anything else, like "yes but make it blue", skips it. thisisfine records your exact words and signs the record with a private key that lives outside the repo. Its public half goes into .thisisfine/keys/, so anyone with the repo can check the signature.
  4. Every stop is a gate. When Claude tries to end a turn, thisisfine starts the app once and runs every promise. If they all pass, Claude stops with โ˜• This is fine. If one fails, Claude is sent back with the sentence, your words, the assertion, and a screenshot. Nothing changed since the last green run? It doesn't run anything.
  5. Changing your mind is allowed, out loud. If you really do want the behaviour to change, /retire 3 we're dropping the badge asks you to confirm, and only your y retires it. Claude is told to ask for this, not to route around a promise.

Three slash commands: /promise <sentence>, /promises (list them), /retire <n> <why>.

For vibecoders

You don't need to know what a test is. You say "perfect" when something works, and Claude asks if you want to lock it in. From then on, if Claude breaks it, Claude finds out before you do, and has to fix it before it's allowed to say it's done. The messages are in plain English and quote what you said.

For developers

  • Zero runtime dependencies. The plugin is plain TypeScript, run straight from the git checkout by Node's built-in type stripping, so it has no build step. The npm package ships the same code compiled to JavaScript in dist/, because Node won't strip types inside node_modules. Playwright, pytest and requests are pinned and installed into .thisisfine/ only.
  • Black-box checks. Checks can't import your code. They drive a real browser or call your API on a free port (PORT is set, and {port} in the start command is replaced), or run your CLI in a fresh scratch folder with its own HOME. {app}, {exe} and {python} in a command stand for the tree under test, .exe on Windows, and thisisfine's venv. Edit .thisisfine/config.json to change what was detected.
  • Fast when nothing changed. The gate hashes the working tree (tracked and untracked files, respecting .gitignore) and skips the run when it matches the last green tree. On the example shop, a passing gate takes about 1.3 s and proving a new promise about 13 s.
  • Only what a change can affect. After a full green run, thisisfine knows which files each promise's check loaded (it records them through a local proxy, no code changes). A later change to, say, about.html re-checks only the promises that visit it. Anything it can't map (backend code, config, a new file) re-checks everything, and every fifth run is a full one anyway. On the example shop every promise loads the same three files, so there is nothing to skip; the gain shows up when promises cover different pages.
  • A crash is named, not guessed. When a check fails, thisisfine opens the page once more and reports any script error with its file and line (SyntaxError: โ€ฆ (/app.js:6:91)), so a typo that kills the whole page doesn't read as just Received: "0". Errors thrown while the check itself runs (a click handler that throws) come from that check's own Playwright trace, also with file and line; failing runs keep the trace in .thisisfine/runs/ for npx playwright show-trace.
  • Flaky isn't failing. The gate retries once. A check that passes on retry is reported as flaky and doesn't block. Proving a promise allows no retries, so a flaky check is refused at the door.
  • The same CLI Claude uses is yours too: status, check, verify, restore, and more. Run node <path-to-plugin>/bin/thisisfine.mjs --help, or just ask Claude to run it.

For team leads

  • Promises live in git. .thisisfine/promises.jsonl is an append-only ledger: who agreed to what, when, in their own words, and whether the check was proven. It shows up in PR diffs like any other file.
  • Agent-written tests encode whatever the code does, bugs included. Promises encode what a human looked at and agreed to. That's the one piece of ground truth in AI-assisted coding, and today it gets thrown away with the chat.
  • thisisfine verify audits every lock: the signature and which key made it, the check file's hash, and whether Claude Code's own transcript shows a person (not a tool) typing those exact words.
  • Verifiable by teammates and CI. Locks are signed with Ed25519. The private key stays in ~/.thisisfine/; the public key is written to .thisisfine/keys/<you>.pub the first time you lock something. Commit it, and thisisfine verify --strict checks every lock on any machine, without any private key. --strict also fails on a lock that no committed key can check, which is what CI wants.
  • Review .thisisfine/keys/ like code. A key in that folder vouches for every lock it signs, so whoever can add a key there can add promises. The agent can't write there, but a person can. Put the folder under CODEOWNERS and treat a new key in a PR the way you'd treat a new deploy credential.

In CI

The Stop hook guards your own machine. The GitHub Action guards the branch: it runs every promise in a real browser on each pull request, fails the job if one is broken, and posts a behaviour diff, which shows what the PR does to the promises rather than to the code.

# .github/workflows/promises.yml
name: promises
on: pull_request
permissions:
  contents: read
  pull-requests: write   # for the comment; drop it and set comment: false to only use the job summary
jobs:
  thisisfine:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npm ci                            # your app's dependencies: the checks start your app
      - uses: dadwritestech/thisisfine@v0      # follows 0.x releases; pin a commit SHA for anything serious
        # with:
        #   working-directory: web             # if .thisisfine/ isn't at the repo root

The action sets up what your checks run on inside .thisisfine/ (as init does: Playwright, a pytest venv, or neither; add actions/setup-go first for Go checks), runs thisisfine check, and then compares the ledger on the PR with the one on its base:

### thisisfine: what this branch does to the promises (vs 4f1c2e9)

โœ— #1 broken: "Adding a coffee twice shows 2 on the cart badge"
    Error: expect(locator).toHaveText(expected) failed
    Locator:  getByTestId('cart-count')
    Expected: "2"
    Received: "1"
    Timeout:  5000ms
+ #4 locked: "Logged-out visitors are sent to /login" โœ… proven ("y, perfect", Oct 6)
- #2 retired: "The footer shows the shop's opening hours" (hours moved to the contact page)
? #5 waiting for a human y: "Search finds a coffee by name"
โœ” 2 promises kept (#3, #4)

The job fails when a promise is broken, when a locked check was edited, or when the PR deleted or rewrote lines from the base's ledger (it's append-only, and CI has no home-directory copy to restore from, so this is how CI notices). The comment keeps only the assertion; Playwright's full call log and the screenshots of failures are in the thisisfine-runs artifact. On the next push the comment is updated in place, not posted again.

Signatures in CI: each lock is signed with a private key that never leaves the machine where the human said yes, and the matching public key is written to .thisisfine/keys/ for you to commit. CI checks every new confirmation against those committed keys: a forged or edited one fails the diff. A confirmation signed with a key that isn't committed yet gets a note under the summary instead of a failure. Run thisisfine verify --strict if you'd rather fail on those too.

The same two commands work locally, before you push:

node <path-to-plugin>/bin/thisisfine.mjs check --report report.json
node <path-to-plugin>/bin/thisisfine.mjs diff origin/main --report report.json

What it can't do

thisisfine can't stop an agent that is determined to cheat. It makes cheating loud.

  • Tamper-evident, not tamper-proof. Each lock is signed with a private key in ~/.thisisfine/, and mirrored there too. If a locked check, the ledger, or the config is changed or deleted, the next stop is blocked until thisisfine restore puts back exactly what you confirmed. The guard that refuses edits up front is a speed bump. Shell commands can be written in endless ways, and it only catches the common ones.
  • A committed key is only as trusted as the commit that added it. Anyone can check a signature against .thisisfine/keys/, but the folder itself is just files in git. An agent that slips a key past the guard and past review can sign locks of its own. They can only add promises, though: on your machine, only your own key can retire or replace a promise you locked, whatever keys are committed. In CI, verify names the key behind every lock, so a stranger's key stands out.
  • Older locks stay per machine. Locks made before Ed25519 signing are HMAC-signed with ~/.thisisfine/key. They still verify on the machine that made them, and anywhere else verify says no committed key can check them (a failure under --strict). To make one checkable everywhere, retire it and lock it again.
  • The transcript check is local. Claude Code's transcript stays on the machine where you typed "y", so a teammate's verify checks the signature and the check file, not the transcript.
  • Claude Code, Codex and pi; web apps, HTTP APIs and CLIs. pi works through an extension and Codex through four hooks, each with one weaker guarantee: their session files can't prove a person typed the "y". Codex's Windows sandbox also sometimes won't start the browser for propose. Other agents (Cursor), libraries tested by import, background workers, and mobile or desktop UIs are out of scope for now. Checks are in JS/TS, Python or Go.
  • It's only as good as the check. A promise proves the check can fail when the behaviour is gone. It doesn't prove the check covers everything you had in mind. That's why the sentence is short, and why a person has to say yes.
  • Your app runs in your folder. If a check clicks Save, it really saves, before every stop. propose names any project file the app wrote while the check ran, so you can point start at a scratch copy before you say yes. (We learned this the hard way: on a real app, a theme check rewrote config.json, and that saved setting later made the check pass with the feature broken.)

FAQ

Why not just ask Claude to write tests? Because Claude writes tests for the code it wrote, and can edit them when they get in the way. A promise starts from you saying yes, is proven to fail without the behaviour, and is locked against edits.

How is this different from TestSprite or Shiplight? They generate and maintain tests for you. TestSprite saves the behaviours its agent has verified. Shiplight's tests repair themselves as the app changes. thisisfine only locks what a person confirmed, and deliberately never repairs a failing check: a failure means "go fix the app", not "update the test".

And nocap, trust-issues, agent-guard, or Kvitansiya? Those watch how the agent works: skipped tests, weakened asserts, claims of "tests pass" without running them, or checking the agent's claimed result at the end of a session. They protect tests that already exist. thisisfine creates the checks from your confirmations and enforces them at every stop. They complement each other well.

Will it slow Claude down? A turn that changed nothing costs nothing. A turn that changed code costs one app boot plus your checks, about a second for the example shop. A failing check takes longer (it waits for the assertion, then retries once), but that's the turn you wanted stopped.

What if a check is flaky or the app won't start? Flaky checks warn and don't block. If the app can't start, Claude is blocked with the start error, because "couldn't check" isn't "fine". If the same block happens three times in a row, thisisfine lets Claude stop and hands the decision to you, so you're never stuck in a loop.

Why the name? It's the face every coding agent makes while it reports success from inside a burning building. The phrase comes from KC Green's comic On Fire. thisisfine isn't affiliated with it, and the art here is original.

Contributing

npm install
npm test
npm run typecheck

You don't need to build anything to work on it: bin/thisisfine.mjs runs src/ directly whenever src/ exists. npm run build compiles src/ to dist/ for the npm package, and npm pack runs it for you (prepack). The package ships dist/ and leaves out src/, which is how the bin knows to use the compiled code.

The end-to-end tests install Playwright and drive a real browser through the whole story above, then do the same for the Python API and the Go CLI (each half skips if its toolchain isn't on PATH). They're opt-in:

THISISFINE_E2E=1 npm run test:e2e

The demo GIF at the top is filmed from that run: every line of thisisfine output in it, and both shop screenshots, come from the test. To re-film it:

npm run demo

License

MIT