flan/CLAUDE.md
Joseph Ferano 57fe91f303 Five records become one, and every citation lands somewhere
FIX.org, NEXT.md, DISCUSS.org, docs/DISCUSS.md and the session handoff at the
root are one TODO.org now: 293 entries under seven subsystem headings, each
carrying an org keyword that says where it stands. A DONE entry is a few lines
saying what was decided and what that rules out; the reasoning that would not
compress — the embedding spike and the four reports the hand-written x86
backend was built from — moved into docs/BUILT.md instead, and its entries
point there in one line.

Every entry was checked against the tree before it got a keyword, and the
prose was wrong in both directions. Things the deleted files called open were
built: the first-evaluation stall, main being redefinable, macro parameter
lists, the type-limit constants, the array constructors, the byte fills,
inc/dec, the discard's fontification, the Emacs buffers, rt_die's _exit, the
backtrace surface, and the acceptance failure that could print and still exit
zero. Things they called done were not: the backend reports' no-plan buckets
had gone stale in the other direction, the value-dependent defvar was
superseded rather than built, and macro-expansion source locations are on an
unmerged lane, so that entry is NEXT and names the branch.

Every comment that cited one of the five by name now cites a heading that
exists, in TODO.org or in docs/BUILT.md. The session reports under
docs/handoffs/ keep naming the files they worked on, because rewriting them
would falsify what those sessions did; each carries a note saying where the
content went.
2026-09-21 21:05:48 +07:00

5.9 KiB

Working in this repository

For any agent working here, including a lane in its own worktree. These are standing rules, not preferences. Where a rule has a reason, the reason is given once — the rules without one are the ones that have already cost something.

Never

  • Never touch anything under /home/joe/Development/fnm/. That is the author's own working copy of the falling-sand game, open in an editor with a live session attached. fnm/flan/sand.flan is not the same file as this repository's sand.flan and is never to be read-and-written-back, moved or edited. The copy in this repository is ordinary tracked source and may be edited like any other file.
  • Never execute examples/*.flan, sand.flan, or anything that links raylib. They open a window on the author's desktop, which strobes it. Compile them, emit them, diff them — never run them.
  • Never run dune clean. It walks up out of a worktree and deletes the main checkout's _build, which takes the author's flan binary with it. This has happened. Run dune with --root . from inside your own worktree.
  • Never kill a flan dev daemon you did not start. The author keeps a live one attached to their editor all day. Orphans from finished lanes are fair game; anything whose cwd is under fnm/ is not.
  • Never put a Co-Authored-By, a Claude-Session trailer, or any other watermark in a commit message.

Commits and branches

master is the trunk and tracks origin/master. Lanes work in their own git worktree off it and do not merge or push — merges are resolved by the session that dispatched the lane.

A commit message is a single declarative sentence in the repository's voice, saying what is now true rather than what was done: "A thunk named for the order it was minted in is a different signature after a reorder", not "fix thunk naming bug". A body is for the reasoning, when there is any.

Tests

dune test --root . is the suite and takes seconds. Run it constantly; it must be green before a lane reports.

dune build @checks is @page, @x86 and @cells — the reference page's examples still compile and print what the page says, the hand-written x86 backend still agrees with LLVM, and a --dev build still calls through its indirection cells. @sanitize and @valgrind run the corpus under ASan/UBSan and memcheck and take tens of minutes.

Those three stay out of a lane's own run. They are swept in a batch after several lanes have merged, and the fixes are batched with them. Keeping the default run fast is deliberate: a suite that takes tens of minutes is a suite nobody runs.

Note that ASan does not see a stack-lifetime bug — uninitialised stack reads need @valgrind.

Evidence

Read the source, do not recall it. docs/REFERENCES.md lists the reference clones on this machine — Odin, SBCL, Zig, Carp, clojure-mode, CIDER, raylib and the rest — and what each one answers. A claim in these notes that came from one of them was read out of the clone; the ones that were recalled instead have been wrong before.

The same applies to this repository. Cite a file and a line because you opened it. When a brief hands you a list of facts, verify them before building on them — a brief written from a stale tree has sent a lane a full day in the wrong direction.

Diagnostics

Elm's shape: the source line, a caret, what the compiler understood, and the fix named. Beyond that:

  • A message is written for someone who has never used Flan and does not know its history. Never "X is now Y", never "X was renamed", never our rationale. If defvar no longer exists, the message says defvar does not exist — not that it became something else.
  • Every suggestion a message prints must compile.
  • An assertion about the compiler's own invariants is prefixed internal: and says it is a compiler bug. A user never caused one.

Writing

User-facing prose — the README, web/index.html, the Emacs manual — is plain. Odin's website is the reference: declarative rather than second-person, the concept defined before the mechanics, the code example after the prose that frames it, and rationale given its own subsection rather than mixed in.

No aphorisms, no closing lines built for effect, no rhetorical inversions. If a sentence's only content is its own style, cut it. This applies to reports and commit messages as much as to documentation.

The records

  • TODO.org — every decision, question and known gap, one ** heading each under a subsystem heading, with an org keyword saying where it stands: TODO for a gap, NEXT for what is queued, WAIT for what is blocked and on what, DONE for what was settled, CANCELLED for an idea considered and rejected. A lane records its decision here, as a few lines saying what was decided and what it rules out — never the argument and never the measurements. On a merge conflict in this file, keep both sides: two lanes recording two decisions is not a conflict.
  • docs/BUILT.md — why the parts that exist are shaped the way they are. The largest and most load-bearing document here. Reasoning that will not fit in a few lines goes here, and TODO.org carries one line pointing at it.
  • docs/handoffs/ — one report per finished work session.
  • docs/README.md indexes all of it.

A CANCELLED entry is not housekeeping. An idea rejected without a record is an idea that gets re-proposed, so the one-line reason is the whole value of it.

The shape of the work

The author dogfoods the language in a separate project, hits friction, and reports it. Work is dispatched as parallel lanes in isolated worktrees; every lane is reviewed by an independent agent before it merges; the dispatching session resolves the merges.

A review is adversarial. Verify by building and running rather than by reading, measure both sides of a claimed fix, and say what you actually measured. A lane that reports a number you did not reproduce is a lane with a number you should reproduce.