docs(humanizer): v5.1.0 release notes across plugin + marketplace docs

- Plugin README: add "What's New in v5.1.0" section with humanizer overview,
  before/after example, plain-language vocabulary table, --raw flag docs.
  Bump version badge 5.0.0 → 5.1.0. Add Version History row.
- Plugin CLAUDE.md: add humanizer.mjs + humanizer-data.mjs to Scanner Lib
  table. Add "Plain-Language Output (v5.1.0)" section documenting output
  modes, vocabularies, and Wave 5 lessons. Bump test count 635 → 792 across
  52 test files.
- Marketplace root README: bump config-audit entry 5.0.0 → 5.1.0, update
  one-line description to mention plain-language UX, add bullet for the
  v5.1.0 humanizer, bump test count 635+ → 792+.

Test-normalizer hardening (consequence of growing CLAUDE.md):
walkClaudeMdCascade walks upward from the marketplace-medium fixture into
this plugin's own CLAUDE.md, so any docs edit ripples into
`scanners[*].activeConfig.claudeMdEstimatedTokens`. The v5.0.0 byte-stability
contract is about scanner internals being unchanged, not ancestor input
content being frozen. Normalizers in json-backcompat, raw-backcompat,
posture-humanizer, scan-orchestrator-humanizer, and snapshot-default-output
now strip claudeMdEstimatedTokens to <ANCESTOR_DERIVED>. The
default-output snapshot for scan-orchestrator was re-seeded via
UPDATE_SNAPSHOT=1 (intent: Wave 6 docs additions; humanizer prose
unchanged).

Verify:
- grep -E "5\.1\.0|v5\.1\.0" README.md CLAUDE.md ../../README.md | wc -l = 12
- node --test 'tests/**/*.test.mjs' = 792/792 pass
- self-audit configGrade A (97), pluginGrade A (100), readmeCheck.passed true
This commit is contained in:
Kjell Tore Guttormsen 2026-05-01 20:35:24 +02:00
commit fc8808d6e4
9 changed files with 170 additions and 10 deletions

View file

@ -67,9 +67,24 @@ async function ensureDriftBaseline() {
}
// ---------------------------------------------------------------------------
// Normalizers — strip time / path fields that vary between runs.
// Normalizers — strip time / path / ancestor-derived fields that vary
// independently of scanner internals. `claudeMdEstimatedTokens` is computed
// by walking the FS cascade upward from the fixture; any edit to this
// plugin's own CLAUDE.md ripples into it, even though scanner behavior is
// unchanged. The byte-stability contract covers scanner output shape, not
// the size of ancestor input docs.
// ---------------------------------------------------------------------------
function stripAncestorDerived(envOrEnvelope) {
if (Array.isArray(envOrEnvelope?.scanners)) {
for (const s of envOrEnvelope.scanners) {
if (s?.activeConfig && 'claudeMdEstimatedTokens' in s.activeConfig) {
s.activeConfig.claudeMdEstimatedTokens = '<ANCESTOR_DERIVED>';
}
}
}
}
function normalizeScanOrchestrator(env) {
const out = JSON.parse(JSON.stringify(env));
if (out.meta) {
@ -81,6 +96,7 @@ function normalizeScanOrchestrator(env) {
s.duration_ms = 0;
}
}
stripAncestorDerived(out);
return out;
}
@ -96,6 +112,7 @@ function normalizePosture(p) {
s.duration_ms = 0;
}
}
stripAncestorDerived(out.scannerEnvelope);
}
return out;
}

View file

@ -66,8 +66,21 @@ async function ensureDriftBaseline() {
// ---------------------------------------------------------------------------
// Normalizers — same as json-backcompat to keep the contracts aligned.
// `claudeMdEstimatedTokens` is stripped because walkClaudeMdCascade walks
// upward from the fixture into this plugin's own CLAUDE.md; any docs edit
// here ripples into it even when scanner internals are unchanged.
// ---------------------------------------------------------------------------
function stripAncestorDerived(envOrEnvelope) {
if (Array.isArray(envOrEnvelope?.scanners)) {
for (const s of envOrEnvelope.scanners) {
if (s?.activeConfig && 'claudeMdEstimatedTokens' in s.activeConfig) {
s.activeConfig.claudeMdEstimatedTokens = '<ANCESTOR_DERIVED>';
}
}
}
}
function normalizeScanOrchestrator(env) {
const out = JSON.parse(JSON.stringify(env));
if (out.meta) {
@ -79,6 +92,7 @@ function normalizeScanOrchestrator(env) {
s.duration_ms = 0;
}
}
stripAncestorDerived(out);
return out;
}
@ -94,6 +108,7 @@ function normalizePosture(p) {
s.duration_ms = 0;
}
}
stripAncestorDerived(out.scannerEnvelope);
}
return out;
}

View file

@ -16,7 +16,10 @@ const POSTURE_STDERR_SNAPSHOT = resolve(REPO, 'tests/snapshots/v5.0.0-stderr/pos
/**
* Normalize a runPosture result for snapshot comparison by zeroing out
* time-varying fields and machine-specific paths.
* time-varying fields, machine-specific paths, and ancestor-cascade-derived
* counts. `claudeMdEstimatedTokens` reflects walkClaudeMdCascade walking
* upward from the fixture; any docs edit to this plugin's own CLAUDE.md
* ripples into it even though scanner behavior is unchanged.
*/
function normalizePosture(p) {
const out = JSON.parse(JSON.stringify(p));
@ -28,6 +31,9 @@ function normalizePosture(p) {
if (Array.isArray(out.scannerEnvelope.scanners)) {
for (const s of out.scannerEnvelope.scanners) {
s.duration_ms = 0;
if (s.activeConfig && 'claudeMdEstimatedTokens' in s.activeConfig) {
s.activeConfig.claudeMdEstimatedTokens = '<ANCESTOR_DERIVED>';
}
}
}
}

View file

@ -15,8 +15,11 @@ const SNAPSHOT_PATH = resolve(REPO, 'tests/snapshots/v5.0.0/scan-orchestrator.js
/**
* Normalize a scan-orchestrator envelope for snapshot comparison by
* blanking out time-varying fields (timestamp, durations, target path).
* Returns a NEW object does not mutate input.
* blanking out time-varying fields (timestamp, durations, target path)
* and ancestor-cascade-derived counts. `claudeMdEstimatedTokens` reflects
* walkClaudeMdCascade walking upward from the fixture; any docs edit to
* this plugin's own CLAUDE.md ripples into it even when scanner behavior
* is unchanged. Returns a NEW object does not mutate input.
*/
function normalizeEnvelope(env) {
const out = JSON.parse(JSON.stringify(env));
@ -27,6 +30,9 @@ function normalizeEnvelope(env) {
if (Array.isArray(out.scanners)) {
for (const s of out.scanners) {
s.duration_ms = 0;
if (s.activeConfig && 'claudeMdEstimatedTokens' in s.activeConfig) {
s.activeConfig.claudeMdEstimatedTokens = '<ANCESTOR_DERIVED>';
}
}
}
return out;

View file

@ -58,6 +58,13 @@ function normalizeScanOrchestrator(env) {
if (Array.isArray(out.scanners)) {
for (const s of out.scanners) {
s.duration_ms = 0;
// claudeMdEstimatedTokens reflects walkClaudeMdCascade walking up from
// the fixture into this plugin's own CLAUDE.md; any docs edit ripples
// into it independently of scanner internals. Strip to keep the
// default-output snapshot focused on humanizer prose stability.
if (s.activeConfig && 'claudeMdEstimatedTokens' in s.activeConfig) {
s.activeConfig.claudeMdEstimatedTokens = '<ANCESTOR_DERIVED>';
}
}
}
return out;

View file

@ -491,7 +491,7 @@
],
"total_estimated_tokens": 1809,
"activeConfig": {
"claudeMdEstimatedTokens": 5716,
"claudeMdEstimatedTokens": "<ANCESTOR_DERIVED>",
"mcpServerCount": 3,
"pluginCount": 41,
"skillCount": 65