@piercebarney/whs-eleventy 2026.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/cli.js +55 -0
- package/lib/_project.js +42 -0
- package/lib/a11y.js +178 -0
- package/lib/check-links.js +288 -0
- package/lib/compliance.js +677 -0
- package/lib/content-check.js +45 -0
- package/lib/doctor.js +416 -0
- package/package.json +44 -0
- package/scripts/bundle-standard.js +35 -0
- package/standard/CHANGELOG.md +625 -0
- package/standard/core.md +1493 -0
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Slug-keyed. A change to a shared concept (a `core.md` chapter) lists the slug so
|
|
4
|
+
"when did the deploy philosophy last change, and which docs moved" is answerable
|
|
5
|
+
at a glance. Stack-only changes list the file.
|
|
6
|
+
|
|
7
|
+
Format: `## <date>` → `### core: <slug>` / `### <stack>` entries.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 2026-09-01 — the shared tooling package (#compliance mechanism, eleventy)
|
|
12
|
+
|
|
13
|
+
### stacks/eleventy-netlify.md
|
|
14
|
+
|
|
15
|
+
- **`@piercebarney/whs-eleventy` — the `whs` CLI.** The generic eleventy checks
|
|
16
|
+
(compliance sweep, doctor, link/CSP, a11y, content-check) move out of every
|
|
17
|
+
project's `scripts/` into one shared package (`packages/whs-eleventy/`).
|
|
18
|
+
Projects add it as a `devDependency` and run `whs compliance` / `whs links` /
|
|
19
|
+
… ; a fix lands once and `npm update` carries it. Only the asset *generators*
|
|
20
|
+
(`scripts/og*.js`, `scripts/icons.js`, `og-card.js`) stay per-project. The
|
|
21
|
+
`#the-gate` `package.json` snippet, `#compliance`, `#a11y` (now axe-core via
|
|
22
|
+
headless Chrome, not `pa11y`), `#audit-page`, the file tree, and the
|
|
23
|
+
new-project + migration checklists are updated to match.
|
|
24
|
+
- The drift check no longer needs the `~/.claude/standards` path: the package
|
|
25
|
+
bundles a `core.md` + `CHANGELOG.md` snapshot (overridable with
|
|
26
|
+
`WHS_STANDARD`), and reports a visible `MANUAL` row — never a silent pass —
|
|
27
|
+
when the standard can't be resolved. Its CHANGELOG slug parser now reads every
|
|
28
|
+
slug on a multi-slug `### core: …` line.
|
|
29
|
+
|
|
30
|
+
### tooling
|
|
31
|
+
|
|
32
|
+
- **`bin/set-version <YYYY-MM-DD>`** bumps every version string the standard
|
|
33
|
+
carries — `**Version:**` (`core.md` + 3 stack docs), `index.json` `version`,
|
|
34
|
+
the `#adopting` block pin, each `templates/*/CLAUDE.md` `standard-version`, and
|
|
35
|
+
`packages/*/package.json` `version` (the date dotted, `2026-09-01` →
|
|
36
|
+
`2026.9.1`) — in one shot. `bin/check`'s fifth check fails if they disagree.
|
|
37
|
+
- **`.github/workflows/publish.yml`** publishes `@piercebarney/whs-eleventy` to
|
|
38
|
+
npm when a `YYYY-MM-DD` version tag is pushed — it lints + tests the package,
|
|
39
|
+
checks the tag date (dotted) matches `package.json`, and publishes unless that
|
|
40
|
+
version is already up. Needs the `@piercebarney` npm scope and an `NPM_TOKEN`
|
|
41
|
+
repo secret. `--provenance` is omitted (needs a public source repo).
|
|
42
|
+
- `standard.yml` gains a `package` job (prettier + eslint + `node:test`); the
|
|
43
|
+
`templates` job installs the file: package's own tree and points
|
|
44
|
+
`WHS_STANDARD` at the checkout so drift is checked against the live standard.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 2026-08-31 — the Cowork ↔ Claude Code loop: concept brief + content ops (#content-model)
|
|
49
|
+
|
|
50
|
+
### core: adopting
|
|
51
|
+
|
|
52
|
+
- Stack-selection **Q1** no longer offers SvelteKit as a selectable framework.
|
|
53
|
+
It is one of: Eleventy + Netlify *(default)* · Elixir/Phoenix *(database /
|
|
54
|
+
server-side state in scope)* · other. `stacks/sveltekit.md` is now a
|
|
55
|
+
**parked** stub — friction points and open questions written, per-section
|
|
56
|
+
"how" not — and the flow says to treat SvelteKit as "other" until a real
|
|
57
|
+
project forces those decisions. `README.md` doc map + flow and `index.json`
|
|
58
|
+
(`status: parked`, reworded warning) follow; the sveltekit STATUS banner
|
|
59
|
+
says PARKED. No `**Version:**` change (same day).
|
|
60
|
+
|
|
61
|
+
### core: content-model
|
|
62
|
+
|
|
63
|
+
- The "non-developer editing" spec gains an **agent-editing** clause: where an
|
|
64
|
+
LLM agent edits the content data files directly, it follows a documented
|
|
65
|
+
per-project content-ops protocol — what it may change (content, not code), a
|
|
66
|
+
pre-commit content check (the content-relevant slice of the gate), a
|
|
67
|
+
`content:` commit convention. Header + block-template pin → `2026-08-31`.
|
|
68
|
+
|
|
69
|
+
### stacks/eleventy-netlify.md
|
|
70
|
+
|
|
71
|
+
- `#content-model` gains the mechanism: a project-root **`CONTENT.md`** (the
|
|
72
|
+
content set, the commit convention, "deploys are a human step") and
|
|
73
|
+
**`scripts/content-check.js`** / `npm run content-check` — `lint → validate →
|
|
74
|
+
build → links` (no browser, no `test`; a11y backstopped by `bin/deploy`),
|
|
75
|
+
plus a warning when the working tree has changes outside the content set.
|
|
76
|
+
`compliance.js`'s `content-model` row reports whether the protocol is
|
|
77
|
+
present. Header → `2026-08-31`.
|
|
78
|
+
- `#internal-links`: the glossary is labelled **opt-in** — the baseline now has
|
|
79
|
+
no glossary wiring at all. `src/_data/glossary.js` (empty stub) and the no-op
|
|
80
|
+
`glossify` filter are removed; `explainer.njk` drops `| glossify`;
|
|
81
|
+
`check-links.js` wraps the `glossary.js` require in `try/catch` so the
|
|
82
|
+
`/glossary/#slug` check activates only when the add-on ships the file. The
|
|
83
|
+
`glossary` add-on gains an `explainer.njk` and its README/config-snippet stop
|
|
84
|
+
referring to a baseline no-op.
|
|
85
|
+
|
|
86
|
+
### stacks/phoenix.md · sveltekit.md
|
|
87
|
+
|
|
88
|
+
- `#content-model` each gets a one-line agent-editing note (`CONTENT.md` + a
|
|
89
|
+
`content.check` alias) — `TODO`, pointing at the eleventy pattern. Headers →
|
|
90
|
+
`2026-08-31`.
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
### templates/concept-brief.md (new)
|
|
95
|
+
|
|
96
|
+
- A stack-agnostic fill-in brief that ideation (Cowork / a Claude chat)
|
|
97
|
+
produces and Claude Code builds from. Sections map to concrete output: **the
|
|
98
|
+
idea** → home intro / about / meta descriptions; **House style** (a fenced
|
|
99
|
+
`- key: value` block) → copied into the project's `CLAUDE.md`, answers the
|
|
100
|
+
`#adopting` five questions; **Brand** (concept-level — "muted teal, calm, a
|
|
101
|
+
leaf mark") → `src/_data/brand.js`, verified AA on `/audit/`; **Content**
|
|
102
|
+
(collection name, extra fields, 3–5 seed entries) → the content model +
|
|
103
|
+
schema + rendered collection; **Pages & layout** → the templates;
|
|
104
|
+
**Add-on packs** (checklist) → which `add-ons/<name>/` to apply; **Meta** →
|
|
105
|
+
name / pkg / currency / owner.
|
|
106
|
+
|
|
107
|
+
### templates/eleventy-netlify/bin/init · new-project.sh
|
|
108
|
+
|
|
109
|
+
- `bin/init --brief <path>`: reads the brief's House-style block + Meta (no
|
|
110
|
+
deps — a line parser), writes the House-style answers into `CLAUDE.md`
|
|
111
|
+
(dropping the decision-menu comments), and takes name / package / URL /
|
|
112
|
+
currency from Meta + the block; prompts only for what the brief left blank.
|
|
113
|
+
- `new-project.sh <stack> <dest> --brief <path>` resolves a relative brief
|
|
114
|
+
path before `cd`-ing, forwards it, and errors with a checklist pointer when
|
|
115
|
+
the stack has no template.
|
|
116
|
+
|
|
117
|
+
### docs — adopting pointer
|
|
118
|
+
|
|
119
|
+
- `core.md#adopting` / `README.md` / `TEMPLATE.md` gain a pointer: the filled
|
|
120
|
+
brief is the recommended input to the new-project flow. No contract or spec
|
|
121
|
+
change of its own — the version move above is the content-ops clause. An
|
|
122
|
+
existing project re-checks `#adopting` and finds nothing to do: the brief is
|
|
123
|
+
new-project tooling.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## 2026-08-30 — a copy-and-modify template for Eleventy + Netlify (#adopting)
|
|
128
|
+
|
|
129
|
+
### templates/eleventy-netlify (new)
|
|
130
|
+
|
|
131
|
+
- A copy-and-modify starter for the Eleventy + Netlify stack, co-located at
|
|
132
|
+
`templates/eleventy-netlify/`, versioned / CHANGELOG'd / consistency-checked
|
|
133
|
+
with the docs. Its structure was lifted from a live compliant project; from
|
|
134
|
+
here it is maintained **against the standard** (`core.md` +
|
|
135
|
+
`stacks/eleventy-netlify.md`, verified by `templates/verify.sh`), not synced
|
|
136
|
+
from any project. `npm run check` and `npm run compliance` pass on commit 1
|
|
137
|
+
(`28 pass · 0 fail`), so a new project starts at "fill in brand + content".
|
|
138
|
+
- **Baseline:** the always-true compliant skeleton — the generic
|
|
139
|
+
tooling/config/scripts verbatim, the GWP files with `__PKG_NAME__` /
|
|
140
|
+
`__SITE_NAME__` / `https://example.com` / `USD` placeholders, a neutral
|
|
141
|
+
`guides` content model with 3 sample entries, and one example client-logic
|
|
142
|
+
module. An obviously-placeholder magenta brand (still AA) so nothing
|
|
143
|
+
un-branded ships by accident.
|
|
144
|
+
- **`add-ons/<name>/`** — opt-in packs mirroring the opt-in chapters:
|
|
145
|
+
`ads` (`#ads`), `ai-proxy` (`#llm-integration` — a worked Netlify Function
|
|
146
|
+
proxy + a pure tested transform), `feed` (article feeds, via
|
|
147
|
+
`@11ty/eleventy-plugin-rss@3`'s `feedPlugin`), `glossary`
|
|
148
|
+
(`#internal-links`), `calculator` (`#client-logic` / `#domain-tests`). Each
|
|
149
|
+
is a folder + a numbered copy-in README. All five smoke-tested against a
|
|
150
|
+
fresh init'd project (`ai-proxy`'s model call excepted — no key in CI).
|
|
151
|
+
- **`bin/init`** (Node, no deps) substitutes the placeholders, installs,
|
|
152
|
+
commits, and runs the gate, then deletes the template-meta files.
|
|
153
|
+
`templates/new-project.sh` wraps copy + `git init` + `bin/init`.
|
|
154
|
+
- **`templates/verify.sh`** runs each template's `check` + `compliance
|
|
155
|
+
--strict` and fails on `standard-version` drift. **`bin/check`** runs the
|
|
156
|
+
four doc-consistency checks (CORE-slug · slug-set mirror · no restatement ·
|
|
157
|
+
sterile). **`.github/workflows/standard.yml`** runs both on every push / PR
|
|
158
|
+
/ weekly — the weekly run is the drift + supply-chain alarm. (The stray
|
|
159
|
+
nested `templates/eleventy-netlify/.github/workflows/template-verify.yml` —
|
|
160
|
+
inert, since GitHub only reads root workflows — is removed.)
|
|
161
|
+
- `scripts/compliance.js` + `test/compliance.test.js` sweep the Part J
|
|
162
|
+
chapters (`llm-integration` / `agent-artifacts` / `usage-metering`) — they
|
|
163
|
+
were missing from the seed (it predated 2026-08-29) despite the
|
|
164
|
+
`2026-08-30` pin. `N/A` for a static site unless `ai: llm-api`. The
|
|
165
|
+
`stacks/eleventy-netlify.md#compliance` PASS-criteria table gains the row.
|
|
166
|
+
- `compliance.js` `#ci-cd` no longer depends on the **live** `core.hooksPath`
|
|
167
|
+
state — that's "`npm install` has run here", not a codebase property, so a
|
|
168
|
+
fresh clone stays compliant. `doctor` still surfaces it on `/audit/` as a
|
|
169
|
+
warn. The template's `prepare` script is guarded to no-op unless it's the
|
|
170
|
+
repo root, so `npm ci` in a template checked out inside another repo (this
|
|
171
|
+
one) doesn't touch the outer git config. `#ci-cd` gains a one-liner.
|
|
172
|
+
|
|
173
|
+
### core: adopting
|
|
174
|
+
|
|
175
|
+
- New **"Starting point"** paragraph after the stack-selection flow: where a
|
|
176
|
+
`templates/<stack>/` exists, starting from it *is* the new-project path; the
|
|
177
|
+
impl doc's checklist is the by-hand equivalent and the annotated inventory.
|
|
178
|
+
Stack-agnostic wording — only `eleventy-netlify` has one today. Header version
|
|
179
|
+
and the block-template `standard-version` pin → `2026-08-30`.
|
|
180
|
+
|
|
181
|
+
### stacks/eleventy-netlify.md
|
|
182
|
+
|
|
183
|
+
- `#adopting`: a **"Fastest path — start from the template"** lead-in above the
|
|
184
|
+
new-project checklist (`new-project.sh` → `bin/init` → `brand.js` + content →
|
|
185
|
+
`npm run deploy`); the 11-step checklist kept verbatim as the by-hand path /
|
|
186
|
+
inventory. `#ads` and `#llm-integration` each gain a one-line pointer to their
|
|
187
|
+
`add-ons/<name>/` pack. Header version → `2026-08-30`.
|
|
188
|
+
- `#styling` (`pico-classes`) + `#brand-source`: `src/tokens.css.njk` maps
|
|
189
|
+
`brand.js`'s core palette (primary, background, text, danger) onto the
|
|
190
|
+
matching `--pico-*` vars, so a `brand.js` colour change reaches the **live
|
|
191
|
+
CSS** — not just re-pinned hexes Pico already has. The template does this;
|
|
192
|
+
the earlier pattern of pinning a Pico theme's hexes into `brand.js` left the
|
|
193
|
+
live CSS unchanged and is a `#brand-source` gap. Pico's build still supplies
|
|
194
|
+
the derived neutrals.
|
|
195
|
+
|
|
196
|
+
### stacks/phoenix.md · sveltekit.md
|
|
197
|
+
|
|
198
|
+
- `#adopting`: a one-line "no starter template for this stack yet — follow the
|
|
199
|
+
checklist; `stacks/eleventy-netlify.md` has the reference template". Header
|
|
200
|
+
versions → `2026-08-30`.
|
|
201
|
+
|
|
202
|
+
### README.md
|
|
203
|
+
|
|
204
|
+
- Doc-map gains a `templates/` row. A "Starting a new project" paragraph in
|
|
205
|
+
"How to use it". "Home" notes the templates ship in-repo and a remote unlocks
|
|
206
|
+
`degit`. Consistency checks gain `templates/verify.sh` as the 4th check.
|
|
207
|
+
"Proposing changes" gains the template-sync rule (update the template in the
|
|
208
|
+
same change as a mirrored-section edit, re-pin, run `verify.sh`).
|
|
209
|
+
|
|
210
|
+
### index.json
|
|
211
|
+
|
|
212
|
+
- Each framework gains a `template` field (`templates/eleventy-netlify/` or
|
|
213
|
+
`null`). `version` → `2026-08-30`.
|
|
214
|
+
|
|
215
|
+
### not a standard-content change
|
|
216
|
+
|
|
217
|
+
- No CORE contract changed. The `#adopting` edit adds a path, not a rule; the
|
|
218
|
+
`stacks/eleventy-netlify.md` `#styling` / `#brand-source` / `#compliance`
|
|
219
|
+
edits clarify existing mechanism; the version bump is for the template pin +
|
|
220
|
+
the doc-set additions above.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 2026-08-30 — the standard is a git repo
|
|
225
|
+
|
|
226
|
+
### repo
|
|
227
|
+
|
|
228
|
+
- Consolidated the two loose copies (the canonical set under
|
|
229
|
+
`~/.claude/standards/web-house-style/` and a stale pre-`#compliance` copy at
|
|
230
|
+
`~/Dev/web-house-style/`) into one git-tracked project at
|
|
231
|
+
`~/Dev/web-house-style/`, default branch `main`. `~/.claude/standards/web-house-style/`
|
|
232
|
+
is now a symlink to it, so every existing reference resolves unchanged.
|
|
233
|
+
- `README.md` "Provisional home" → "Home", updated for the repo layout and the
|
|
234
|
+
remaining deferred items (global `CLAUDE.md` pointer, a GitHub remote,
|
|
235
|
+
existing-project migration).
|
|
236
|
+
- No standard-content change — versions and slugs untouched.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## 2026-08-29 — AI & agent capabilities, opt-in (#llm-integration, #agent-artifacts, #usage-metering)
|
|
241
|
+
|
|
242
|
+
### core: llm-integration · agent-artifacts · usage-metering (new chapters)
|
|
243
|
+
|
|
244
|
+
- New **Part J — AI & agent capabilities (opt-in)**, three chapters, inserted
|
|
245
|
+
before the old Part J (Meta), which becomes **Part K**. All three are
|
|
246
|
+
need-gated the way `#beyond-this-standard` and `#ads` are — opt-in per project,
|
|
247
|
+
**not** a consequence of the framework. A project that calls no model API is
|
|
248
|
+
unaffected (all three `N/A`).
|
|
249
|
+
- **`#llm-integration`** — in scope whenever the project calls a model
|
|
250
|
+
provider's API at all. Server-side-only key handling per environment
|
|
251
|
+
(extends `#secrets`), explicit timeout + capped-backoff retry + an app-side
|
|
252
|
+
per-user request ceiling, per-request token/cost telemetry + spend-anomaly
|
|
253
|
+
alert (extends `#observability`), a moderation/safety layer sized to the
|
|
254
|
+
subject's risk (three tiers), a plain visible AI disclosure to end users, and
|
|
255
|
+
a pre-launch provider-usage-policy fit check with a recorded date. The
|
|
256
|
+
response transform is a pure tested function (`#domain-tests`).
|
|
257
|
+
- **`#agent-artifacts`** — in scope only when users generate downloadable files
|
|
258
|
+
via model-driven code execution. Sandboxed execution (no route to secrets /
|
|
259
|
+
DB / network), object-storage with an enforced TTL, authorization-checked
|
|
260
|
+
delivery with defensive `Content-Type` / `Content-Disposition` / `nosniff` /
|
|
261
|
+
CSP, per-artifact cost as its own `#observability` signal.
|
|
262
|
+
- **`#usage-metering`** — in scope only when pricing isn't flat. An immutable
|
|
263
|
+
per-subject event log, server-side pre-spend plan-tier enforcement, a
|
|
264
|
+
scheduled reconciliation job against the payment processor (idempotent usage
|
|
265
|
+
reporting, webhook-driven tier state), a user-facing usage view from the same
|
|
266
|
+
rows, tested partial-failure paths, tested allowance boundaries.
|
|
267
|
+
- Threaded one-liners into `#third-parties` (a model provider is a declared third
|
|
268
|
+
party; key-bearing → server-side), `#privacy` (sending end-user content to a
|
|
269
|
+
provider is a privacy-page entry; use the no-train/zero-retention mode),
|
|
270
|
+
`#secrets` (model-provider key = per-env, spend-capped, scoped, rotatable
|
|
271
|
+
without redeploy), `#observability` (per-request token/cost + reconciliation
|
|
272
|
+
drift join the loop), `#beyond-this-standard` (an LLM/agent surface is its own
|
|
273
|
+
opt-in axis; a single stateless call is a serverless function, streaming /
|
|
274
|
+
artifacts / metering are past the envelope), `#compliance` (the Part J chapters
|
|
275
|
+
are `N/A` when `ai: none`), `#out-of-scope` (ML infrastructure — fine-tuning,
|
|
276
|
+
RAG retrieval layer, evals harness, prompt-versioning — is not covered),
|
|
277
|
+
`#anti-patterns` (six new entries).
|
|
278
|
+
- `#adopting`: a **5th** stack-selection question ("Does the project call an
|
|
279
|
+
LLM / agent API?", default No, with the `+artifacts` / `+metering` follow-ups);
|
|
280
|
+
`ai: none` added to the `CLAUDE.md` block template. The example block's
|
|
281
|
+
`standard-version` pin and the doc header move to `2026-08-29`.
|
|
282
|
+
- Slug registry gains `llm-integration` · `agent-artifacts` · `usage-metering`
|
|
283
|
+
(after `observability`).
|
|
284
|
+
|
|
285
|
+
### stacks/phoenix.md
|
|
286
|
+
|
|
287
|
+
- Three new mirrored sections (the concrete "how" this change is grounded in):
|
|
288
|
+
`#llm-integration` — a context module + `Req` with explicit timeout, an Ash
|
|
289
|
+
`Conversation` / `Message` resource for chat state, LiveView streamed assigns
|
|
290
|
+
for token streaming, a `LLM.Usage` Ash resource written per call, `Oban` for
|
|
291
|
+
async summarisation/metering, a `Moderation` pass, the provider origin in the
|
|
292
|
+
`ThirdParties` manifest; `#agent-artifacts` — an Oban job driving a sandboxed
|
|
293
|
+
runner (a separate container / microVM, never the app node), R2/S3 storage with
|
|
294
|
+
a TTL + an Oban cron sweeper, an authorization-checked `ArtifactController`
|
|
295
|
+
download with defensive headers; `#usage-metering` — a `UsageEvent` immutable
|
|
296
|
+
Ash resource, a pre-spend `Ash.Policy` / plan check, an `Oban` reconciliation
|
|
297
|
+
worker against Stripe with idempotent usage records and webhook handlers.
|
|
298
|
+
Adds AI items to the new-project checklist and the migration list; two
|
|
299
|
+
Phoenix-specific anti-patterns.
|
|
300
|
+
|
|
301
|
+
### stacks/eleventy-netlify.md
|
|
302
|
+
|
|
303
|
+
- Three new mirrored sections, all **out-of-scope-with-a-pointer**: a single
|
|
304
|
+
stateless model call belongs in a `netlify/functions/*` proxy (the one
|
|
305
|
+
sanctioned serverless carve-out — key in the Function env, timeout, the origin
|
|
306
|
+
in `_data/thirdparties.js`, the privacy page updated, a visible AI disclosure,
|
|
307
|
+
the transform pure-tested); conversation state, streaming, sandboxed code
|
|
308
|
+
execution, and usage metering are all past `#beyond-this-standard` → use
|
|
309
|
+
`stacks/phoenix.md`. `ai: none` added to the `CLAUDE.md` block template and a
|
|
310
|
+
change-checklist line.
|
|
311
|
+
|
|
312
|
+
### stacks/sveltekit.md
|
|
313
|
+
|
|
314
|
+
- Three new mirrored sections — `TODO` stubs with hints (a `+server.js` endpoint
|
|
315
|
+
or a serverless function for the keyed call under `adapter-static`; anything
|
|
316
|
+
stateful/streamed/metered → `core.md#beyond-this-standard` and the Phoenix
|
|
317
|
+
reference). `ai:` note added to the change checklist.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## 2026-08-28 — codebase-vs-standard conformance (#compliance)
|
|
322
|
+
|
|
323
|
+
### core: compliance (new chapter)
|
|
324
|
+
|
|
325
|
+
- New chapter `#compliance` in Part I, between `#the-gate` and `#ci-cd`.
|
|
326
|
+
Contract: one command reports the codebase's state against **every chapter** —
|
|
327
|
+
`PASS` / `FAIL` / `MANUAL` / `N/A` — as the drift-and-completeness sweep that
|
|
328
|
+
`#the-gate` (regressions only) does not do. It is **not** part of the gate: a
|
|
329
|
+
`FAIL` is a migration gap, not a regression, and gating on it would freeze work
|
|
330
|
+
mid-migration. Specs: walks every CORE chapter + mirrored impl section; summary
|
|
331
|
+
line `N pass · M fail · K manual`; runs on demand and at build time
|
|
332
|
+
(cache-aware) feeding the audit page's Compliance panel; the deploy pre-flight
|
|
333
|
+
warns on any `PASS → FAIL` since the last deploy; a standard-version drift row
|
|
334
|
+
compares the pinned `standard-version` to `core.md`'s version header and emits
|
|
335
|
+
`MANUAL: re-check #<slug>` per CHANGELOG slug changed since. Reuses existing
|
|
336
|
+
machinery (repo introspection + link-check output scan). The assistant runs it
|
|
337
|
+
at onboarding, when the pin is behind, before a multi-chapter release, and when
|
|
338
|
+
asked "are we still in line?".
|
|
339
|
+
- Threaded into `#the-gate` (a bullet: completeness + version drift are
|
|
340
|
+
`#compliance`'s job, not the gate's), `#audit-page` (a Compliance tab),
|
|
341
|
+
`#ci-cd` (production-deploy pre-flight warns on `PASS → FAIL`), `#adopting`
|
|
342
|
+
(the "four questions" fix, a `#compliance` line in the directive, an
|
|
343
|
+
"Onboarding an existing project" paragraph, a Change-checklist item),
|
|
344
|
+
`#observability` (a re-run trigger), `#anti-patterns` (compliance in the gate /
|
|
345
|
+
treating a `FAIL` as a regression, working the backlog without re-running it,
|
|
346
|
+
a stale `standard-version` pin).
|
|
347
|
+
- Slug registry gains `compliance` (after `the-gate`).
|
|
348
|
+
|
|
349
|
+
### stacks/eleventy-netlify.md
|
|
350
|
+
|
|
351
|
+
- New `compliance` mirrored section: `scripts/compliance.js`,
|
|
352
|
+
`"compliance": "node scripts/compliance.js"` in `package.json` (**not** in
|
|
353
|
+
`check`), a per-chapter PASS-criteria table, the MANUAL list, the
|
|
354
|
+
standard-version drift check (`standard-version` in `CLAUDE.md` vs `**Version:**`
|
|
355
|
+
in `core.md` vs `### core:` entries in `CHANGELOG.md`), `_data/infra.js`
|
|
356
|
+
running it cache-aware for the audit-page Compliance tab, `bin/deploy`
|
|
357
|
+
diffing against `.cache/compliance-last.json` and warning on `PASS → FAIL`
|
|
358
|
+
(non-zero exit only under `--strict`). "Compliance" added to the audit-page
|
|
359
|
+
tabs; the House-style directive block, the new-project checklist, and the
|
|
360
|
+
existing-project migration note updated.
|
|
361
|
+
|
|
362
|
+
### stacks/phoenix.md
|
|
363
|
+
|
|
364
|
+
- New `compliance` mirrored section: a `mix whs.check` task (**not** in the
|
|
365
|
+
`check` alias), reusing `mix links.check`'s output scan and the `/audit`
|
|
366
|
+
repo-config introspection; an Ash-flavored per-chapter table; the
|
|
367
|
+
standard-version drift row; `/audit` Compliance tab + `bin/deploy` pre-flight
|
|
368
|
+
wiring.
|
|
369
|
+
|
|
370
|
+
### stacks/sveltekit.md
|
|
371
|
+
|
|
372
|
+
- New `compliance` mirrored section — `TODO` stub with a hint
|
|
373
|
+
(`scripts/compliance.js` or a Vite task over `build/` + repo, the drift row,
|
|
374
|
+
the prerendered `/audit` Compliance tab).
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## 2026-08-28 — the `CLAUDE.md` House-style directive
|
|
379
|
+
|
|
380
|
+
### core: adopting
|
|
381
|
+
|
|
382
|
+
- The recommended `CLAUDE.md` content changed from a bare data block (with one
|
|
383
|
+
passive "read the docs" sub-bullet) to a **`## House style` section**: a
|
|
384
|
+
directive paragraph — *use the standard for all work unless a request
|
|
385
|
+
explicitly says otherwise; check the governing chapter before implementing;
|
|
386
|
+
a conflict with the current code is a migration gap to surface, not route
|
|
387
|
+
around; never silently diverge from a Contract, record deliberate deviations;
|
|
388
|
+
where the standard is silent, do what's idiomatic; run the gate before
|
|
389
|
+
reporting done* — followed by the data block. Verify updated to require both
|
|
390
|
+
parts. Header version → 2026-08-28.
|
|
391
|
+
|
|
392
|
+
### stacks/eleventy-netlify.md · phoenix.md · sveltekit.md
|
|
393
|
+
|
|
394
|
+
- Each `adopting` section now shows its filled-in `## House style` section with
|
|
395
|
+
the gate command substituted (`npm run check` / `mix check`) and any
|
|
396
|
+
stack-specific `CLAUDE.md` prose (Eleventy: the generated-assets note;
|
|
397
|
+
Phoenix: the brand-tokens note) folded in. New-project checklists point at it.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## 2026-08-28 — display advertising (AdSense)
|
|
402
|
+
|
|
403
|
+
### core: ads (new chapter)
|
|
404
|
+
|
|
405
|
+
- New chapter `#ads` in Part G, after `#privacy`. Contract: display ads are
|
|
406
|
+
opt-in per project (`ads:` key in the `CLAUDE.md` block, `none` default);
|
|
407
|
+
consent-gated via a provider-certified CMP; every ad origin declared in the
|
|
408
|
+
manifest; slots carry reserved dimensions (CLS); the loader never runs on a
|
|
409
|
+
non-production build; `ads.txt` is generated/managed; the CSP relaxation is
|
|
410
|
+
the widest it ever gets, scoped to the ad directives, documented, and never
|
|
411
|
+
touches `default-src` or adds `'unsafe-eval'`.
|
|
412
|
+
- Threaded one-liners into `#security-headers` (the ad CSP relaxation),
|
|
413
|
+
`#privacy` (ads always need a CMP + cookies — never privacy-first),
|
|
414
|
+
`#third-parties` (an ad provider is a declared third party), `#noindex` (same
|
|
415
|
+
`isProduction` gate covers ads), `#observability` (ads regress CLS/LCP;
|
|
416
|
+
reserved slots hold CLS), `#out-of-scope` (ad density / editorial),
|
|
417
|
+
`#anti-patterns` (ad loader before consent, ads on non-prod, drifted
|
|
418
|
+
`ads.txt`, unreserved slots, non-certified CMP, `'unsafe-eval'`).
|
|
419
|
+
- `#adopting`: a 4th stack-selection question ("Display advertising?" default
|
|
420
|
+
No); `ads: none` added to the `CLAUDE.md` block template.
|
|
421
|
+
- Slug registry gains `ads`.
|
|
422
|
+
|
|
423
|
+
### stacks/eleventy-netlify.md
|
|
424
|
+
|
|
425
|
+
- New `ads` mirrored section: `_data/ads.js`, `_includes/ad-slot.njk` (wrapper
|
|
426
|
+
div + `<ins class="adsbygoogle">`, no inline `style`), the `push({})` in
|
|
427
|
+
`site.js`, `src/ads.txt.njk`, the `style.css` reserved-height rules, the
|
|
428
|
+
`netlify.toml` CSP fence with a representative AdSense/CMP directive set, the
|
|
429
|
+
`build.isProduction` gate on the CMP + loader + macro, `check-links.js`
|
|
430
|
+
non-prod / `ads.txt` checks, and an `/audit/` **Ads** tab. Google's GDPR
|
|
431
|
+
message named as the low-friction certified CMP.
|
|
432
|
+
- Tree, `noindex`, `security-headers`, `internal-links`, `audit-page`,
|
|
433
|
+
`adopting`, and `anti-patterns` gained ad cross-refs.
|
|
434
|
+
|
|
435
|
+
### stacks/phoenix.md
|
|
436
|
+
|
|
437
|
+
- New `ads` mirrored section: `<App>Web.Ads` module (`enabled?/0` =
|
|
438
|
+
`config_env() == :prod`), `config.exs` holds the ids, `<.ad_slot>` component,
|
|
439
|
+
`/ads.txt` controller, the CSP in the `SecurityHeaders` plug, the LiveView
|
|
440
|
+
nonce vs `'unsafe-inline'` note. `noindex` / `audit-page` / `anti-patterns`
|
|
441
|
+
cross-refs.
|
|
442
|
+
|
|
443
|
+
### stacks/sveltekit.md
|
|
444
|
+
|
|
445
|
+
- New `ads` mirrored section (skeleton + hints): `src/lib/ads.js`, the loaders in
|
|
446
|
+
`+layout.svelte` gated on `import.meta.env.PROD` + a consent store, an
|
|
447
|
+
`<AdSlot>` component, `/ads.txt` as a `+server.js` route, the `svelte.config.js`
|
|
448
|
+
`csp` interaction.
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## 2026-08-28 — hardening from the first full Eleventy migration
|
|
453
|
+
|
|
454
|
+
Fixes and gaps surfaced running the first full Eleventy migration end to end.
|
|
455
|
+
|
|
456
|
+
### core: (client-logic, theme, assets, internal-links, security-headers)
|
|
457
|
+
|
|
458
|
+
- **`client-logic`** — the page wiring is **CSP-safe**: no inline `<script>`, no
|
|
459
|
+
`on*=`; it's a loaded file, and for a per-page calculator the browser-only
|
|
460
|
+
wiring sits in a `typeof document !== "undefined"` block *inside the same pure
|
|
461
|
+
module* (one file, DOM-free in the test runner). Resolves the standing
|
|
462
|
+
contradiction with `security-headers`' `script-src 'self'`.
|
|
463
|
+
- **`theme`** — the anti-FOUC script is a separate render-blocking file (or a
|
|
464
|
+
hashed inline block), never a raw inline `<script>` — same CSP reason.
|
|
465
|
+
- **`assets`** — added: verify a custom font's subset actually covers the
|
|
466
|
+
site's text (a mis-scoped subset fails silently to the system stack); serve
|
|
467
|
+
the web manifest as `application/manifest+json`.
|
|
468
|
+
- **`internal-links`** — the build check also fails on any inline `<script>` /
|
|
469
|
+
`<style>` / `on*=` / `style=` in the output (CSP-regression guard), and on any
|
|
470
|
+
CSP-granted origin missing from the manifest (the reverse isn't required —
|
|
471
|
+
a host/form-processor is in the manifest but loads nothing).
|
|
472
|
+
- **`security-headers`** — Verify reworded to the one-directional CSP↔manifest
|
|
473
|
+
check; note that the CSP only applies once served (check live + click through
|
|
474
|
+
in a real browser after deploy); don't restate platform-managed HSTS.
|
|
475
|
+
|
|
476
|
+
### stacks/eleventy-netlify.md
|
|
477
|
+
|
|
478
|
+
- `client-logic` / `theme` — the calc module now carries the browser wiring
|
|
479
|
+
block; `data-calc=` on the button; anti-FOUC → `static/theme-init.js`.
|
|
480
|
+
- `og-image` — Satori needs a **static** ttf/otf/woff (not woff2, not a
|
|
481
|
+
variable font's `fvar`); bundle `@fontsource/<family>` static weights; build
|
|
482
|
+
the card as a vnode object, drop `satori-html`.
|
|
483
|
+
- `the-gate` — `"test": "node --test test/*.test.js"` (the glob; bare
|
|
484
|
+
`node --test test/` errors on newer Node). `png-to-ico` + `@fontsource/*` in
|
|
485
|
+
deps; `node-html-parser` (check-links) in devDeps; note `npm ci` installs
|
|
486
|
+
both tiers.
|
|
487
|
+
- `ci-cd` — `actions/checkout@v5` / `setup-node@v5` (v4 → deprecated Node 20);
|
|
488
|
+
shallow checkout is fine for the `git log` `<lastmod>`; `NETLIFY_SITE_ID` is
|
|
489
|
+
the UUID Netlify now labels "Project ID".
|
|
490
|
+
- `security-headers` — `[[headers]]` for `/site.webmanifest` content-type.
|
|
491
|
+
- `internal-links` — the CSP-safety + CSP-origin scan documented in
|
|
492
|
+
`check-links.js`; ad/privacy opt-out `<a>` allowlist.
|
|
493
|
+
- `adopting` Pattern A — steps 6/8 reworded to write CSP-shaped modules up front
|
|
494
|
+
and land `theme-init.js` + the inline-code scan with the headers.
|
|
495
|
+
|
|
496
|
+
### stacks/sveltekit.md
|
|
497
|
+
|
|
498
|
+
- `theme` hint — anti-FOUC must be a hashed inline block (SvelteKit `csp`) or a
|
|
499
|
+
`<script src>`, not raw inline.
|
|
500
|
+
|
|
501
|
+
---
|
|
502
|
+
|
|
503
|
+
## 2026-08-28 — solo is the default working mode
|
|
504
|
+
|
|
505
|
+
### core: ci-cd
|
|
506
|
+
|
|
507
|
+
- Reframed from a PR-centric flow with solo accommodations to **solo as the
|
|
508
|
+
documented default**: branch → pre-push hook runs the gate → fast-forward
|
|
509
|
+
`main` → push; open a pull request only for a concrete reason (CI vetting a
|
|
510
|
+
branch before `main`, the rendered diff, a paper trail), never as
|
|
511
|
+
self-approval ceremony; multi-committer projects add the PR/review layer on
|
|
512
|
+
top and nothing below changes. New specs: the pre-push hook is *the*
|
|
513
|
+
enforcement point solo; push `main` after each merge (offsite copy + CI
|
|
514
|
+
actually runs); CI is a clean-room environment-drift check, not a merge gate.
|
|
515
|
+
Rationale gained a paragraph; Verify now checks the hook is *installed*.
|
|
516
|
+
|
|
517
|
+
### core: repo-hygiene
|
|
518
|
+
|
|
519
|
+
- "one logical change per pull request" → "per commit (and per PR, on projects
|
|
520
|
+
that use them)". Contract line "small reviewable changes" → "small
|
|
521
|
+
self-contained changes".
|
|
522
|
+
|
|
523
|
+
### core: adopting
|
|
524
|
+
|
|
525
|
+
- "PR review checklist" → "Change checklist" — run before a merge to `main` and
|
|
526
|
+
again before `bin/deploy`; "all in this PR" → "all in the same change".
|
|
527
|
+
|
|
528
|
+
### stacks/eleventy-netlify.md · phoenix.md · sveltekit.md
|
|
529
|
+
|
|
530
|
+
- `ci-cd` mirrors: the Eleventy binding states the solo flow up front and
|
|
531
|
+
`ci.yml` triggers on `push: {}` (any branch) + `pull_request`; "every merged
|
|
532
|
+
PR" → "everything on `main`"; "PR review checklist" → "Change checklist"
|
|
533
|
+
across all three; migration `[one PR:]` markers → `[together:]`; the
|
|
534
|
+
"busy PR flow" build-minutes framing dropped.
|
|
535
|
+
|
|
536
|
+
## 2026-08-27 — netlify deploy `--no-build`
|
|
537
|
+
|
|
538
|
+
### stacks/eleventy-netlify.md
|
|
539
|
+
|
|
540
|
+
- `ci-cd` — corrected the deploy command. The doc claimed `netlify deploy`
|
|
541
|
+
"without `--build`" uploads the artifact without building; the current Netlify
|
|
542
|
+
CLI (v17+, verified on v27) runs `netlify.toml`'s `build.command` **by
|
|
543
|
+
default** on `netlify deploy`. Without `--no-build` it rebuilds `_site/` with
|
|
544
|
+
no `DEPLOY_TARGET`, i.e. in preview mode, and ships `noindex` + `robots`
|
|
545
|
+
`Disallow: /` to production. Surfaced by the first real `bin/deploy` on the
|
|
546
|
+
first Eleventy migration — production served `noindex` for ~35 min.
|
|
547
|
+
`bin/deploy`, the `deploy.yml`/PR-preview snippets, the "zero build minutes"
|
|
548
|
+
line, and the anti-pattern entry all now carry `--no-build`.
|
|
549
|
+
|
|
550
|
+
### stacks/sveltekit.md
|
|
551
|
+
|
|
552
|
+
- `ci-cd` hint — same `--no-build` fix in the `adapter-static` + Netlify note.
|
|
553
|
+
|
|
554
|
+
## 2026-08-27 — initial split
|
|
555
|
+
|
|
556
|
+
The single 1518-line "Eleventy + Netlify static-site house style" document was
|
|
557
|
+
split into a stack-agnostic CORE contract plus per-stack implementation
|
|
558
|
+
bindings.
|
|
559
|
+
|
|
560
|
+
### core: all
|
|
561
|
+
|
|
562
|
+
- Created `core.md` — ~34 contract chapters (`Contract / Specs / Rationale /
|
|
563
|
+
Verify / Implementation`), each with a frozen slug. The 23 sections of the
|
|
564
|
+
source doc were dispositioned CORE / IMPL / SPLIT; every stack-agnostic
|
|
565
|
+
principle landed in CORE with its concrete specs preserved verbatim (the CSP
|
|
566
|
+
string, the OG/Twitter tag table, the a11y checklist, the security-header
|
|
567
|
+
values, the OG image dimensions).
|
|
568
|
+
- `#privacy` — encodes the "consent-gated allowed" decision: default is
|
|
569
|
+
no-tracking; third-party analytics (incl. GA/GTM) permitted **only** when
|
|
570
|
+
privacy-first or gated behind a CMP that loads nothing until opt-in.
|
|
571
|
+
- `#ci-cd` — genericized from the Netlify "a git push never triggers a build"
|
|
572
|
+
framing to "deploys are gated, sanctioned, no bypass, on your schedule";
|
|
573
|
+
push-to-deploy is explicitly fine where the gate runs first.
|
|
574
|
+
- `#security-headers` — "framing denied" accepts `X-Frame-Options: DENY` **or**
|
|
575
|
+
`SAMEORIGIN` + `frame-ancestors 'none'` (surfaced by writing the Phoenix
|
|
576
|
+
binding, where `put_secure_browser_headers` uses SAMEORIGIN).
|
|
577
|
+
- `#assets` — "a system font stack also satisfies 'no external font origin'"
|
|
578
|
+
(surfaced by the Phoenix binding).
|
|
579
|
+
- `#content-model` — the validation-gate contract holds for DB-backed content
|
|
580
|
+
via changeset/resource validation; "no database" is not a CORE rule (it is the
|
|
581
|
+
static envelope, addressed in `#beyond-this-standard`).
|
|
582
|
+
|
|
583
|
+
### stacks/eleventy-netlify.md
|
|
584
|
+
|
|
585
|
+
- The source doc reworked as the Eleventy binding (~90% mechanism reuse). 4
|
|
586
|
+
impl-only sections (`stack-baseline`, `project-layout`, `eleventy-config`,
|
|
587
|
+
`templating`) + 31 mirrored sections + Eleventy-specific `adopting` /
|
|
588
|
+
`out-of-scope` / `anti-patterns`.
|
|
589
|
+
- Two migration paths added, keyed to common starting conditions (Pattern A:
|
|
590
|
+
self-hosted, content in templates, no CI; Pattern B: CDN assets + a form
|
|
591
|
+
processor, content already externalized).
|
|
592
|
+
|
|
593
|
+
### stacks/phoenix.md
|
|
594
|
+
|
|
595
|
+
- New binding, grounded in a real production Phoenix/Ash codebase (unnamed —
|
|
596
|
+
the standard is project-agnostic). Every CORE contract could be expressed as a
|
|
597
|
+
Phoenix "how" — none had to move back to Eleventy-specific.
|
|
598
|
+
- Notable idiomatic contrasts: cached `SitemapController` + `Ash.Notifier`
|
|
599
|
+
invalidation (not a build-time template); `bin/deploy {env}` `git push` to a
|
|
600
|
+
release remote (push-to-deploy is fine); the serverless key-hiding proxy
|
|
601
|
+
dissolves (call from a context module); `internal-links` partly free via
|
|
602
|
+
verified routes `~p`.
|
|
603
|
+
- The common non-compliance of an existing Phoenix app (no CSP, per-page meta
|
|
604
|
+
unassigned, no JSON-LD, marketing scripts with no CMP, no a11y tooling, a
|
|
605
|
+
`.env` in git history, an unrouted health check, a misplaced `force_ssl`
|
|
606
|
+
`exclude:` key, unauthenticated AshAdmin, no CI workflow) is documented
|
|
607
|
+
per-section as **common gaps** and consolidated into a migration checklist.
|
|
608
|
+
|
|
609
|
+
### stacks/sveltekit.md
|
|
610
|
+
|
|
611
|
+
- New binding as a **stub**: full mirrored-slug skeleton, per-section "how" is
|
|
612
|
+
`TODO` with a concrete hint. The `where-sveltekit-fights-the-grain` (5
|
|
613
|
+
friction points + 3 places it helps) and `open-questions-for-later` (6 items)
|
|
614
|
+
sections are written.
|
|
615
|
+
|
|
616
|
+
### deleted
|
|
617
|
+
|
|
618
|
+
- `eleventy-house-style.md` (the source) — superseded by `core.md` +
|
|
619
|
+
`stacks/eleventy-netlify.md`.
|
|
620
|
+
|
|
621
|
+
### not done (deferred)
|
|
622
|
+
|
|
623
|
+
- Move the doc set to a permanent home; wire the global `~/.claude/CLAUDE.md`
|
|
624
|
+
pointer; backfill the House-style block into and migrate existing Eleventy
|
|
625
|
+
projects. Each needs its own review.
|