@piercebarney/whs-eleventy 2026.9.8 → 2026.9.10

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/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // whs — the web house style's Eleventy + Netlify tooling.
3
3
  //
4
+ // whs status [--hook] standard-version drift, at a glance
4
5
  // whs compliance [--strict] codebase-vs-standard conformance sweep
5
6
  // whs doctor [--live|--deploy-preflight] infrastructure drift check
6
7
  // whs links built-output link / CSP / JSON-LD integrity
@@ -16,6 +17,7 @@ const path = require("node:path");
16
17
  const { spawnSync } = require("node:child_process");
17
18
 
18
19
  const COMMANDS = {
20
+ status: "status.js",
19
21
  compliance: "compliance.js",
20
22
  doctor: "doctor.js",
21
23
  links: "check-links.js",
package/lib/compliance.js CHANGED
@@ -832,4 +832,11 @@ if (require.main === module) main();
832
832
 
833
833
  // CHECKS is exported for the "coverage" test — nothing else should read it as
834
834
  // data (call runCompliance() for results).
835
- module.exports = { runCompliance, summarize, changelogSlugs, changelogEntries, CHECKS };
835
+ module.exports = {
836
+ runCompliance,
837
+ summarize,
838
+ changelogSlugs,
839
+ changelogEntries,
840
+ versionDrift,
841
+ CHECKS,
842
+ };
package/lib/status.js ADDED
@@ -0,0 +1,108 @@
1
+ // Standard-version drift, at a glance (core.md#compliance). The delta-only
2
+ // slice of `whs compliance` — how far the project's pinned standard-version is
3
+ // behind the standard, and which chapters that means re-checking — without the
4
+ // full codebase sweep. Cheap enough to run on every Claude Code SessionStart.
5
+ //
6
+ // whs status print the version delta + the re-check rows
7
+ // whs status --hook silent when current; one line when behind (for the
8
+ // SessionStart hook — never non-zero, never noisy)
9
+ //
10
+ // Exit code is always 0: like `compliance` without --strict, drift is a
11
+ // backlog signal, not a build break.
12
+
13
+ const fs = require("node:fs");
14
+ const os = require("node:os");
15
+ const path = require("node:path");
16
+
17
+ // `status` answers "is there a newer standard available on this machine?" — so
18
+ // before the compliance module resolves its standard text, prefer the local
19
+ // canonical checkout (the ~/.claude symlink the README keeps) when one exists
20
+ // and nothing more specific was set. `whs compliance` still measures the
21
+ // codebase against the tooling's own bundled snapshot; `status` measures the
22
+ // pin against the freshest tree available, which is what a "you're behind"
23
+ // nudge should reflect. Machines without the symlink (CI, a fresh clone) fall
24
+ // back to the bundled snapshot unchanged.
25
+ if (!process.env.WHS_STANDARD) {
26
+ const local = path.join(os.homedir(), ".claude", "standards", "web-house-style");
27
+ if (fs.existsSync(path.join(local, "core.md"))) process.env.WHS_STANDARD = local;
28
+ }
29
+
30
+ const { versionDrift } = require("./compliance.js");
31
+ const { resolveStandard } = require("./_project.js");
32
+
33
+ // Distinct CHANGELOG dates after the pin — the "N versions behind" count. The
34
+ // standard versions by date, and several entries can share one date (one
35
+ // release); this counts releases, not entries.
36
+ function versionsBehind(pin) {
37
+ const std = resolveStandard();
38
+ if (!std || !pin) return 0;
39
+ try {
40
+ const changelog = fs.readFileSync(path.join(std, "CHANGELOG.md"), "utf8");
41
+ const dates = new Set();
42
+ for (const line of changelog.split("\n")) {
43
+ const m = line.match(/^##\s+([0-9]{4}-[0-9]{2}-[0-9]{2})\b/);
44
+ if (m && m[1] > pin) dates.add(m[1]);
45
+ }
46
+ return dates.size;
47
+ } catch {
48
+ return 0;
49
+ }
50
+ }
51
+
52
+ function main() {
53
+ const hook = process.argv.includes("--hook");
54
+ const { pin, current, rows } = versionDrift();
55
+
56
+ const unresolved =
57
+ rows.length === 1 && rows[0].startsWith("MANUAL: standard text not resolvable");
58
+ if (unresolved) {
59
+ if (!hook) console.log(rows[0]);
60
+ return;
61
+ }
62
+
63
+ if (!rows.length) {
64
+ if (!hook) {
65
+ console.log(
66
+ pin && current
67
+ ? `web house style: up to date (pinned ${pin}, standard at ${current})`
68
+ : "web house style: standard-version not pinned in CLAUDE.md",
69
+ );
70
+ }
71
+ return;
72
+ }
73
+
74
+ const breaking = rows.filter((r) => r.startsWith("BREAKING:")).length;
75
+ const versions = versionsBehind(pin);
76
+ const behind = `${versions} version${versions === 1 ? "" : "s"} behind`;
77
+ const recheck = `${rows.length} re-check${rows.length === 1 ? "" : "s"}${
78
+ breaking ? ` (${breaking} breaking)` : ""
79
+ }`;
80
+
81
+ if (hook) {
82
+ console.log(
83
+ `⚠ web house style: ${behind}, ${recheck} — pinned ${pin}, standard at ${current}. ` +
84
+ `Run /whs:upgrade to catch up.`,
85
+ );
86
+ return;
87
+ }
88
+
89
+ console.log(
90
+ `\nstandard-version: pinned ${pin}, standard at ${current} — ${behind}, ${recheck}\n`,
91
+ );
92
+ for (const row of rows) console.log(` ${row}`);
93
+
94
+ const hasUpgrade = fs.existsSync(
95
+ path.join(process.cwd(), ".claude", "commands", "whs", "upgrade.md"),
96
+ );
97
+ console.log(
98
+ hasUpgrade
99
+ ? "\nRun /whs:upgrade in a Claude Code session to work this backlog.\n"
100
+ : "\nThis project predates /whs:upgrade. Copy .claude/commands/whs/upgrade.md,\n" +
101
+ ".claude/hooks/standard-drift.sh, and the settings.json SessionStart entry\n" +
102
+ "from templates/eleventy-netlify/ in the standard, then run /whs:upgrade.\n",
103
+ );
104
+ }
105
+
106
+ if (require.main === module) main();
107
+
108
+ module.exports = { versionsBehind };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piercebarney/whs-eleventy",
3
- "version": "2026.9.8",
3
+ "version": "2026.9.10",
4
4
  "description": "The web house style's Eleventy + Netlify tooling — the compliance sweep, the infra doctor, the link/CSP integrity check, and the a11y scan, shared by every project on the stack.",
5
5
  "bin": {
6
6
  "whs": "cli.js"
@@ -18,6 +18,50 @@ enforced, new *opt-in* chapters/capabilities, tooling-only changes — is
18
18
 
19
19
  ---
20
20
 
21
+ ## 2026-09-10 — a one-command standard-version catch-up, and drift that surfaces itself [non-breaking]
22
+
23
+ Catching an adopted project up to a moved-on standard was a hand-written prompt
24
+ every time. The detection existed (`whs compliance`'s severity-tagged
25
+ `re-check #<slug>` rows) but was passive — nothing said "you're behind" unless
26
+ someone ran it. `core.md#compliance` already expects the assistant to run
27
+ compliance "whenever `standard-version:` is behind"; this makes the Eleventy
28
+ stack actually do that, and adds the command that works the result. Non-breaking:
29
+ a project compliant before is still compliant — this is an opt-in reporter, a
30
+ session-start notice, and a new command; no requirement changes. Eleventy-only
31
+ pilot, same stance as `/whs:build`; Phoenix/SvelteKit keep the by-hand path
32
+ until they have a tooling package.
33
+
34
+ ### stacks/eleventy-netlify.md: compliance
35
+
36
+ - Documented `whs status` (the drift row alone — pinned vs. current version,
37
+ versions behind, the `re-check` list — no codebase sweep) and its
38
+ `SessionStart` wiring in the `compliance` section's **Wiring** bullet. Added a
39
+ migration-section note: an already-adopted project that has only fallen behind
40
+ runs `/whs:upgrade`; one scaffolded before the command shipped copies three
41
+ files from the template first.
42
+
43
+ ### templates/eleventy-netlify
44
+
45
+ - New `/whs:upgrade` command (`.claude/commands/whs/upgrade.md`) — reads the
46
+ CHANGELOG delta since the pin, bumps `@piercebarney/whs-eleventy`, works the
47
+ `compliance` backlog one `re-check` row at a time (delegating to the
48
+ `brand`/`content`/`layout`/`pack` stage agents where a slug is theirs), then
49
+ re-pins `standard-version`. Never deploys. Structured like `/whs:build`.
50
+ - New `.claude/hooks/standard-drift.sh` + a `SessionStart` entry in
51
+ `.claude/settings.json` — runs `whs status --hook` at the top of every Claude
52
+ Code session; one line when the pin is behind, silent when current, never
53
+ blocks.
54
+ - `package.json` gains a `status` script (`whs status`).
55
+ - `CLAUDE.md` points at `/whs:upgrade` for the behind-the-standard case.
56
+
57
+ ### packages/whs-eleventy
58
+
59
+ - New `whs status` subcommand (`lib/status.js`) — reuses `compliance.js`'s
60
+ `versionDrift()` (now exported) and reports the delta without running the
61
+ `CHECKS` sweep. `--hook` mode: one compact line when behind, nothing when
62
+ current, always exit 0. Registered in `cli.js`. Tests in `test/status.test.js`
63
+ plus a `versionDrift`-export assertion in `test/compliance.test.js`.
64
+
21
65
  ## 2026-09-08 — content-ops: reservation becomes a real opt-in, not a hardcoded default [non-breaking]
22
66
 
23
67
  Found while building `/whs:build`'s Content stage agent, which needs to know
package/standard/core.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Web project house style — CORE (stack-agnostic)
2
2
 
3
- **Version:** 2026-09-08 · **Status:** active
3
+ **Version:** 2026-09-10 · **Status:** active
4
4
 
5
5
  This is the stack-agnostic contract every web project follows, regardless of
6
6
  framework, host, or CSS system. It says **what** must be true, with concrete
@@ -1422,7 +1422,7 @@ governs it and follow that chapter — the slugs are the index (a `<script>` →
1422
1422
  - production-url: https://example.com
1423
1423
  - content-type: tool # tool | article (article => feeds)
1424
1424
  - publishing-rate: ~5 pages/week
1425
- - standard-version: 2026-09-08 # recommended — enables standard-version drift tracking (#compliance)
1425
+ - standard-version: 2026-09-10 # recommended — enables standard-version drift tracking (#compliance)
1426
1426
  ```
1427
1427
 
1428
1428
  If the section is absent, the assistant's first action is to run the flow above