i2y/ritsu/geas

Claude Code and agent skill by i2y/ritsu

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Hold code to claims a person has read, with geas. A `.geas` file holds claims about what a program does as anyone could observe it from outside (what a command prints and exits with, what an HTTP service answers, what a screen shows), and geas runs every claim against the real program, whatever language it is written in. Use when writing or changing code that a `.geas` claims file holds; when running the gate on a change (`ritsu geas check`, `drift`, `map` and `affected`); when a claim fails or a geas diagnostic (E001-E090, W060-W061, W901) has to be fixed; when the scenarios of an OpenSpec spec or change have to be held to claims (`ritsu geas scenarios`); or when behavior no claim covers has to be proposed to the person as new claims.

Install

npx vskill@latest install i2y/ritsu/geas

SKILL.md

When this applies

The project keeps a claims file (*.geas) beside its code, or a person wants one. Each claim says what the program does as anyone could see it from outside: what a command prints and exits with, what an HTTP service answers, what a screen shows to a screen reader. The person reads and accepts the claims; you write the code; ritsu geas check runs every claim against the real program and says which hold.

It does not apply to tests of internal functions: a claim never names a file, a function, a class, a CSS selector or a line, and geas never reaches into the code. A library is claimed through a small command or service around it.

The files bundled with this skill are listed in §10. Read them when you need them, not all up front.


Working with geas

1. Who writes what

geas splits the work: the person reads about fifty lines of claims, you write the code, and geas holds the code to the claims. That split is the whole point, so keep to it.

  • The claims file is the person's. Every line of it: the claims, the targets, the masks, the

pins. Do not change it to make the gate pass. Write a change you think it needs as text in your answer, and put it in the file once the person says yes. When there is no claims file yet, propose a first one the same way, from what the person asked for.

  • A failing claim is fixed in the code, unless the person says the claim is wrong. A claim

that cannot be true of any sensible program is a question for the person, not something to loosen.

  • A missing claim is proposed, not written. When ritsu geas affected lists changed code no claim

runs, when ritsu geas drift lists a change no claim covers, or when the person asks for behavior no claim states, write the claim you propose, say why, and let the person accept it. When the project has rules, gates, books or workflows, from-specs.md is how to propose many at once.

  • A new baseline and a new mask are asked for. ritsu geas snap changes what drift compares

against, and a mask line stops drift from looking at a field; both can hide a change, so the person decides.

  • The files under `.geas/` are geas's. The journal, the baseline and the record are written

by geas; never edit them by hand.

  • OpenSpec's scenarios are the person's too. In a project that keeps its behavior in OpenSpec,

each scenario (#### Scenario: under a requirement) is a case the person agreed on, and the claim that runs it carries the scenario's name exactly. ritsu geas scenarios <spec.geas> --openspec openspec/specs says which scenarios no claim answers; --openspec openspec/changes/<id> does the same for a change before it is archived. For each one, propose a claim (--draft writes the frame, with the scenario quoted above it; the steps are yours to propose and the person's to accept). Never rename a claim or a scenario to make the two meet: say which is wrong, and ask.

Run every command here as ritsu geas <command> (through a link to ritsu named geas, geas <command> is the same). Everything is reachable from the command line: ritsu geas --help lists the commands, ritsu geas explain <code> explains a diagnostic, and the commands that report take --json. There is no step where you have to read geas's source.

2. The loop

  1. Check: ritsu geas check <spec.geas> runs every claim. Read each not ok (§4), fix the code,

and check again until every claim holds (exit 0).

  1. Snap, once the person accepts the behavior: ritsu geas snap <spec.geas> runs the claims and

keeps every observation, not only what the checks look at, as the baseline in .geas/<stem>.baseline.jsonl. Ask before you snap.

  1. On every later change:
  • ritsu geas check <spec.geas>: every claim still holds.
  • ritsu geas drift <spec.geas>: what changed since the baseline. A change marked [unclaimed]

is behavior no claim promises; tell the person, with a claim to add or a reason not to.

  • ritsu geas map <spec.geas>, then git diff | ritsu geas affected <spec.geas> -: which claims the

change touches, and which changed lines no claim runs. map runs the claims with coverage on and records, for each claim, the lines it ran; affected reads the diff and that record and runs nothing. Run map on the changed code: added lines need a record of the code after the change (E063 otherwise). A file the change adds is in git diff only once git knows it, so git add -N <file> it first.

  1. Report what the person has to read: the claims the change touches, the changed code no

claim runs, and drift's unclaimed changes, each with the claim you propose or the reason it needs none.

check, snap, drift and map take several specs; --jobs N runs up to N claims at once (§8). Exit codes: 0 all held or nothing to report, 1 something failed or changed, 2 the spec, a file or the arguments are wrong. In a project that also holds the other languages' files, ritsu check runs the claims of every .geas with the rest (the ritsu skill).

3. The language on one page

A whole claims file, examples/greeter/greeter.geas:

geas
# Claims for a small HTTP service. Each claim gets a fresh server
# instance, on a port of its own, so the stateful scenario below is
# self-contained.
target api {
serve "python3 server.py {port}"
port auto
}
# Declared volatility: these fields are noise by nature, not behavior.
mask header "date"
mask header "server"
claim "greets by name" {
when api.get("/greet?name=Alice")
then status is 200
and body json ".message" is "Hello, Alice"
}
claim "rejects an empty name" {
when api.get("/greet?name=")
then status is 400
}
claim "totals accumulate across requests" {
when api.post("/reset")
then status is 200
when api.post("/add", body: "5")
when api.post("/add", body: "7")
when api.get("/total")
then body is "12"
}
claim "unknown paths are 404" {
when api.get("/nope")
then status is 404
}

A target says how to start the program. A claim is when steps, each an action on a target, and then checks of what the last when observed; and is another check of the same observation. Each claim starts its own instance of every service it touches, so claims do not see each other's state and can run in any order. # starts a comment. The forms, at a glance:

text
target calc { run "python3 calc.py" } a command, run once per `when`
target api { serve "node server.ts {port}" a service, started once per claim,
port auto } on a free port geas picks; or `port 8123`
contract "api.yaml" (in a serve target) every HTTP call is held to this OpenAPI document
target app { pixie "greeter" } a pixie app, driven headless
target ui { driver "python3 my_driver.py" } any other GUI, through a driver program
serial (in a target) its claims never run side by side
when calc.run("2", "+", "3") arguments after the target's command
when api.get("/path") when api.post("/path", body: "5")
when api.put("/a/1", body: "x") when api.patch("/a/1", body: "y") when api.delete("/a/1")
when api.get("/a", header "authorization": "Bearer {token}") headers on any of the five, as many as needed
when api.get("/jobs/1") within 10 seconds called again until its checks hold (1 to 60 s)
when web.open("/") when app.open() a page of a serve target, or the app's first screen
when app.click("Save") when app.click("Save", nth: 2)
when app.input("Ada") when app.input("Ada", field: 2) when web.input("Ada", into: "Your name")
when app.submit() when app.press("cmd-s") when app.advance(500)
then stdout | stderr | exit after run
then status | header "etag" | body | body json ".a[0].b" after an HTTP call
then screen after a GUI action
keep body json ".id" as id holds a value of the `when` above for the steps below: `{id}`
keep stdout | stderr | header "etag" | body as x
keep env "API_TOKEN" as token from geas's own environment, before a `when` if need be; shown nowhere
in any order { the steps in it (each a `when` with what is under it) run in
when api.post("/events", body: "…") every order, each order on services started afresh;
then status is 200 the claim holds when every order holds
when api.post("/events", body: "…")
}
is "5" | is 0 | is true | is null | is not "x"
is above 1 | is below 10 | is at least 0 | is at most 3 | is between 200 and 299
contains "part" | does not contain "part"
matches "\d+ items?" | does not match "error.*"
exists | does not exist for header and body json
contains button "Save" disabled on screen: a node by role, name, value, state
contains exactly 2 listitem in list "Items"
contains text containing "Hello" | contains text matching "Hello, .+!"
contains textbox "Name" with value "Ada" | does not contain dialog
mask header "date" drift does not compare it
mask body json ".request_id"
mask screen text containing "Updated"
env "NAME" "value" | env clean | env pass "HOME" | tz "Asia/Tokyo" | locale "ja-JP"
clock "2026-08-29T09:00:00+09:00" [env "NOW"] | seed 7 [env "SEED"]

A role is a WAI-ARIA role name (button, textbox, heading, link, checkbox, listitem, dialog, …) or text, and the states are disabled, enabled, checked and unchecked. A pattern (matches, matching) is held against the whole value. In a string, \n, \t, \" and \\ are escapes, and a pattern keeps its backslashes as written ("\d+"). geas splits a command into words itself, never through a shell: '…' and "…" keep blanks inside a word, and a shell is named when one is wanted (run "sh -c 'cd tools && ./gen'").

A when on a command or a service can wait for an answer that comes later, such as a job's result after the service accepted it: with within <n> seconds after its call, geas calls again, 100 ms after each answer, until every check under that when holds on one answer, for up to n seconds from the first call. When the time runs out, the checks read the last answer. A call is made again as it is written, so a call that waits does what it does on every call: a post that creates something creates it again, and a put or a patch writes again. Wait on a read where there is one.

A handler that takes events, messages or webhooks from another system cannot choose the order they come in, and the same one can come twice. When the code you write is such a handler, propose a claim with in any order { … } around the deliveries and the state they should end in after it: geas runs the claim once for each order of the steps in the block, and a handler that writes each event as it comes fails in the orders that end on an older one. A delivery that comes twice is the same step written twice. The orders are fixed, so a failure comes back in every run. Its limit: geas makes one call at a time, so a handler that goes wrong only when two events are handled at the same time is not caught; say so when that is the risk. language.md has every form, with what each one means.

4. Reading the answers

ritsu geas check prints one line a claim, ok or not ok, then a summary. A failed check says what it expected and what came back, with the line of the spec and the run that gets there:

text
not ok 2 - rejects an empty name
examples/greeter/greeter.geas:22: status is 400 — got 200
22 | then status is 400
the run that gets there:
21 when api.get("/greet?name=") → 200, body "{\"message\": \"Hello, \"}"

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 90 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/i2y/ritsu/geas/badge)](https://verified-skill.com/skills/i2y/ritsu/geas)

Something wrong with this skill or its page? Tell us.