Build and deploy / build-and-deploy (push) Failing after 4s
Four rulings from Pouya, plus what implementing them turned up. Q58 — RULED, and he attributed the ambiguity to his own document: "3.5 was meant as the TOTAL time committed, of which 2 is preparation — leaving 1.5 hours in the room. Your arithmetic caught it: if prep sat inside, 3.5 and 7 wouldn't be exactly 2x, because preparation doesn't scale with session length." The card now reads: half day up to 3 hours of session, fee includes up to 2 hours of preparation, $2,000; full day up to 6 hours, up to 3 hours of preparation, $4,000. docs/07's own research table corroborates 3 and 6 — Patey and Zuber both publish those hours, and ADR Chambers' roster rate counts preparation separately from "up to three hours of mediation". One provenance note under R14: he recalled "all or part of 3 hours" as their wording; the committed extract carries the hours but not the phrase, so docs/07 cites the hours and attributes the phrase to nobody. Two things fell out of the ruling that the instruction did not name, and both were defects. docs/07 §All parameters confirmed was itself prescribing the flat "including 2 hours of preparation" — the sentence /for-parties/ was built against, so the spec was generating the defect. And the cap had to reach the copy: "including up to 2 hours". FEES.mediation.*.hours is corrected 3.5 -> 3 and 7 -> 6; it had no consumer in src/ while the question was open, which is the only reason no page was ever wrong. /fees/ is unblocked for step 9 on the question Q58 asked. Q57 — CLOSED with no seventh undertaking. "A reader assumes the outcome, and the obvious undertaking adds nothing a reader doesn't already infer." The TODO(pouya) is replaced by the ruling where the question was; src/ now carries zero live TODO(pouya) markers. §4's mediation row lists all six published areas. Q56's ruling had named five, which was four areas plus the word "commercial" — a scope descriptor, not a seventh area. The hedge is struck on his instruction; the clause saying the six are not the authorised subject-matter list is restored, because his ruling supplied a correct value and did not close Q35(c)'s class. Split-stamped. docs/03's compliance checklist now names what to look for on a page and which §4 row decides it, never the bar's own wording. 12 items before, 12 after — a structural fix, not a coverage change. Thirteen review findings across two rounds, all applied, none declined. Three were mine to own. The capped-form rule was written and then applied to one surface: /mediation/ shipped an uncapped form in words no barred-string grep could reach, site.ts quoted a docs/07 sentence Q58 had just deleted, and §9's Q15/Q16/Q17 row prescribed the flat form — which is what a later implementer building /fees/ reads. A derived fee term was asserted as applied fact in the document that is the authority on money: "overtime begins after 3 h and 6 h" is in no ruling. Struck, and opened as Q59. And round 2 caught the arithmetic in round 1's own fix. The full-day route is flat $4,000 until hour 6, so generalising it as 500n+1000 for all n>=3 was valid only from 6 h, and "cheaper by $500 at every length" was wrong across the whole 3-6 h band. The real spread is $2,000 at three hours narrowing to $500 from six on — up to four times larger, and largest exactly where a half-day booking overruns. Written into docs/07 §Recorded dissent and §12's R5 row, which is where the 12-month fee review will read it. Round 1's fix for the missing consequence also published the overtime rate on a page that now states an unambiguous cap, defining the trigger by adjacency with no other quantity for it to attach to; the rate came off the page. R11 at the step 6 -> 7 boundary: 13 of 14 pins current. §7's TypeScript hold named one gate and there are two — typescript-eslint requires <6.1.0, tighter than @astrojs/check, so the recorded removal trigger was unreachable. Both are now named. Verified: check 0 errors, lint 0, build 0 (14 pages), check:claims 0, npm audit 0, minifier tripwire clean, zero JS shipped, all copy present with JavaScript disabled. Lighthouse not run — tool unavailable until step 7. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
477 lines
28 KiB
Markdown
477 lines
28 KiB
Markdown
# CLAUDE.md — operating instructions for Claude Code
|
|
|
|
## Read this first
|
|
|
|
1. **`AGENTS.md` is the source of truth for this project.** Read it in full
|
|
before your first edit in any session. It carries the locked decisions, the
|
|
credential register, the open questions, and the full history.
|
|
2. **You are required to maintain `AGENTS.md`** under the constitution written at
|
|
the top of it. Update *Current Truth* in place; append to the *Change Log*,
|
|
newest first; never edit a past entry; never delete history. Record decisions
|
|
and plans, not only executed work. Stamp facts `[verified YYYY-MM-DD]` or
|
|
`[assumed]`.
|
|
3. Update it **at the end of every working session**, not only when something
|
|
ships. A session that produced a decision and no code still produces a Change
|
|
Log entry.
|
|
4. **Read `AGENTS.md` §12 Standing Reminders at the start of every substantial
|
|
session and surface anything live to Pouya.** These are decisions he parked
|
|
deliberately, not settled matters — R1 in particular is his explicit
|
|
instruction to keep raising the licensure wording. A parked decision that
|
|
stops being raised has quietly become permanent, which is the failure mode
|
|
§12 exists to prevent.
|
|
|
|
## The one rule that matters more than the code
|
|
|
|
This is Pouya's public marketing surface, and the site it replaces carried
|
|
fabricated credentials. **No factual claim about him, his credentials, his
|
|
experience, or his practice may appear on a public page unless it is in the
|
|
Verified table in `AGENTS.md` §4.**
|
|
|
|
(§4 does not verify licensure either way — so do not describe him as
|
|
"licensed", or as a "legal professional", anywhere, this file included. State
|
|
the reason for the rule, not a credential the register cannot vouch for.)
|
|
|
|
If a page needs a fact you do not have:
|
|
|
|
- Do not infer it from context.
|
|
- Do not soften it into something defensible ("extensive experience", "years of").
|
|
- Do not carry it over from the old site — the old site contained a fictitious
|
|
founder, invented matter values, and a fabricated testimonial.
|
|
- **Leave `TODO(pouya): <the exact question>` in the source, and add the question
|
|
to `AGENTS.md` §9.** A build that fails on an unanswered question is a correct
|
|
build.
|
|
|
|
Read the Forbidden table in §4 before writing any statistic, number, or
|
|
superlative.
|
|
|
|
## How work is executed here
|
|
|
|
Pouya is the architect. He makes the decisions and hands you the task. **You
|
|
implement, then you adversarially review your own work before calling it done.**
|
|
This is the standing agreement — it applies to every substantial change without
|
|
being restated in the prompt.
|
|
|
|
**Run `/build <task>` for any substantive change.** It encodes the loop:
|
|
|
|
1. **Plan** — read `AGENTS.md` (including §12 Standing Reminders, and surface
|
|
anything live), read the governing specs, name the decisions the task touches,
|
|
and **stop and ask on any conflict**. A blocked build is a correct build.
|
|
2. **Implement** — following the conventions below.
|
|
3. **Adversarial review** — invoke `adversarial-reviewer` on the diff. **D20:
|
|
`claims-auditor` does NOT run per step.** It runs once, at cutover, over the
|
|
whole finished site.
|
|
4. **Resolve** — fix each finding or decline it with a stated reason. Re-review
|
|
material fixes.
|
|
5. **Verify** — run the checks. Never report a check as passing that you did not
|
|
run.
|
|
6. **Record** — append the `AGENTS.md` Change Log entry.
|
|
|
|
`/review` runs phase 3 alone. `/wrap` runs phase 6 at session end.
|
|
|
|
**Agent definitions load at session start.** An edit to `.claude/agents/*.md`
|
|
does not reach the session you made it in — the brief in force is the one that
|
|
was on disk when the session began. After committing a change to one, **restart
|
|
before relying on it, and say in the report which version actually ran.** Found
|
|
2026-08-30: the gloss lens was added to `claims-auditor` and the agent then
|
|
reconstructed it from the `AGENTS.md` Change Log rather than having it in its
|
|
brief, which is luck, not process.
|
|
|
|
**Think deeply before acting.** Extended thinking is on by default for this
|
|
project (`.claude/settings.json`), and `/build` and `/review` request it
|
|
explicitly. The planning and review phases are where it earns its cost — a defect
|
|
reasoned out before implementation is far cheaper than one found after.
|
|
|
|
### Why the review is adversarial, and what would break it
|
|
|
|
Two rules make the difference between a review and a rubber stamp:
|
|
|
|
**Do not brief the reviewers on why your work is correct.** Give them the diff
|
|
and the specs, nothing else. Your rationale anchors them, and an anchored
|
|
reviewer produces agreement rather than review. They must form an independent
|
|
view from the artefact — that independence *is* the mechanism.
|
|
|
|
**The reviewers are instructed to treat uncertainty as a defect.** They will
|
|
sometimes be wrong, and that is the intended trade. Explaining why a finding is
|
|
mistaken costs minutes; a missed defect on this project's public marketing
|
|
pages costs considerably more — the site this replaces carried fabricated
|
|
credentials, and that is the standard being corrected. Do not read a finding as
|
|
an accusation, and do not argue a reviewer down — either fix it, or record the
|
|
reason you declined it so a later reader can see the judgement was made rather
|
|
than missed.
|
|
|
|
**Two reviewers, because they catch different things — but they no longer run at
|
|
the same time.** `adversarial-reviewer` reads the code. `claims-auditor` reads the
|
|
copy against the §4 register and knows nothing about whether the code is elegant.
|
|
A generic reviewer consistently under-weights the professional-conduct check,
|
|
which is the highest-stakes failure mode on this project — so it keeps its own
|
|
pass rather than being folded into the code review.
|
|
|
|
**D20, 2026-08-30 — the claims pass moved to cutover.** Per step it is
|
|
`adversarial-reviewer` alone. `claims-auditor` runs **once, over the whole
|
|
finished site**, as a blocking item on `docs/06`'s cutover checklist. Pouya's
|
|
reasoning, and it is a calibration and not an erosion: nothing has shipped, so
|
|
every claims finding so far has been about a page no visitor can reach — the risk
|
|
is deferred to cutover anyway, and one pass over twenty finished pages catches
|
|
**more** than nine passes over drafts, because it sees the site as a reader does.
|
|
The `/med-arb/` ADRIC gloss is the proof: no individual claim was false, the
|
|
defect was **adjacency**, and adjacency does not exist until the pages sit next to
|
|
each other. The code reviewer stays per step because what it catches **compounds**
|
|
— an accessibility or crawlability defect propagates into the next page built on
|
|
it, and a claims defect does not; it sits there until someone reads it.
|
|
|
|
**What it costs is recorded in `AGENTS.md` D20, not summarised away here.** Read
|
|
it before proposing any further relaxation: `claims-auditor` has caught defects
|
|
that would have been serious on a live page, and D20 accepts that such a defect
|
|
may now live in an unpublished draft for weeks. Two things carry that risk in the
|
|
meantime — **`npm run check:claims`, which is unchanged and runs on every build
|
|
and both deploy paths**, and **Pouya reading the copy as it is built**. Neither is
|
|
optional, and neither is a substitute for the cutover pass.
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
npm install
|
|
npm run dev # local dev server
|
|
npm run build # static build to ./dist
|
|
npm run preview # serve ./dist locally
|
|
npm run check # astro check — type and template errors
|
|
npm run check:claims # §4 Forbidden, enforced on dist/ — run it after a build
|
|
npm run lint # eslint + prettier check
|
|
npm run format # prettier — rewrite files in place
|
|
npm run deploy # build + deploy from this machine (see docs/06)
|
|
```
|
|
|
|
## Where things live
|
|
|
|
```
|
|
AGENTS.md living project record — read first, maintain always
|
|
docs/ the specs you build from
|
|
01-architecture.md sitemap, URL map, per-page content outline
|
|
02-design-system.md tokens, type scale, motion, contrast constraints
|
|
03-content-spec.md voice, copy rules, per-page copy deck
|
|
04-seo-spec.md metadata, structured data, sitemap, crawlability
|
|
05-backend-spec.md intake form, Lambda/DynamoDB/SES, booking, PIPEDA
|
|
06-deployment.md S3/CloudFront, Gitea Actions, IAM, cutover checklist
|
|
src/
|
|
content.config.ts content collections — Content Layer API, NOT content/config.ts
|
|
styles/tokens.css design tokens — the single source of colour and scale
|
|
styles/global.css reset, base type, utilities
|
|
layouts/ page shells
|
|
components/ UI components
|
|
pages/ routes (file-based)
|
|
content/insights/ Insights MDX only; the config sits above, not in here
|
|
data/site.ts site-wide constants, nav, contact details
|
|
public/ static assets served as-is
|
|
```
|
|
|
|
## Conventions
|
|
|
|
**Framework.** Astro **7.x**, `output: 'static'` (D1 as amended). Never introduce
|
|
a server runtime without a Change Log entry recording why. The major is pinned
|
|
deliberately — check `npm view astro version` before changing it.
|
|
|
|
**JavaScript.** Default to zero. Reach for an Astro island only when a feature
|
|
genuinely cannot be CSS or progressive HTML. If you add a `client:*` directive,
|
|
say why in the Change Log. A `<details>` element beats a JS accordion.
|
|
|
|
**Styling.** Plain CSS with custom properties. No Tailwind, no CSS-in-JS, no
|
|
utility framework. Every colour, space, and font size comes from a token in
|
|
`tokens.css` — no raw hex values and no magic numbers in component styles.
|
|
|
|
**Accessibility is a build requirement, not a polish pass.** Semantic landmarks,
|
|
one `<h1>` per page, heading levels never skipped, visible focus states, all
|
|
interactive elements reachable by keyboard, `prefers-reduced-motion` honoured on
|
|
every animation. Gold `#c9a876` never sits on cream — it fails contrast at
|
|
2.10:1. See `docs/02-design-system.md`.
|
|
|
|
**Anything a spec makes a claim about must be reachable from the repository.**
|
|
If the artefact lives only in Drive, in a console, or on someone's laptop, no
|
|
reviewer can compare the claim against it and the claim is **unverifiable by
|
|
construction** — not merely unverified. Commit the artefact, or commit a faithful
|
|
extract with its provenance and the command that produced it.
|
|
|
|
This has cost twice. `AGENTS.md` Q24 was the AWS hosting guide, the only record
|
|
of how the infrastructure was hand-built, living outside the repo. Q32 was the
|
|
infinity mark: it was traced from the old site's *loading placeholder*, the
|
|
source comment said so in as many words — and **two adversarial review passes
|
|
still could not catch that the shape was wrong**, because the real artwork was
|
|
not in the repo to compare against. Stating a doubt is not enough when the thing
|
|
that would resolve it is unreachable. Tracked as R14.
|
|
|
|
**A command that did not run is not evidence of absence.** Check that a tool
|
|
exists before trusting its silence, and read exit status, not just stdout. This
|
|
project ran `timeout 60 ls "$DRIVE"` four times, got empty output each time, and
|
|
reported the brand assets unreachable — `timeout` is not installed on macOS, so
|
|
the command had never executed and the directory was fully readable all along.
|
|
Empty output from a command that failed to start looks exactly like empty output
|
|
from a command that found nothing. Same family as *a sweep is a command, not a
|
|
claim*: the claim must rest on output you actually read, from a command that
|
|
actually ran.
|
|
|
|
**And never suppress stderr in a verification script.** *Pouya's convention,
|
|
2026-08-28, from verifying the deploy credential (`AGENTS.md` Q22).* This is the
|
|
rule above from the other direction, and it is the more dangerous direction:
|
|
**`2>/dev/null` converts "it failed" into "it found nothing", and those are
|
|
opposite results.** His first pass at eight `simulate-principal-policy` checks
|
|
returned empty for all eight; the suppression was hiding an `InvalidInput` error,
|
|
and the empty output was then explained with a guess — *"probably lacks the
|
|
permission"* — which is the answer the check was supposed to produce, arrived at
|
|
without the check running. The actual cause was a **zsh parameter-expansion bug**:
|
|
`$ACCT:user/` parses `:u` as a history modifier and yields `327082975128ser/`.
|
|
Braces fixed it.
|
|
|
|
So: no `2>/dev/null` in anything whose output you intend to believe, read the
|
|
exit status, and when a result is empty **remove the suppression and look before
|
|
proposing a cause.** A guessed explanation for an empty result is worse than no
|
|
result, because it closes the question.
|
|
|
|
**And never TRUNCATE the output of a check you intend to believe.** *Added
|
|
2026-08-29, from build step 5.* This is the stderr rule's twin and it is easier
|
|
to commit, because the command runs and the pipe looks harmless. `npm run check`
|
|
prints its verdict as three lines — `- N errors`, `- N warnings`, `- N hints` —
|
|
followed by a blank line. **`npm run check 2>&1 | tail -3` therefore returns
|
|
warnings, hints and the blank line, and silently drops the errors line.** It was
|
|
run four times that way and reported as passing each time; `astro check` was
|
|
exiting **1 with 10 type errors**, and both deploy paths run it before the build,
|
|
so nothing could have shipped. `adversarial-reviewer` found it.
|
|
|
|
The fix is not a bigger `tail`. **Read the exit status** — `cmd; echo "exit=$?"`
|
|
or `cmd || echo FAILED` — because it is the one signal a pipe cannot silently
|
|
reshape. `head`, `tail`, `grep -c` and `| grep -i error` all have the same
|
|
failure mode: they turn a verdict you did not read into a verdict you assert.
|
|
Same family as *a sweep is a command, not a claim*, and note the asymmetry that
|
|
makes it dangerous — the truncation only ever hides the bad news, because the
|
|
error line comes first.
|
|
|
|
*Corroborated the same day, twice, in the same session and both in zsh:*
|
|
`grep -rn $EX 'Mediator-Arbitrator'` printed an option error and no matches —
|
|
which reads as clean — because zsh does not word-split unquoted variables; and a
|
|
digest-comparison loop using `set -- $pair` printed **`DIFFER` on all five rows**,
|
|
which reads as "the source changed under me", because the loop body received one
|
|
argument and the comparison never ran. Prefer `git grep`, quote or array-expand
|
|
anything you pass as flags, and re-check any result whose shape is "uniformly
|
|
bad".
|
|
|
|
**And re-check "uniformly GOOD" too — that is the dangerous half.** *Added
|
|
2026-08-30; sharpened on Pouya's instruction 2026-08-31, as "the sharpest
|
|
instrument finding yet".* The same `set -- $pair` loop recurred while confirming
|
|
nine restored files matched a saved copy, and this time it printed **`same` on
|
|
all nine**: `shasum` was handed both filenames as one argument, errored, and left
|
|
both variables empty, so `"" = ""` passed.
|
|
|
|
**The distinguishing property, and it is the whole rule: a broken verification
|
|
that fails loudly is safe; one that passes uniformly is not.** `DIFFER` on every
|
|
row announces itself — it is alarming, so it starts an investigation, and the
|
|
investigation finds the broken loop. A uniform pass is **the result you were
|
|
hoping for, so it ends the check** instead of starting one. The two failures come
|
|
from the identical bug and only one of them is survivable.
|
|
|
|
So a comparison must **assert that both things it compares exist** before
|
|
comparing them — that is the assertion the shell loop skipped, and it is what
|
|
turns this class of bug back into the loud kind. Note the same hole in `git grep`:
|
|
it silently misses untracked files, so a clean sweep across new work means
|
|
nothing until the files are staged.
|
|
|
|
**A parent cannot style a child component's root element.** Astro does not pass
|
|
a parent's scope attribute down, so `<Button class="header-cta" />` compiles the
|
|
parent's rule to `.header-cta[data-astro-cid-<parent>]` while the rendered `<a>`
|
|
carries only `<Button>`'s own cid. **The rule silently never matches** — no
|
|
error, no warning, and the CSS looks correct in the source. Wrap the child in an
|
|
element the parent owns (`<div class="header-cta"><Button …/></div>`), or reach
|
|
it deliberately with `:global()` from a parent-scoped ancestor. Inherited
|
|
properties (`white-space`, `color`, `font-*`) do cross the boundary and are the
|
|
exception. This cost a header CTA that was documented as hidden on mobile,
|
|
was not hidden, and sat 75 px short of the right edge on desktop — both found by
|
|
measurement, neither by reading. It will recur with `PracticeCard`,
|
|
`ArticleCard`, and `Pill`.
|
|
|
|
**Never write the `animation` shorthand beside `animation-timeline`.** Longhands
|
|
only — `animation-name`, `animation-duration`, `animation-timing-function`,
|
|
`animation-fill-mode`, then `animation-timeline` and `animation-range`.
|
|
`scroll()` and `view()` are not legal components of the shorthand, and Lightning
|
|
CSS folds the two declarations together on minify into something invalid, which
|
|
is then discarded whole. **It works in `npm run dev` and is dead in
|
|
`npm run build`** — the worst shape a defect can take. It happened twice in one
|
|
session, the second time inside the fix for the first. `/build` Phase 5 greps
|
|
`dist` for it; do not remove that check.
|
|
|
|
**Images.** Astro `<Image>` with explicit width and height. AVIF/WebP with
|
|
fallback. Never base64-inline an image into HTML — the old site did this with
|
|
~1 MB of logo PNGs — a figure `AGENTS.md` Q34 is now open against, so treat the
|
|
rule as standing on its own merits rather than on that number.
|
|
|
|
**Fonts.** Self-hosted, subset, `font-display: swap`, preloaded. No Google Fonts
|
|
request at runtime — it costs a round trip and adds a third-party call to a
|
|
page that collects legal inquiries.
|
|
|
|
**Every page ships with:** a unique `<title>` and meta description, a canonical
|
|
URL, Open Graph and Twitter card tags, and appropriate JSON-LD. See
|
|
`docs/04-seo-spec.md`. A page without these is not finished.
|
|
|
|
**A version pin is verified against the registry, never recalled.** Before you
|
|
write or change any dependency version, run `npm view <pkg> version` and pin
|
|
against what it returns. One second of checking; a stale pin costs a migration.
|
|
This rule exists because `astro: "^5.0.0"` was written from memory and was
|
|
**two majors stale on the day it was written** — which meant shipping a
|
|
framework carrying high-severity XSS advisories. The same check applies to
|
|
every pin in `package.json`, not just the framework.
|
|
|
|
Re-check currency at each phase boundary in the build order (`AGENTS.md` R11),
|
|
not only when something breaks.
|
|
|
|
**`AGENTS.md` §7 is the single source of truth for operational facts.** Resource
|
|
IDs, regions, DNS records, credential state, service status — these live in §7
|
|
and nowhere else. Specs in `docs/` **cite** §7; they do not restate it. Write
|
|
"the region `AGENTS.md` §7 records", not the region. Same for bucket names,
|
|
distribution IDs, DKIM tokens, endpoints, and account identifiers.
|
|
|
|
A duplicated fact is a fact that will eventually be wrong in one place, and the
|
|
copy that goes stale is the one nobody re-reads. This rule exists because
|
|
`docs/05-backend-spec.md` carried its own copy of the SES DKIM table, a
|
|
correction reached §7 and never reached it, and the stale copy ended up telling
|
|
an operator to delete the three records that authenticate outbound mail —
|
|
under the heading "Never delete".
|
|
|
|
**A measurement is a claim about your instrument until you check the
|
|
instrument.** This has now cost six times, and the shape is identical every
|
|
time: a number that looks like a finding, from a probe nobody validated.
|
|
|
|
- `timeout 60 ls "$DRIVE"` — **the command never ran.** `timeout` is not
|
|
installed on macOS. Empty output from a command that failed to start looks
|
|
exactly like empty output from a command that found nothing, and it produced a
|
|
report that the brand assets were unreachable when the directory was fully
|
|
readable.
|
|
- **`1.23:1` for the traced mark** — the bounding box of the path's *coordinate
|
|
hull*, not of the curve. A cubic's control points sit outside it, so the box
|
|
was 33% too tall while giving the *correct* width — which means the obvious
|
|
sanity check, "does the width look right?", passes.
|
|
- **"the mark renders at 24px"** — the harness reported the worst-deviating
|
|
instance on the page, not the instance under discussion, which was exact.
|
|
- **"0 overflow at every width"** — true, and it measured the *document*. A flex
|
|
child was absorbing the deficit by being crushed to aspect 0.891. **Measure
|
|
the elements, not only the page.**
|
|
- **`img.naturalWidth` = 64 at DPR 1, 2 and 3** — which reads as *the density
|
|
ladder is not being generated at all*, a shipped defect on every page. It is
|
|
**density-corrected by spec**: a 192px file selected at `3x` correctly reports
|
|
64. The files on disk were 64 / 128 / 192 all along.
|
|
- **"10 distinct contexts" from `grep -roh '.\{50\}X.\{50\}' dist/ | sort -u`**
|
|
— **`grep -o` takes NON-OVERLAPPING matches.** On minified HTML a page is a
|
|
handful of very long lines, so an early window eats the characters a later one
|
|
needs and occurrences vanish silently. A whole shipped sentence was missing
|
|
from the list. **A `grep -o` window count is not an enumeration** — to count
|
|
occurrences of a string, iterate every match position, or `grep -o` the bare
|
|
string with no context window.
|
|
|
|
So before acting on a number: say what it is a number *of*; confirm the command
|
|
actually ran and read its exit status; and check it against a second method that
|
|
cannot fail the same way — the bytes on disk, a screenshot, a hit test.
|
|
|
|
**And a grep that matches is not a finding until you read what it matched.**
|
|
A case-insensitive sweep for `LSO` hit `I aLSO practise`; a superlative sweep for
|
|
`leading` hit `the pLEADINGs`. Both on the same page on the same day. Print the
|
|
match with context before you believe it.
|
|
|
|
**Never name an Astro prop `as`.** `const { as = 'p' } = Astro.props` detaches
|
|
the `Props` interface from the component, and **every call site silently stops
|
|
being type-checked.** `astro check` reports it only as `ts(6196) 'Props' is
|
|
declared but never used`, which reads like lint noise. Measured: with the prop
|
|
named `as`, `<Eyebrow dot as="h9" bogusProp={1} />` compiled with **0 errors**;
|
|
renaming the one identifier to `tag` made the same probe fail correctly. **Do not
|
|
silence a `ts(6196)` with `Astro.props as Props`** — that hides the warning and
|
|
leaves the call sites unchecked, which is strictly worse. If that hint appears on
|
|
any component, pass it a bogus prop before believing its props are checked.
|
|
|
|
**A sweep is a command, not a claim.** Any statement that a change was applied
|
|
across files — a phrase removed everywhere, a path updated everywhere, a
|
|
decision swept through the docs — must cite the command that proves it, and be
|
|
written only after reading that command's output. Paste the `grep` into the
|
|
Change Log entry. Three consecutive entries on this project asserted a completed
|
|
sweep; instances survived all three, and one of them was inside
|
|
`.claude/agents/claims-auditor.md` — the definition of the agent whose job is to
|
|
catch exactly that. Recall is not evidence.
|
|
|
|
**And sweep the VOCABULARY, not only the subject.** *Added 2026-08-30, from the
|
|
Q.Arb amendment.* `git grep 'Q.Arb'` is line-anchored, so it could not find **ten
|
|
lines in `docs/03` that were entirely about Q.Arb and never named it** — an
|
|
unstruck, imperative block still instructing the struck form, eleven lines below
|
|
that change set's own strike notice on the same bullet. The sweep was a real
|
|
command and its output was read honestly. It was still the wrong command.
|
|
|
|
So after sweeping the term, sweep the words its claims are **made of** — here,
|
|
the stage vocabulary (`commenced`, `in progress`, `pathway`, `not yet`) with no
|
|
mention of the designation. This is R8's sharpest edge, and it is the one that
|
|
survives an honest reader: a sweep can pass every test in the rule above and
|
|
still miss everything, because the anchor you chose is not the anchor the text
|
|
uses. The same session also excluded `docs/reference/` as "sourced extracts" —
|
|
half right. The quotations there are evidence; **the commentary around them is
|
|
this repository's voice**, and three lines of it still asserted the struck row.
|
|
|
|
|
|
**`check:claims` IS FROZEN. It is a tripwire, not a program.** *Pouya's ruling,
|
|
2026-08-30.* Round 2 of the Q.Arb amendment found **five defects in round 1's own
|
|
fixes to that script, two of which made it worse than before the pattern
|
|
existed** — a dedup key that reported two breaches of the same string as one (the
|
|
check truncating its own output), and a collapsed-text view whose window leapt
|
|
paragraph boundaries onto approved copy while its comment claimed it could not.
|
|
At that point it was generating defects at roughly the rate it caught them.
|
|
|
|
The rule, and it has no exceptions:
|
|
|
|
- **A pattern is added only after a real breach has reached `dist/`.** Never
|
|
speculatively, never to close a gap you can imagine.
|
|
- **Each addition ships with a probe** — an injected page proving it catches the
|
|
actual breach — **and a negative fixture** proving it stays silent on the
|
|
approved copy nearest to it.
|
|
- **No refactors. No coverage improvements. No tidying.** If a pattern is wrong,
|
|
change that pattern deliberately, with a Change Log entry. Do not rewrite the
|
|
scanner around it.
|
|
|
|
Under D20 this script is the only per-step claims control, which is an argument
|
|
for keeping it **correct**, not for growing it. It catches the §4 breaches that
|
|
are greppable and makes no claim about the ones that are not.
|
|
|
|
**Comments record decisions, not history — D19.** *"X because D13"* stays.
|
|
*"This was Y, then flagged, then became X"* belongs in the `AGENTS.md` Change
|
|
Log, which is where a reader looks for how something got here. **A comment
|
|
longer than the code it explains must justify itself. Trim on sight.**
|
|
|
|
Pouya's ruling, 2026-08-28, on his own measurement: **342 lines added to `src/`
|
|
in one session for 8 functional lines**, and four of that session's review
|
|
findings were stale statements living inside those comments. A comment that
|
|
narrates its own revision history becomes a second record to keep true, and then
|
|
a source of defects about the record rather than about the site.
|
|
|
|
This does **not** license deleting a comment that carries a live constraint. The
|
|
parent-scope trap, the `animation-timeline` minifier defect and the `as`-prop
|
|
hazard above are load-bearing and stay. The test is whether a future reader needs
|
|
it **to avoid breaking something** — not whether it is interesting.
|
|
|
|
**Commits.** Conventional Commits (`feat:`, `fix:`, `docs:`, `chore:`, `refactor:`).
|
|
One logical change per commit. Never commit secrets, `.env` files, or AWS
|
|
credentials. Gitea is not an AWS OIDC provider, so the deploy key is designed as
|
|
a static IAM access key to be held in Gitea Actions secrets — whether it has
|
|
actually been provisioned is `AGENTS.md` Q22. It must never reach the repo.
|
|
|
|
**Performance budget.** Lighthouse ≥ 95 on all four categories, on mobile, for
|
|
every page. Under 100 KB of JS on any route. LCP under 2.0 s on a simulated
|
|
Slow 4G connection. Treat a budget breach as a failing build.
|
|
|
|
**Lighthouse cannot currently be run.** `@lhci/cli` was removed on 2026-08-26
|
|
(it carried 7 high-severity advisories, `0.15.1` is `latest`, and it had no
|
|
pages and no `lighthouserc` to work with). The budget stands; the instrument is
|
|
missing. It is re-added at build step 7 under `AGENTS.md` R11 — with a freshly
|
|
verified pin, not on the assumption that `0.15.1` is still the ceiling. **Say
|
|
"not run — tool unavailable" rather than silently omitting it.** A documented
|
|
control that no longer exists is precisely the defect Q22 turned out to be.
|
|
|
|
## What "done" means for a page
|
|
|
|
- [ ] Copy written from `docs/03-content-spec.md`, every claim traceable to `AGENTS.md` §4
|
|
- [ ] No `TODO(pouya)` left unlogged in §9
|
|
- [ ] Unique title, meta description, canonical, OG/Twitter tags, JSON-LD
|
|
- [ ] Semantic HTML; keyboard navigable; reduced-motion honoured
|
|
- [ ] Lighthouse ≥ 95 mobile, all four categories — **UNAVAILABLE until step 7**
|
|
(see the performance budget above). Report it as not run; do not tick it
|
|
- [ ] Renders correctly with JavaScript disabled
|
|
- [ ] `AGENTS.md` Change Log entry appended
|