dynamisk-rettsstat/CLAUDE.md
Kjell Tore Guttormsen 13f6c28dc0
fix: findings from the Cowork test, released as v0.1.1
The operator tested v0.1.0 in Cowork on 2026-11-11. README now says what
was measured there and what was not.

- miljo: no longer claims that kontroll compares "with tolerance, not bit
  for bit" when numpy or scipy differ from the note's versions. kontroll
  always compares byte for byte first, and numpy 2.5.3 gave bit for bit.
- kontroll --alt: show every differing line. The cut-off line now names
  the option instead of only counting the lines left out.
- skill simuler: the question before a run must be answerable with yes or
  no. In the test it became a choice between variants and the run stalled.
- skill forbedre: offer the working copy as a zip when the user cannot
  open the folder it was made in, as in Cowork.

263 tests pass; full kontroll: bit for bit with the note.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 20:24:58 +01:00

83 lines
7 KiB
Markdown

# dynamisk-rettsstat
This file is for development sessions in this repo. It is not loaded as plugin context, and `claude plugin validate`
warns about a `CLAUDE.md` in the plugin root; that warning is expected.
## Context
A Claude plugin (Claude Code and Cowork) that runs the model behind the concept note *Den dynamiske rettsstaten* on the
user's own numbers: find the binding link in the criminal justice chain (`diagnose`), follow the measure over time
(`forlop`), run scenarios (`simuler`) and change the model (`forbedre`). Users are Norwegian: people working in the
justice chain (mostly non-programmers) and researchers. The note and its code live in
`~/repos/den-dynamiske-rettsstaten` (read-only from here). Open, non-commercial; nothing here is for sale.
## Stack
- Skills only: `skills/<name>/SKILL.md`. No hooks, no MCP servers, no mod, no top-level `bin/` (Cowork refuses a plugin
with one).
- `modell/kjor.py`: the single entry point, Python 3.9+ standard library only. Finds a Python with numpy and scipy for
the commands that need them; installs nothing unless `miljo --opprett` is run.
- `modell/kjerne/`: `sim.py`, `diagnose.py`, `forlop.py`, unmodified copies from the note's repo, pinned by sha256 in
`modell/kjerne/KILDE.json`. numpy and scipy pinned in `modell/requirements.txt` (as the note).
- `fasit/`: reference output (22 data files and 5 printouts, 27 files) that `kontroll` compares against.
- `modell/scenario.py` (scenario runner over the core's five simulators), `modell/kontroll.py` (reproduction check),
`modell/arbeidskopi.py` (copies the model to a folder the user owns), `modell/utvidelser/` (mechanisms outside the
core; each must reproduce the core exactly in a limit case).
- `docs/`: `inndata.md`, `scenarioformat.md`, `modellkart.md`, `utvidelser.md`, `forbehold.md`, Norwegian. The test for
extensions reads the template in `utvidelser.md` between the markers `<!-- mal:start -->` and `<!-- mal:slutt -->`.
- `eksempler/`: `eksempel_domstol.json` (stylized), `norge_2025.json` (public Norwegian figures), `scenarioer/`
(six scenarios of our own, and `notatet/` with 21 files that regenerate 20 of the note's 22 data files).
## Conventions
- **Never edit `modell/kjerne/`.** It is synced from the note's repo, and these change together in one commit: the
three files copied at the new commit; the commit and sha256 values in `modell/kjerne/KILDE.json`; `fasit/` rebuilt
with the note's own environment (its 22 data files must be byte-identical to the note's `paper/data/` at that
commit); and the constants `NOTATETS_COMMIT`, `NOTATETS_KJERNE` (3 sha256) and `NOTATETS_FASIT` (all 27 `fasit/`
files) in `modell/kontroll.py`. `kontroll` anchors on those constants, not on `KILDE.json`, so updating `KILDE.json`
or `fasit/` alone makes it report a change. Then run the full `kontroll`. A test fails if a core file does not match
its sha256. No lint on the core. Changes to the model go in `modell/utvidelser/` or in a user's working copy.
- User-facing text (skills, docs, script messages) is Norwegian bokmål, plain and direct, with the note's terms (ledd,
skranke, restanse, utnyttelse, saksbehandlingstid, ledet utenom, den koordinerte regelen). Code, comments and this
file may be English.
- Facts in docs and skills are read from the code or the note, never from memory. Where they differ, the code wins.
- The note's core sentence is quoted verbatim: «Innsatsen for å håndheve lover og regler bør løpende stå i forhold til
hvor mye, og hvor alvorlig, de faktisk brytes.» Short form: «Straffebud og strafferammer ligger fast.»
- Skill frontmatter has only `name` and `description` (other keys make claude.ai reject an uploaded skill).
- `${CLAUDE_PLUGIN_ROOT}` and `${CLAUDE_PLUGIN_DATA}` are substituted in skill text, but they are not environment
variables in Bash. Skills write them literally into the command; `kjor.py` treats an empty `--data` or one containing
`${` as not given and falls back to `~/.dynamisk-rettsstat`. Both were substituted in Cowork too (operator's test
2026-11-11: the plugin under `/root/.claude/plugins/synced/<id>/dynamisk-rettsstat`, data under
`/root/.claude/plugins/data/dynamisk-rettsstat-synced`). Cowork ran the commands in a Linux machine where numpy and
scipy were already installed in the system Python, so the venv path is untested there.
- Skills never invent numbers, show the numbers as understood before computing, divide yearly figures by 12 and say
so, relay script numbers verbatim, carry the caveats, and never install anything without asking. A
`rettsstat-modell/` in the user's folder is used only after asking, and only after `kontroll --rask` on it has been
run and its level told to the user.
- Input to `diagnose`/`forlop`: `kjor.py` rejects unknown field names in a `ledd` or in `styring` (exit 2, with a
suggestion); `merknad`, `kilde`, `kilder` are allowed there, and top-level fields are not checked.
- Scenarios: a changed base parameter stops the run unless the file sets `startlikevekt` (`kalibrer` or `ubalanse`);
skills let the user choose and never pick. The models with a court queue drift slowly even without a shock (the
note's own start is not an exact equilibrium); `docs/forbehold.md` has the numbers.
- Exit codes of `kjor.py`: 0 ok, 1 differences found by `kontroll`, 2 input or usage error (a core `sys.exit("...")`
passes through as 2 with the message unchanged), 3 numpy/scipy missing.
## Commands
- Development environment (versions pinned in `pyproject.toml`, group `dev`): `python3 -m venv .venv && .venv/bin/pip install --group dev`
- Tests: `.venv/bin/pytest` (about 2.5 minutes on the development machine, measured 2026-11-11);
`.venv/bin/pytest -m treg` runs the note's full simulation (not timed).
- Lint and types (the core is excluded in `pyproject.toml`): `.venv/bin/ruff check .` and `.venv/bin/mypy .`
- Reproduction check against the note: `.venv/bin/python modell/kjor.py kontroll` (full run; about 1.5 minutes on the
development machine, measured 2026-11-11) or `.venv/bin/python modell/kjor.py kontroll --rask` (skips the full run of
`sim.py`). Six parts: core and `fasit/` against the note's checksums, `diagnose`, `forlop`, full run, extensions'
limit cases, scenario runner against `fasit/data`. `--alt` shows every differing line instead of the first 40 per
comparison.
- Try the skills' commands: `python3 modell/kjor.py diagnose eksempler/eksempel_domstol.json`,
`python3 modell/kjor.py forlop eksempler/eksempel_domstol.json`,
`python3 modell/kjor.py scenario eksempler/scenarioer/sjokkstorrelse_kjede.json --ut <scratch folder>` (`forlop` and
`scenario` need numpy and scipy; `.venv/bin/python` has them)
- Validate the plugin: `claude plugin validate .`
- Load the plugin locally: `claude --plugin-dir .`
## Workflow
- Remote is Forgejo (`git.fromaitochitta.com`), never GitHub. Visibility, catalog entry and the move to `open/` are the
operator's decisions.
- `PLAN.md` and `STATE.md` are local working files (gitignored).
- Conventional Commits. Do not commit working documents.