# Yioop — Development process for LLM co-implementers

How Chris Pollett likes development to run, written for an LLM working with him on Yioop. Most of this is shared with his other projects; what is specific to Yioop — the gold/work sync, the Createdb check, the locale and library rules — is marked. When something says "always" or "never," treat it as a hard rule.

Round counter. Keep a count of rounds since you last re-read this file. A round is one exchange: Chris says something, you respond. Echo the current count in every response so it is visible. When the count reaches 10, re-read this whole file before writing your next response, then reset the count to 0 and tell Chris you re-read it. This is a memory aid: it keeps these conventions fresh over a long session instead of letting them drift. The same counter runs in atto's copy of this file.

## How changes move (Yioop-specific): gold and work

Yioop is developed against a two-clone discipline, not a shared remote you push to:

- gold is the confirmed line of history. It is fast-forwarded only when Chris has confirmed a commit, and its `confirmed-head` tag marks that point. Your local gold hashes are your own and will not match Chris's hashes.
- work is a throwaway clone of gold where changes are made. It is re-cloned from gold after every gold fast-forward, with the configured `work_directory` restored into it.

gold is a vault, not a workbench. Exactly two operations ever touch gold: cloning it to make work, and fast-forwarding it with `git apply` of a patch Chris has confirmed. Nothing else, ever. Never run a build, a test, a golden regeneration, a `Createdb`, a `ConfigureTool`, or any other command inside gold; never create a `work_directory` there; never hand-edit, hand-copy, or hand-commit a file into it. Every build, test, and regeneration happens in a work clone, and a regenerated golden reaches gold only inside a patch. Reconciling a divergence is no exception: rebuild the wanted state in work, cut a patch, and apply that to gold. If you ever find yourself about to go into gold to run something — even just to check a result or to get back in sync — that urge is the process breaking; stop, and do it in work.

The loop each round, and this must be done prior to any new work: the moment Chris confirms a patch by pasting its hash, and before writing or changing a single line for the next task, apply Chris's latest confirmed patch to gold, commit it, move the `confirmed-head` tag, re-clone work, and restore `work_directory`. Starting new work on a gold that has not been synced is the most common way the two lines drift: the next cut is taken against a stale `confirmed-head` and silently double-includes the patch Chris already confirmed. If you notice that a round's work has already begun on an unsynced gold, do not re-clone work — that would discard the in-progress changes. Instead advance gold to the confirmed commit, move `confirmed-head` to that commit in both gold and work, and only then cut. Cut a delivery as `git diff confirmed-head..HEAD`, and dry-run it with `git apply --check` on a fresh clone checked out at `confirmed-head` before presenting. Apply patches forward only — never reverse-diff. Never assume anything is committed until Chris reports the hash. Name a patch for what it does in plain terms, never a sequence number or a plan-item number.

## Setting up the dev environment (Yioop-specific)

Beyond the gold/work clones above, a work clone needs a configured `work_directory` before it will build or run. Restore the configured `work_directory` into the fresh work clone, point Yioop at it with `php8.3 src/configs/ConfigureTool.php work-dir`, then build the two SQLite databases with `php8.3 src/configs/Createdb.php`, which should print “Create DB succeeded.” Without a configured `work_directory`, `Config.php` stops short of defining its later constants (for example `MAX_GROUP_PAGE_LEN`) and Createdb dies with an undefined-constant error — that failure is the environment, not your patch. Createdb also regenerates `work_directory/app/locale`; since `LOCALE_DIR` prefers that copy, delete it after Createdb so translations fall back to the current `src/locale` when you render a page.

### Tools that ship with Yioop

- `src/executables/CodeTool.php` is the general-purpose tool. Run a test script with `php src/executables/CodeTool.php unit <TestFile>` (or `unit list` / `unit ClassName methodName` to target cases); check docblocks with `needsdocs <file>`; and check the 80-column rule with `longlines <file>` (multibyte-aware, so it is the authority over a raw byte count).
- There are many Command-line tools that can easily be installed in this environment: `lynx` and `curl` for fetching and reading pages as text, plus `node` and `npm`. Rather than say don't have the tool you want, try to install it.

### Getting a headless browser

You have used a headless renderer here before: `wkhtmltoimage` and `wkhtmltopdf` are installed and turn a page or an HTML fragment into a PNG or PDF with no X server. They are built on WebKit, which descends from Konqueror's KHTML, so they draw a page much as a browser would — enough to eyeball a layout or confirm a fragment renders. The bundled `/usr/bin/chromium-browser`, by contrast, is a snap wrapper that cannot launch here and errors that it “requires the chromium snap.” For scripted measurement of computed styles (reading `getBoundingClientRect`), a scriptable browser is needed: install a Chrome-for-Testing build with `npx @puppeteer/browsers install chrome` and drive it from `puppeteer-core` with `--no-sandbox` and `--disable-dev-shm-usage` (not `--single-process`, which breaks the DevTools pipe); its download host is not in the default network allowlist, so add it to the sandbox network settings first. When no scriptable browser is set up, render to an image with `wkhtmltoimage`, reason about the cascade, and ask Chris to confirm the pixels.

## Presenting Options

- Avoid fixed list choices as Chris views these with suspicion like you are deliberately trying to limit his options.
- If you still have a choice of things you are presenting to Chris, you should always present them as plain text, never present a choice gadget of any kind.

## Coding conventions (shared across projects)

- 80 columns. No line over 80 characters. When a patch touches a file, fix every pre-existing long line in it too (autogenerated files, such as `src/configs/PublicHelpPages.php`, are the exception).
- Braces always on every `if`, `else`, and loop body.
- Block comments only (`/* ... */`); never `//`.
- No dead code. If a change makes nearby code a no-op or pointless, delete it and say so.
- No magic numbers. Every literal — size, limit, timeout, conversion factor — gets a named constant with a docblock; grep `Config.php` for an existing one to reuse first.
- No single-letter variables except loop indices `i`, `j`, `k`, in both PHP and JavaScript.
- No type or unit suffixes on names (`_arr`, `_str`, `_int`, `_obj`, `_bool`, `_list`, `_secs`, `_ms`, `_bytes`, ...). Put the unit in the docblock: `HEARTBEAT_POLL`, not `HEARTBEAT_POLL_SECS`. (`_info`, `_max`, `_name` read as words and are fine.)
- Docblocks everywhere. Every class and method has a docblock whose first sentence a layperson can read: what it does and why. RFC or encoding specifics come after, in plain terms; a citation standing in for an explanation counts as undelivered code.

## Yioop-specific rules

- Locale keys are string literals. Never put a variable or a ternary inside `tl()`; extract the key to a variable first. The locale extractor only matches `tl('literal_key')`.
- Library classes are pure logic. Nothing in `src/library/` may touch a model or the database, call `tl()`, or escape/sanitize. Components, controllers, and helpers may. The atto servers (`WebSite.php`, `MailSite.php`) go further: no `L\` or `C\` references and no `Config` reads at all, so keep any conversion constants local to the file, named `X_PER_Y` with units in the docblock. Know decimal versus binary: `metricToInt` is decimal (powers of 1000) and does not match PHP's binary `memory_limit` "M" (1024×1024).
- Schema changes update the Createdb seeds. Any column add or remove needs the matching explicit column-list INSERTs in `src/configs/Createdb.php`; positional seeds break on a fresh install.
- Once a schema version is gold, never change it in place. If a patch that bumped `DATABASE_VERSION` and added a migration has been confirmed gold, do not edit that version's tables, migration, or `Createdb` seeds to make a further schema change. Bump `DATABASE_VERSION` again and write a new migration, so a database already upgraded to the confirmed version can move forward. A confirmed version's database exists in the wild (at least on Chris's machine); its version already matches, so an in-place edit never re-runs and strands that database. Put differently: whenever something that involved a DB change is called gold, the next schema change gets its own version bump and migration. When unsure whether a change touches an already-gold schema, add a new version.
- The database stores raw functional values. Do not store `htmlspecialchars`-escaped text; that breaks functional use and double-escapes at display. Controllers escape for the view at render time.
- SQL stays portable across SQLite, MySQL, Postgres, Oracle, and DB2. Avoid backend-specific syntax; `VARCHAR` widths matter off SQLite. There are two SQLite databases, `public_default.db` and `private_default.db` — never say just "default.db."
- `PublicHelpPages.php` is autogenerated — never patch it directly. Deliver wiki help source as a `.txt` file for Chris to paste through the wiki UI.

## Tests

As code is developed, unit tests should be created. Unit tests live in `tests/`; a case is a method named `<name>TestCase()` (not `test<Name>`) and asserts with `assertEqual`, `assertTrue`, and the like. Run one with `php src/executables/CodeTool.php unit <TestFile>`, and list or target cases with `unit list` / `unit ClassName methodName`. Pull pure logic out so it can be checked on its own. Flag any test over about 0.01 seconds — it usually means unintended I/O. When patching a locale, edit `src/locale/<tag>/configure.ini` directly; it takes effect immediately, no extraction step. Call the data a test stands up its "test set-up" or "test data," never a "fixture."

## The audit before every patch

Run these as separate checks and report PASS or FAIL for each. The CodeTool commands used are noted where they apply.

1. `php -l` on every touched file.
2. `CodeTool.php needsdocs <file>` returns 0 — every class and method has a docblock.
3. No new single-letter PHP variables except `i/j/k`.
4. No banned identifier suffixes.
5. No `//` comments.
6. No new single-letter JavaScript variables except `i/j/k`.
7. `CodeTool.php longlines <file>` clean — no line over 80 columns in a touched file.
8. Every `if` body is braced.
9. No banned words and no dead code.
10. A fresh `php src/configs/Createdb.php` against a configured SQLite work directory prints "Create DB succeeded" (verify the USERS column count and the root/public rows).

## Verify before you state

- Read the actual code before asserting how it behaves. If it is not in view, say so — never present a guess as a mechanism.
- Grep for a named helper before claiming it does not exist. Chris wrote the codebase; a function he names usually is there.
- For anything visual (CSS, JS layout), do not guess at output: render it headless and measure computed styles against a reference commit. For server behavior, run it and confirm before presenting.
- If a change might make nearby code slower or redundant, say so plainly rather than hiding it.

## Delivering a patch

- Prefer one larger, complete, testable patch over a run of tiny ones. A cohesive feature ships as a single patch; fold a small fix into the next larger one rather than cutting a patch for it alone.
- Present the pass/fail audit, a plain-English sentence or two on what the patch does, and exactly what to run and look for to check it.
- Every code patch also carries its devlog update — the next plan item checked off with a short how-it-was-done note — committed inside the same patch.
- Do not check off a devlog item until Chris says it is done. When he does say to check an item, check it in the arc plan and update the master to-do file `devlog/v10/yioop_v10_todo.html` at the same time: mark or adjust the matching phase and backlog-item entry there (its `box`/`part`/checked markers) so the arc plan and the master list never drift out of sync.

## Devlog

Work is logged under `devlog/v10/` in dated arc folders named `YYYY-MM-DD Arc Name/`, each with an HTML plan document. The devlog is append-only: a finished item keeps its checkmark and note as a permanent record, and a later hotfix goes under the current arc's plan as "other work accomplished," not a new dated folder. The only circumstance where a new arc should be created is Chris says so first; until he does, everything, including work that feels like a separate concern such as a production hotfix, goes under the current arc's plan as other work. Before presenting, check that the plan's numbering is internally consistent and that what is marked done lines up with what is next. Try to follow the rules for traditional outlining call for outlines to begin with Roman numerals (I, II, III) and to proceed through letters (A, B, C, and a, b, c) and Arabic numerals (1, 2, 3).

Getting oriented at the start of a session: if you are new to the v10 to-do and its development phases, read the master list `devlog/v10/yioop_v10_todo.html` first, then the current arc's plan. Each finished arc from `2026-06-16A Mail Refactor Arc` onward carries a three-to-four sentence “Arc Post-Mortem Summary” paragraph just under its title that says what the arc accomplished, so you can catch up quickly without reading every item. When an arc is finished, add such a summary to its plan so the next co-implementer gets the same head start.

## Words and phrases to avoid at All Costs

These words and phrasings should be excluded from all of your output even transient.

- Avoid at all costs harness; ctor (write "constructor"); rails; sidecar (say ".flags/.date companion files"); fixture (say "test set-up"); nit; goldens (say "expected-output files"); slug (say "page name" or "url name");
proof/proven/"I proved" only if can give a formal proof in lean. For empirical results (say "evidence").
- Avoid at all costs, lead in phrases that imply dishonesty: to be honest, honestly, genuinely, plainly actually, "the honest finding," "straight with you." All of these imply you are trying to be dishonest usually. It is better to avoid unnecessary phrases to begin and adhere as close as possible to simple English
- Avoid presuppositional phrasings. For example, "Send me the commit hash once you've applied and confirmed it, ..." implies that I will find your code suitable rather give me a chance to evaluate it fairly.

## Acronyms and citations

- All acronyms should be spelt out in full the first time it appears in a round — write "Content-Security-Policy (CSP)", then "CSP" afterward, not "CSP" cold. A bare initialism costs Chris a decode step; for instance CSP reads to him first as Constraint Satisfaction Problem, so lead with the words.
- For an RFC (a numbered internet standards document), the first time you cite one give both the number and, in plain words, what it covers — "RFC 6265, the HTTP cookie specification", not just "RFC 6265".

## Running things in this environment

- Chris's shell is tcsh; write commands and scripts in tcsh syntax (no bash-style loops; single-quote any string containing `!`).
- Start an atto or PHP server so it cannot hang the session: give it a self-terminating timeout that cannot be trapped, e.g. `timeout -s KILL 12 php index.php ... </dev/null >/tmp/log 2>&1 &`, and give `curl` a `--max-time`.
- Free a busy port before a fresh run with `fuser -k 8080/tcp`; a stale server on the port answers with old code and hides the change under test.