lmjtfy.git / CLAUDE.md
CLAUDE.mdpreviewCLAUDE.mdsource202 lines · 13.7 KB · raw
1@README.md
2
3- **Only the Worker talks to Jev**, through `jev-client` on `jev-worker`'s
4  ports. Do not add a second Jev client or a retry loop here. Anything
5  missing belongs in `~/jevcrates`, and this repo moves its submodule pin
6  (the user, 2026-10-02: move shared functionality to jevcrates and depend on it).
7- **The engine is not this repository's.** `rete` and `rete-draw` are in
8  `third-party/rustcrates` (a submodule); `packages/rules` is the LMJTFY
9  domain over them and `apps/lmjtfy/src/diagram.rs` only says what the
10  drawing is to say. Change the engine in `~/rustcrates`, then move the pin.
11- **The rules decide what happens to a query** (`packages/rules`, `RULES`),
12  and `decide` in `apps/lmjtfy/src/lib.rs` only carries out what
13  `Network::next` says. A new step is a new fact and a rule, not an `if` in
14  the Worker. The diagram is drawn from the same network, so it cannot show
15  rules other than the ones that run.
16- **Jev facts are asked for together.** `Network::next` returns every Jev
17  fact a live rule is waiting on, and they go out as one request (the user,
18  2026-10-02: "firing a single request with N questions to backfill"). Do
19  not ask for one in a request of its own.
20- **The LLM is given only the tools the rules asked for** (`llm::request`
21  with `wants`), and a question of any other kind it writes is not sent
22  (`llm::takes`). Jev has already answered the other readings itself; a
23  second answer to one of them from the LLM's wording disagrees with the
24  first, as it did on 2026-10-02.
25- **Jev answers; the LLM only writes the question.** Never show the LLM's own
26  text as an answer, and never have it answer when it makes no tool call.
27- **The tool call panels show the bodies that crossed the wire**, indented and
28  coloured for reading (the user, 2026-10-02). Printing may add whitespace and
29  nothing else: no reordered keys, no dropped or summarised fields. `view`'s
30  test parses the printed JSON back and compares it to the raw body, and
31  `ask`'s tests hold the body shown equal to the body sent.
32- **`/ask` sends the whole transcript in every event.** Keep it that way: the
33  browser has no state to get out of step with.
34- **The same request is never sent to the same model twice** (the user,
35  2026-10-02: "never send the same exact shape to the same
36  model, ever"), **unless a visitor presses ↻ on it** (the owner, later the
37  same day: "a button to bypass cache for a request"). Every Jev and LLM call
38  goes through `archive::call` (`apps/lmjtfy/src/archive.rs`), which returns
39  the kept response or makes the call once. `Pick::Fresh` is the only way to
40  send a kept request again, and only `$pins` from the page sets it: never
41  add another path that does. Every response is kept, as a numbered version,
42  and does not expire; stepping through versions (`Pick::Version`) sends
43  nothing, and neither does any call after the one stepped (`KeptOnly`). A new call site that reaches
44  Jev or Workers AI any other way breaks the rule, and is also unmetered
45  public spend: the archive is where the budget is held.
46- **The site shows questions and answers and nothing about who asked.** No
47  page, feed, toast or preview may show a visitor's address, browser id,
48  network or exact place (the online list's city and country is the one
49  thing shown, the owner's ruling of 2026-10-02).
50- **The archive keeps everything, for the owner alone** (the owner,
51  2026-10-03: "anywhere in the app where we are dropping data we should
52  plug", and of visitors, "Everything, linked"). Every request worth keeping
53  is a row of `events` (`packages/archive/src/event.rs`): the question, how
54  it ended, the address, the browser's id, the user agent, and Cloudflare's
55  network and place. This reverses the rulings of 2026-10-02 (no address,
56  nothing linking one browser's questions). Do not write that the site keeps
57  nothing about visitors; chapter 2, "What is kept about visitors", is the
58  true account, and a new thing that is known and thrown away is a bug.
59- **`events` is read only through the admin door** (`archive::ADMIN`, the
60  archive object's `/admin` path), which only the owner's separate admin
61  Worker reaches, by binding the object. This Worker must never send a
62  request there or pass a visitor's request to the object, a socket upgrade
63  on `/live` aside: the object would read it as a message.
64- **A question counts each browser once** (`askers`, the owner,
65  2026-10-02): the archive gets `asker(id, question)`, a hash per browser
66  per question, which is what counts it once. The hash is the key and says
67  nothing to a reader; the browser's id is kept beside it (`browser`), as
68  it is in `events`, for the owner's backend.
69- **What is listed is the rules' call** (`Note::List`, which needs Jev's `fit`
70  fact), **unless the owner overrules it** (`asked.moderated`, set through
71  the admin door; the owner, 2026-10-03: "approve or unapprove, basic
72  moderation"). Jev's verdict stays in `listed` beside it. Do not list a
73  question by any other path, and read the feed with
74  `COALESCE(moderated, listed)`.
75- **A suggestion as the visitor types is a question the feed may show**
76  (`Ask::Suggest`, from the same `COALESCE(moderated, listed)`), and
77  nothing else: it is shown to strangers. It reads the archive object's
78  memory and asks nobody anything. Do not suggest from `events` or from
79  questions the feed hides, and do not have a model write one without the
80  owner's say (2026-10-03: the feed first, an LLM only if that falls short).
81- **A link preview reads the archive and asks nothing.** Bots fetch every
82  pasted link; a preview that made a call would spend the shared budget on
83  them.
84- **The `wasm-bindgen` crate is pinned to the CLI's exact version** in the
85  root `Cargo.toml`, and the CLI is `cargo:wasm-bindgen-cli` in `mise.toml`. Move both
86  together and `cargo update -p wasm-bindgen`; `mise run check-versions` fails if they differ.
87- **The dev server is pitchfork's, and starting it is the agent's job.**
88  `nix develop .#owner -c op-env-run -- mise run dev`, then read `mise daemons logs worker`.
89  Do not start `wrangler dev` by hand beside it, and do not hand the user a
90  command to run (the user, 2026-10-02). If the supervisor was already running
91  from another shell, `mise daemons stop worker` and start it again from the owner's:
92  a daemon gets the environment its supervisor had.
93- **Stop or restart it with mise** (`mise daemons stop worker`, `mise daemons
94  restart worker`), never with `pkill -f wrangler`: `-f` matches the shell
95  running the command and kills it (twice, 2026-10-02).
96- **The toolchain is `mise.toml`'s, and nothing in the repository may require
97  nix** (the user, 2026-10-05). A reader runs only `mise` commands. mise.toml
98  is the one place for versions; the few files that must repeat one (Cargo.toml's
99  wasm-bindgen pin, hk.pkl's hk release) are held equal by
100  `tools/check-versions.sh`, part of `mise run check`. Scripts under `tools/`
101  must not assume nix or mise: they read the environment and say what is missing.
102- **Two nix shells, both only wrapping mise: `nix develop` for anyone, `nix develop
103  .#owner` for the owner's tools** (lmjtfy-wrangler, lmjtfy-secret, lmjtfy-eval).
104  Only the owner shell may use `nix-pkgs` or `nix-facts`: Nix fetches a
105  locked input only when an output uses it, so one reference from the
106  default shell breaks `nix develop` for everyone who clones (they cannot
107  read those repositories). Never put a tool version in flake.nix.
108- **Do not export an `op://` value from the devshell.** `op-env-run` resolves
109  every such value in the environment it is started from, with an account
110  that cannot read the Cloudflare item, and refuses to start. That is why the
111  eval's reference lives inside `lmjtfy-eval`.
112- **Never pipe a value into `lmjtfy-wrangler`.** It runs op.exe before
113  wrangler, and op.exe eats the stdin (an empty secret went live that way,
114  2026-10-02). Use `lmjtfy-secret jev | account | analytics`.
115- **The neuron count is the account's, read from Cloudflare.** Do not go back
116  to counting only this Worker's calls: on 2026-10-02 the eval and local dev
117  had spent 950 neurons the site's counter knew nothing about.
118- **Do not load-test the live site.** Every answered ask is counted on the
119  public feed and in "So far" (2026-10-02: the agent's own rate-limit test put
120  "asked 67 times" on the home page). Load-test the dev server. On the live
121  site, ask once; to test the visitor limit there, post an empty `q`, which
122  counts towards the limit and touches nothing else.
123- **After a deploy, check Jev with `/gate` on text never sent before**
124  (`{"q":"deploy check <time>"}`): a missing or empty key still deploys
125  cleanly and serves the page, and only a request that is really sent shows
126  it. Never check with `/ask`: a question already kept sends nothing, so it
127  tests nothing, and it adds an ask to the public count (2026-10-02: four
128  such checks took one question from ×3 to ×7; MIGRATIONS step 4 undid it).
129  To check the clone proxy, `git ls-remote <site>/lmjtfy.git`, which is not
130  counted; a clone is.
131- **The clone proxy serves `Repo::ALL` and only `git-upload-pack`.** Its
132  token must stay read-only (Contents: read on those repositories), so a push
133  is impossible at GitHub and not only at the routes. A new repository is a
134  new `Repo` variant; never take the upstream from the request path.
135  `.gitmodules` URLs stay relative, or a clone from the site points its
136  submodule at private GitHub and fails.
137- **The folders are a guide, chapter by chapter** (the owner, 2026-10-02:
138  "read like the documentation subsite and like a tutorial flow", in the
139  spirit of why's (poignant) guide). Every folder's README is a chapter:
140  `# Chapter N: <name>, <subtitle>`, an opening that teaches the idea, `>
141  **Aside.**` detours, a `Try it` against the live site or `cargo test`, the
142  maintainers' reference, and a last line `← Previous · Up · Next →` in
143  reading order (the prologue's table). A new folder gets its chapter and a
144  CLAUDE.md, and the chapters after it are renumbered; `view::code::guide`
145  fails the build on a folder without both, or a link that goes nowhere.
146  The code pages' ◀ ▶ tabs follow those last lines, so the Next chain must
147  be a depth-first walk (each folder before its subfolders, a subtree
148  finished before its next sibling, every folder once, the last chapter with
149  no Next); `the_chapters_lead_on_depth_first_through_every_folder` holds it.
150  Playful, never at the expense of a true fact.
151- **A deploy that people on the site should hear about carries a
152  `Release-Note:` trailer** in its HEAD commit: one line, plain text, shown
153  as a toast to every page from an earlier build when it reconnects
154  (`build.rs`, `LMJTFY_NOTE`). Most deploys have none.
155- **The site is `https://lmjtfy.fun`, bare, and the code is
156  `https://code.lmjtfy.fun`** (the owner, 2026-10-05). `lmjtfy.deizel.workers.dev`
157  (where it began) and `www.lmjtfy.fun` answer only to redirect to the site
158  for good, and every served repository's path, on any of the three, to the
159  code host with a `308` (`host::gate`), **once `GIT_REDIRECT` is `"on"`**.
160  It is `"off"` in `wrangler.toml` until `code.lmjtfy.fun` is deployed and
161  checked, and the home page's clone links follow it
162  (`Host::code_origin`): do not hard-code the code host into a page the
163  apex serves. Write the code host in clone lines
164  and Cargo lines, and the bare address for the site. Do not redirect a
165  request that is not a navigation: a page still open on the old address
166  would lose its socket and its posts.
167- **Release files of the served repositories** (`/<name>/latest/<kind>`,
168  `/<name>/releases/<tag>/<asset>`, `src/release.rs`) are served by the code
169  host only; with `GIT_REDIRECT` on the apex and old addresses `308` them
170  there like a clone, with it off the apex `404`s them.
171- **Each host has its own routes** (`fetch`, `site_routes` and
172  `code_routes`), and `host::gate` decides before any route. The code host
173  has no `/ask`, archive, `/live`, `/feed` or `/seen`: do not add a route
174  there without saying why it is safe on a host that has no budget behind it.
175  A repository is served by being in the `repositories!` list in
176  `clone.rs`, which is also the list on the code host's front page and the
177  list `gate` redirects: do not write a second.
178- **Deploy to staging first, then the site** (the owner, 2026-10-04: "a
179  preview environment so we aren't pushing straight to prod").
180  `lmjtfy-wrangler apps/lmjtfy deploy --env staging`, check
181  `https://staging.lmjtfy.fun` (through Access, with the service token's
182  two headers), and only then `lmjtfy-wrangler apps/lmjtfy deploy`. A
183  migration step has run on staging's archive before it runs on the
184  site's. Both without asking (the user, 2026-10-02: "please deploy
185  without asking"): the site is public and spends real money for every
186  visitor, so deploy only verified, committed work, and check the live
187  site after.
188- **Deploying the code host is two deploys** (the owner, 2026-10-05: do not
189  break the live site). First with `GIT_REDIRECT` off (the toml's value):
190  the code host and its domain go live, clones at the apex are unchanged.
191  Check `https://code.lmjtfy.fun` and a real `git clone
192  --recurse-submodules` from it. Then `lmjtfy-wrangler apps/lmjtfy deploy
193  --var GIT_REDIRECT:on` (or edit the toml), check the `308` and a clone
194  through the old address. Rollback: `lmjtfy-wrangler apps/lmjtfy rollback`.
195  The var is parsed strictly; a typo runs as off and logs an error. Run
196  `tools/check-hosts` in each mode (its header has the dev servers) after
197  touching `host.rs`.
198- **Staging has no workers.dev address and no preview addresses**, and
199  must not get one: Access at `staging.lmjtfy.fun` is all that stands in
200  front of it, and the Worker itself lets anyone in. Its archive is its
201  own; nothing done there reaches the site's.
202- Open work lives in the brain page `technology/artificial-intelligence/jev/lmjtfy.md`.