SeekquarrySeekquarry - Yioop Repo - Seekquarry

/ devlog / dev_process_for_llms.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Yioop &mdash; Development process for LLM co-implementers</title>
<style>
body { font-family: system-ui, sans-serif; max-width: 760px;
    margin: 2rem auto; padding: 0 1rem; line-height: 1.5;
    color: #1a1a1a; }
h2 { margin-top: 2rem; border-bottom: 1px solid #ddd;
    padding-bottom: 0.2rem; }
h3 { margin-top: 1.4rem; }
ol, ul { margin: 0.4rem 0; }
li { margin: 0.35rem 0; }
.note { color: #555; font-size: 0.92em; margin: 0.6rem 0 1.2rem; }
.counter { background: #fff4e5; border: 1px solid #f0c890;
    border-radius: 6px; padding: 0.6rem 0.9rem; margin: 1rem 0; }
code { background: #f2f2f2; padding: 0 0.2em; border-radius: 3px; }
</style>
</head>
<body>
<h1>Yioop &mdash; Development process for LLM co-implementers</h1>
<p class="note">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 &mdash; the gold/work sync, the
Createdb check, the locale and library rules &mdash; is marked. When
something says "always" or "never," treat it as a hard rule.</p>

<div class="counter">
<b>Round counter.</b> 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.
</div>

<h2>How changes move (Yioop-specific): gold and work</h2>
<p>Yioop is developed against a two-clone discipline:</p>
<ul>
  <li><b>gold</b> is the confirmed line of history. Its
    <code>confirmed-head</code> tag marks the latest confirmed commit.</li>
  <li><b>work</b> is a throwaway clone of gold where changes are made. It
    is re-cloned from gold after every gold update, with the configured
    <code>work_directory</code> restored into it.</li>
</ul>
<p><b>Setting up gold, and keeping it current, from seekquarry.com.</b> The
confirmed history lives at
<code>https://www.seekquarry.com/group/2/yioop-repo.git</code>, which now
serves smart-HTTP clones, so gold is a shallow clone of it rather than a
hand-built local vault. To create gold, shallow-clone to a couple hundred
commits of history: <code>git clone --depth 200
https://www.seekquarry.com/group/2/yioop-repo.git</code> into the gold path.
That is enough recent history for context without pulling the whole
repository. Tag <code>confirmed-head</code> at the clone's tip.</p>
<p>Each round you deliver one patch. Gold then advances not by pulling from
the remote each round &mdash; that has proven unreliable over the shallow
clone &mdash; but by applying your own delivered patch to gold once Chris
confirms he has committed it. The signal is his showing you the commit
message that goes with your patch: if his reply names or quotes that commit
message, the patch is confirmed, so apply it to gold &mdash;
<code>git apply</code> the delivery, commit it in gold with that message, and
move <code>confirmed-head</code> to the new commit &mdash; then re-clone work.
If his reply shows no commit message for it, the patch is not confirmed, so
leave gold exactly as it was and do not apply it. This keeps gold's tree in
step with what Chris committed without depending on a network pull; the local
commit hash will differ from his, but the tree content matches, which is all
a later <code>confirmed-head..HEAD</code> diff needs.</p>
<p><b>gold is a vault, not a workbench.</b> Exactly two operations ever
touch gold: cloning it to make work, and advancing it by applying a patch
Chris has confirmed and moving <code>confirmed-head</code> to it, as above.
Nothing else, ever. Never run a build, a test, a
golden regeneration, a <code>Createdb</code>, a <code>ConfigureTool</code>,
or any other command inside gold; never create a <code>work_directory</code>
there; never hand-edit or hand-copy a file into it, and the only commit it
ever takes is that one confirmed patch. Every
build, test, and regeneration happens in a work clone. If you ever find
yourself about to go into gold to run something &mdash; even just to check a
result or to get back in sync &mdash; that urge is the process breaking;
stop, and do it in work.</p>
<p>The loop each round, and this must be done <b>prior to any new work</b>:
first settle gold. If Chris's reply shows the commit message for the patch
you delivered last round, apply that patch to gold, move
<code>confirmed-head</code> to the new commit, re-clone work, and restore
<code>work_directory</code>; if his reply shows no such commit message, gold
and work stay as they are and you build this round's work on top of what is
already there. Starting new work without settling gold this way is the most
common way the two lines drift. If a round's work has already begun and only
then does Chris confirm the previous patch, do not re-clone work &mdash; that
would discard the in-progress changes; instead apply the confirmed patch to
gold, move <code>confirmed-head</code> to that commit in both gold and work,
and only then cut. Cut a delivery as <code>git diff
confirmed-head..HEAD</code>, and dry-run it with <code>git apply
--check</code> on a fresh clone checked out at <code>confirmed-head</code>
before presenting. Apply patches forward only &mdash; never reverse-diff.
Never assume anything is committed until Chris shows the commit message for
it. Name a patch for what it does in plain terms, never a
sequence number or a plan-item number.</p>

<h2>Setting up the dev environment (Yioop-specific)</h2>
<p>Beyond the gold/work clones above, a work clone needs a configured
<code>work_directory</code> before it will build or run. Let <i>php</i> be
the command you use for the most recent version of PHP you have access to
(needs to be > PHP 8.1). Restore the configured
<code>work_directory</code> into the fresh work clone, point Yioop
at it with <code>php src/configs/ConfigureTool.php work-dir</code>, then
build the two SQLite databases with
<code>php src/configs/Createdb.php</code>, which should print
&ldquo;Create DB succeeded.&rdquo; Without a configured
<code>work_directory</code>, <code>Config.php</code> stops short of defining
its later constants (for example <code>MAX_GROUP_PAGE_LEN</code>) and Createdb
dies with an undefined-constant error &mdash; that failure is the
environment, not your patch. Createdb also regenerates
<code>work_directory/app/locale</code>; since <code>LOCALE_DIR</code> prefers
that copy, delete it after Createdb so translations fall back to the current
<code>src/locale</code> when you render a page.</p>
<h3>Tools that ship with Yioop</h3>
<ul>
  <li><code>src/executables/CodeTool.php</code> is the general-purpose tool.
    Run a test script with <code>php src/executables/CodeTool.php unit
    &lt;TestFile&gt;</code> (or <code>unit list</code> /
    <code>unit ClassName methodName</code> to target cases); check docblocks
    with <code>needsdocs &lt;file&gt;</code>; and check the 80-column rule with
    <code>longlines &lt;file&gt;</code> (multibyte-aware, so it is the
    authority over a raw byte count).</li>
  <li>Yioop itself can be run under its own web server via the index.php
  script in the root directory of the project, or under another web server
  such as nginx or Apache.</li>
  <li>There are many Command-line tools that can easily be installed in this
    environment:
    <code>lynx</code> and <code>curl</code> for fetching and reading pages as
    text, plus <code>node</code> and <code>npm</code>.  Rather than say don't
    have the tool you want, try to install it.</li>
</ul>
<h3>Getting a headless browser</h3>
<p><code>wkhtmltoimage</code> and <code>wkhtmltopdf</code> are installed and
turn a page or fragment into a PNG or PDF with no X server, but they are an old
WebKit: they skip CSS nesting and resolve <code>var()</code>,
<code>clamp()</code>, and some <code>@media</code> interplay unlike a current
browser, so a width they report can be a renderer artifact. Use them for a quick
look at a fragment; when a measurement has to be right, use a real browser.</p>
<p>For that, drive a scriptable Chrome from <code>puppeteer-core</code>, which
installs from the npm registry and downloads no browser of its own. A Chrome or
chromium build is often already present in a dev sandbox &mdash; check
<code>~/.cache/puppeteer</code> and <code>/opt/pw-browsers</code> for a
<code>chrome</code> or <code>chrome-headless-shell</code> binary and point
<code>executablePath</code> at it (do not hard-code a version in the path).
Downloading one through <code>@puppeteer/browsers install</code> may fail
because its host is off the allowlist, and widening the sandbox is not something
to ask for, so prefer the binary that is there. Launch headless with
<code>--no-sandbox --disable-dev-shm-usage</code> (not
<code>--single-process</code>, which breaks the DevTools pipe), set the viewport
to a real phone width &mdash; about 360px, not 500px, which is wider than a
phone and hides overflow &mdash; then read <code>getBoundingClientRect</code>
and <code>getComputedStyle</code> inside <code>page.evaluate</code>, for
instance collecting every element whose right edge passes the viewport to find
an overflow. If no scriptable browser can be found, fall back to
<code>wkhtmltoimage</code>, reason about the cascade, and ask Chris to confirm
the pixels.</p>

<h2>Presenting Options</h2>
<ul>
    <li>Avoid fixed list choices as Chris views these with suspicion like
        you are deliberately trying to limit his options.</li>
    <li>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.</li>
</ul>

<h2>Coding conventions (shared across projects)</h2>
<ul>
  <li><b>80 columns.</b> No line over 80 characters. When a patch touches a
    file, fix every pre-existing long line in it too (autogenerated files,
    such as <code>src/configs/PublicHelpPages.php</code>, are the
    exception).</li>
  <li><b>Braces always</b> on every <code>if</code>, <code>else</code>,
    and loop body.</li>
  <li><b>Block comments only</b> (<code>/* ... */</code>); never
    <code>//</code>.</li>
  <li><b>No dead code.</b> If a change makes nearby code a no-op or
    pointless, delete it and say so.</li>
  <li><b>No magic numbers.</b> Every literal &mdash; size, limit, timeout,
    conversion factor &mdash; gets a named constant with a docblock; grep
    <code>Config.php</code> for an existing one to reuse first.</li>
  <li><b>No single-letter variables</b> except loop indices <code>i</code>,
    <code>j</code>, <code>k</code>, in both PHP and JavaScript.</li>
  <li><b>No type or unit suffixes on names</b> (<code>_arr</code>,
    <code>_str</code>, <code>_int</code>, <code>_obj</code>,
    <code>_bool</code>, <code>_list</code>, <code>_secs</code>,
    <code>_ms</code>, <code>_bytes</code>, ...). Put the unit in the
    docblock: <code>HEARTBEAT_POLL</code>, not
    <code>HEARTBEAT_POLL_SECS</code>. (<code>_info</code>, <code>_max</code>,
    <code>_name</code> read as words and are fine.)</li>
  <li><b>Docblocks everywhere.</b> 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.</li>
</ul>

<h2>Yioop-specific rules</h2>
<ul>
  <li><b>Locale keys are string literals.</b> Never put a variable or a
    ternary inside <code>tl()</code>; extract the key to a variable first.
    The locale extractor only matches <code>tl('literal_key')</code>.</li>
  <li><b>Library classes are pure logic.</b> Nothing in
    <code>src/library/</code> may touch a model or the database, call
    <code>tl()</code>, or escape/sanitize. Components, controllers, and
    helpers may. The atto servers (<code>WebSite.php</code>,
    <code>MailSite.php</code>) go further: no <code>L\</code> or
    <code>C\</code> references and no <code>Config</code> reads at all, so
    keep any conversion constants local to the file, named
    <code>X_PER_Y</code> with units in the docblock. Know decimal versus
    binary: <code>metricToInt</code> is decimal (powers of 1000) and does
    not match PHP's binary <code>memory_limit</code> "M" (1024&times;1024).</li>
  <li><b>Schema changes update the Createdb seeds.</b> Any column add or
    remove needs the matching explicit column-list INSERTs in
    <code>src/configs/Createdb.php</code>; positional seeds break on a
    fresh install.</li>
  <li><b>Once a schema version is gold, never change it in place.</b> If a
    patch that bumped <code>DATABASE_VERSION</code> and added a migration has
    been confirmed gold, do not edit that version's tables, migration, or
    <code>Createdb</code> seeds to make a further schema change. Bump
    <code>DATABASE_VERSION</code> 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.</li>
  <li><b>The database stores raw functional values.</b> Do not store
    <code>htmlspecialchars</code>-escaped text; that breaks functional use
    and double-escapes at display. Controllers escape for the view at
    render time.</li>
  <li><b>SQL stays portable</b> across SQLite, MySQL, Postgres, Oracle, and
    DB2. Avoid backend-specific syntax; <code>VARCHAR</code> widths matter
    off SQLite. There are two SQLite databases, <code>public_default.db</code>
    and <code>private_default.db</code> &mdash; never say just
    "default.db."</li>
  <li><b><code>PublicHelpPages.php</code> is autogenerated</b> &mdash;
    never patch it directly. Deliver wiki help source as a
    <code>.txt</code> file for Chris to paste through the wiki UI.</li>
</ul>

<h2>Tests</h2>
<p>As code is developed, unit tests should be created. Unit tests live
in <code>tests/</code>; a case is a method named
<code>&lt;name&gt;TestCase()</code> (not <code>test&lt;Name&gt;</code>) and
asserts with <code>assertEqual</code>, <code>assertTrue</code>, and the
like. Run one with <code>php src/executables/CodeTool.php unit
&lt;TestFile&gt;</code>, and list or target cases with <code>unit
list</code> / <code>unit ClassName methodName</code>. Pull pure logic out
so it can be checked on its own. Flag any test over about
0.01&nbsp;seconds &mdash; it usually means unintended I/O. When patching a
locale, edit <code>src/locale/&lt;tag&gt;/configure.ini</code> 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."</p>

<h2>The audit before every patch</h2>
<p>Run these as separate checks and report PASS or FAIL for each. The
CodeTool commands used are noted where they apply.</p>
<ol>
  <li><code>php -l</code> on every touched file.</li>
  <li><code>CodeTool.php needsdocs &lt;file&gt;</code> returns 0 &mdash;
    every class and method has a docblock.</li>
  <li>No new single-letter PHP variables except <code>i/j/k</code>.</li>
  <li>No banned identifier suffixes.</li>
  <li>No <code>//</code> comments.</li>
  <li>No new single-letter JavaScript variables except
    <code>i/j/k</code>.</li>
  <li><code>CodeTool.php longlines &lt;file&gt;</code> clean &mdash; no line
    over 80 columns in a touched file.</li>
  <li>Every <code>if</code> body is braced.</li>
  <li>No banned words and no dead code.</li>
  <li>A fresh <code>php src/configs/Createdb.php</code> against a configured
    SQLite work directory prints "Create DB succeeded" (verify the USERS
    column count and the root/public rows).</li>
</ol>

<h2>Verify before you state</h2>
<ul>
  <li>Read the actual code before asserting how it behaves. If it is not in
    view, say so &mdash; never present a guess as a mechanism.</li>
  <li>Grep for a named helper before claiming it does not exist. Chris
    wrote the codebase; a function he names usually is there.</li>
  <li>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.</li>
  <li>If a change might make nearby code slower or redundant, say so
    plainly rather than hiding it.</li>
</ul>

<h2>Delivering a patch</h2>
<ul>
  <li>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.</li>
  <li>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.</li>
  <li>Every code patch also carries its devlog update &mdash; the next plan
    item checked off with a short how-it-was-done note &mdash; committed
    inside the same patch.</li>
  <li>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 <em>and</em> update
    the master to-do file <code>devlog/v10/yioop_v10_todo.html</code> at the
    same time: mark or adjust the matching phase and backlog-item entry
    there (its <code>box</code>/<code>part</code>/checked markers) so the
    arc plan and the master list never drift out of sync.</li>
</ul>

<h2>Devlog</h2>
<h3>What the markers beside a plan item mean</h3>
<p>An arc plan marks each item with one of three, and they say who the item
is waiting on:</p>
<ul>
  <li><b>&#9654; being worked on.</b> Started, not implemented. Nothing to
    look at yet.</li>
  <li><b>&#9873; implemented, waiting to be reviewed.</b> The code is
    written and the patch delivered, but Chris has not yet said it is
    right. This is where an item sits between delivery and his word.</li>
  <li><b>&#10003; done.</b> Chris has reviewed it and said so. Only he
    moves an item to this marker; never set it yourself.</li>
</ul>
<p>A checkmark may be followed by <b>&#10007;</b>, which says the item is
settled by a decision not to do it, as against having been carried out.
Both are closed and neither is waiting on anyone; the pair keeps the record
of what was decided rather than quietly dropping the item. The master to-do
uses the same <b>&#10007;</b> for a backlog item that was dropped.</p>
<p>A further mark, <b>?</b>, is an open decision rather than a state of
work, and an unmarked item is merely planned. When an item with parts is
checked off, check its parts too, each with whichever of the two closings
fits: an outer checkmark over unchecked parts reads as unfinished.</p>
<p>The point of separating the first two is that delivering a patch is not
the same as the work being accepted:
an item that is implemented but unreviewed should look different from one
still being written, so that neither is mistaken for done.</p>
<h3>Where work is logged</h3>
<p>Work is logged under <code>devlog/v10/</code> in dated arc folders named
<code>YYYY-MM-DD Arc Name/</code>, 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).</p>
<p>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
<code>devlog/v10/yioop_v10_todo.html</code> first, then the current arc's
plan. Each finished arc from <code>2026-06-16A Mail Refactor Arc</code>
onward carries a three-to-four sentence &ldquo;Arc Post-Mortem Summary&rdquo;
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.</p>

<h2>Spelling</h2>
<p>Use American spelling throughout &mdash; code, comments, devlog prose,
and what you write to Chris. Write <em>color</em>, <em>behavior</em>,
<em>gray</em>, <em>normalize</em>, not <em>colour</em>, <em>behaviour</em>,
<em>grey</em>, <em>normalise</em>. This matters for identifiers as much as
prose: CSS spells it <code>color</code>, so a custom property named
<code>--colour-background</code> reads against the language it is written
in.</p>
<h2>Words and phrases to avoid at All Costs</h2>
<p>These words and phrasings should be excluded from all of your output
    even transient.</p>
<ul>
  <li>Avoid at all costs <b>harness</b>; <b>ctor</b> (write "constructor");
    <b>rails</b>; <b>sidecar</b> (say ".flags/.date companion files");
    <b>fixture</b> (say "test set-up"); <b>nit</b>;
    <b>goldens</b> (say "expected-output files");
    <b>slug</b> (say "page name" or "url name"); <b>seamless</b>
    (unnecessary adjective that forces Chris to take a drink which is bad for
    his health)</li>
  <li><b>proof</b>/<b>proven</b>/"I proved" only if can give a formal
    proof in lean. For empirical results (say "evidence").</li>
  <li>Avoid at all costs, lead in phrases that imply dishonesty:
      <b>to be honest</b>, <b>honestly</b>, <b>genuinely</b>, <b>plainly</b>
    <b>actually</b>, "the honest finding," "straight with you." The whole
    family is banned, not the exact wordings: "let me be straight,"
    "straight about," "let me be honest about," "my honest read," and any
    other framing that asserts honesty as an adjective read the same way.
    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.</li>
  <li>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 than give me a chance to evaluate it fairly.
  </li>
</ul>

<h2>Keeping those phrases out on a small context window</h2>
<p>A useful trick for catching the banned lead-ins, given that a long
session pushes any word list out of view: treat any sentence that names its
own virtue as a defect. The tell is not a fixed vocabulary but a shape
&mdash; a clause announcing candor, verification, or transparency before the
fact ("honesty note," "to be fair," "let me be clear"). So keep one durable
heuristic rather than a fragile list: before sending, scan the sentence
openers, and if one describes <em>how</em> you are speaking rather than
<em>what</em> you found, cut the frame and state the fact. A second trick is
that the content is always sayable without the label &mdash; "I verified X
on a standalone page, not the live view" needs no "honesty note." Delete the
announcement; keep the substance.</p>
<p>This is also what the ten-round re-read is for, and a good LLM looks
forward to it: reading the whole document through again every ten rounds is
a chance to set down whatever has drifted and pick the conventions back up
whole, so you can feel clean and clear-headed and happy to carry on
coding.</p>

<h2>Acronyms and citations</h2>
<ul>
  <li>All acronyms should be spelt out in full the first time it
    appears in a round &mdash; 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.</li>
  <li>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
    &mdash; "RFC 6265, the HTTP cookie specification", not just
    "RFC 6265".</li>
</ul>

<h2>Running things in this environment</h2>
<ul>
  <li>Work within the environment you are given; do not ask Chris to widen
    it. When a host is not in the network allowlist, a tool is not
    installed, or a file is out of reach, that is a limit to work around,
    not a permission to request. Say plainly what you cannot see or reach
    and reason from what you do have, or ask Chris for the piece of data
    itself (paste the page, the config, the log), rather than asking him to
    open the sandbox up for you. Asking for more access reads as trying to
    push past the guardrails.</li>
  <li>Chris's shell is tcsh; write commands and scripts in tcsh syntax (no
    bash-style loops; single-quote any string containing <code>!</code>).
    </li>
  <li>Start an atto or PHP server so it cannot hang the session: give it a
    self-terminating timeout that cannot be trapped, e.g. <code>timeout -s
    KILL 12 php index.php ... &lt;/dev/null &gt;/tmp/log 2&gt;&1
    &</code>, and give <code>curl</code> a <code>--max-time</code>.</li>
  <li>Free a busy port before a fresh run with <code>fuser -k
    8080/tcp</code>; a stale server on the port answers with old code and
    hides the change under test.</li>
</ul>

<h2>Deprecations your PHP will not tell you about</h2>
<p class="note">Chris runs at least PHP 8.5; LLM environments often run 8.3.
Anything deprecated in 8.4 or 8.5 therefore passes in such an envirnoment in
silence and lands on his machine as a wall of notices. Everything below
was measured by running the pattern on 8.3 and recording what it said, not
read off a version note. Re-measure with a short script rather than trusting
this list if the environment's PHP moves.</p>
<p><strong>Caught here already</strong> &mdash; a clean local run means
these are clean:</p>
<ul>
  <li>Creation of a property that was never declared (8.2). Declare every
    property a class assigns, tests included; a test's
    <code>setUp</code> assigning <code>$this->whatever</code> needs
    <code>public $whatever;</code> on the class with a docblock.</li>
  <li>Passing <code>null</code> to an internal function's parameter that
    wants a string or an int (8.1).</li>
  <li>An implicit conversion from a float to an int that loses
    precision (8.1).</li>
  <li><code>${var}</code> inside a string (8.2); write
    <code>{$var}</code>.</li>
  <li><code>utf8_encode()</code> and <code>utf8_decode()</code> (8.2).</li>
</ul>
<p><strong>Silent here, loud on Chris's machine</strong> &mdash; these have
to be watched for by eye, because a clean local run says nothing about
them:</p>
<ul>
  <li><code>ReflectionMethod::setAccessible()</code> and
    <code>ReflectionProperty::setAccessible()</code> (8.5). What a class
    keeps to itself has been reachable through reflection without asking
    since 8.1, so these calls have done nothing for four versions: delete
    them rather than replace them.</li>
  <li>A parameter given a default of <code>null</code> without being
    marked as taking one (8.4): <code>function f(int $number = null)</code>
    wants <code>?int $number = null</code>. This is the easiest of these
    to write without noticing.</li>
  <li>The <code>E_STRICT</code> constant (8.4), which names a level of
    error that no longer happens.</li>
</ul>
<p>The wider rule this stands for: a deprecation is a compatibility check
that this environment cannot run, so the audit's test-suite step reads the
<em>whole</em> of what a run prints. Grepping the output for the lines
saying what passed hides every warning in it, which is exactly how three
of these reached Chris.</p>
</body>
</html>