Skip to content

release: staging → main (helixir 0.6.0 — Accessibility dimensional upgrade, 261 commits)#233

Merged
himerus merged 261 commits into
mainfrom
staging
May 11, 2026
Merged

release: staging → main (helixir 0.6.0 — Accessibility dimensional upgrade, 261 commits)#233
himerus merged 261 commits into
mainfrom
staging

Conversation

@himerus

@himerus himerus commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Release PR from staging to main, now folding in the Accessibility dimensional upgrade (helixir 0.6.0) on top of the original 48-commit scope. Total of 261 commits since the last main release.

This release replaces helixir's single weight-10 critical Accessibility scoring dimension with 8 evidence-backed sub-dimensions that read helix-native AAA metadata (helixMeta in CEM, aaa-verdicts.json snapshots, AAA-AUDIT.md sidecars, source-level signals) when available, and surface unknown confidence on non-helix libraries rather than gaslighting A's.

Merging this PR triggers changesets/action@v1 which will open the "chore: version packages" version PR with bumped manifests + changelog entries.

Release shape (per pnpm changeset status)

  • helixir → 0.6.0 (minor — dimensional upgrade is additive + back-compat)
  • helixir-vscode → patch (workspace consumer, private)
  • @helixir/core → ignored (workspace internal)
  • @helixui/mcp → major (showing in status; release manager should verify before letting changeset-bot ship — this is a workspace-consumer side-effect of updateInternalDependencies: "patch" chain)

What ships in helixir 0.6.0

Dimension registry

  • Accessibility (weight 10, critical) → split into 5 new dims:
    • WCAG Conformance (critical, 4)
    • APG Keyboard Contract (critical, 2)
    • Focus Indicator (important, 1)
    • Form Association (important, 1)
    • Accessible Label Pattern (important, 2)
  • 3 new orthogonal dims:
    • Forced Colors Mode (important, 1)
    • Form Validity Reporting (advanced, 1)
    • AAA Audit Self-Certification (advanced, 0 — informational)
  • Total: 14 → 21 dims, 105 → 107 weight, 5 → 6 critical-tier
  • scoring.weights.accessibility config key still works through 0.7.x via deprecation fan-out

New MCP tool: detect_helix_evidence(tagName, libraryRoot?, libraryId?)

Existing tools gain optional libraryRoot: score_component, score_all_components, audit_library, analyze_accessibility, get_health_summary

Real-world catch surfaced by the new scorers: helix hx-button declares static formAssociated = true in its CEM but never calls attachInternals() / setValidity() — Form Association scorer flags 20/heuristic with attach-internals-missing + set-validity-not-called notes. Documented in defect-corpus class 18 in bst-cto-kb.

Engineering process

The dimensional upgrade went through:

  • 12 local-review codex rounds (every round on new code paths, zero outright flips) — trajectory 3,3,3,4,5,3,3,1,3,2,3,0 = clean pass
  • 4 push-gate codex rounds (different base SHA, found additional adjacent issues — addressed each)
  • 1 documented REA_SKIP_CODEX_REVIEW skip on push-gate round 4 per the project memory's 3-flip rule (packageName-scoping decision flipped R7→R10→push-gate-R4)
  • Total: ~40 findings addressed across P1, P2, P3 severities

Test plan

  • dev → staging merged (PR promote: dev → staging (helixir 0.6.0 — Accessibility dimensional upgrade) #235, c64f4b1..31924b8)
  • Local: pnpm exec vitest run — 3429 passing (was 3298), 0 failing
  • Local: pnpm exec tsc --noEmit — clean
  • Local: pnpm exec eslint . — clean on new code
  • Local: pnpm run build — clean
  • Local: pnpm changeset status — clean (was broken by 20 pre-existing mixed @helixir/core changesets that this PR migrated)
  • Live smoke against real helix tree captured at tests/__fixtures__/smoke/
  • CEM regenerated (custom-elements.json)
  • CI green on staging (in progress)
  • Merge triggers changesets/action — opens version PR on main
  • Verify version PR shape before merging it
  • Merge version PR → pnpm changeset publish → helixir 0.6.0 on npm

himerus and others added 30 commits March 26, 2026 17:09
…r-returns-todo

fix: replace TODO placeholder with var() fallback in theme handler
…ite-for-styling-tools

test: add test suite for styling tools (29 tools, zero coverage)
- packages/core/src/handlers/cem.ts: throw MCPError with VALIDATION category
  for CSS custom property name validation
- packages/core/src/handlers/dependencies.ts: import MCPError/ErrorCategory,
  throw MCPError with NOT_FOUND category for missing component
- packages/core/src/handlers/extend.ts: import MCPError/ErrorCategory,
  throw MCPError with NOT_FOUND category for missing parent component
- src/mcp/index.ts: import handleToolError, use .message for stderr logging
- src/cli/index.ts: import handleToolError, use .message for stderr logging

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n-error-throws-with

fix: replace plain Error throws with MCPError across all handlers
- Updates tool count badge from 87+ to 73 (accurate count)
- Updates feature headline from "30+ MCP tools" to "73 MCP tools"
- Adds audit_library to Health section (was missing)
- Adds all 29 styling tools in a new Styling section
- Adds TypeGenerate, Theme, Scaffold, and Extend tool sections
- Verified all tool names match src/mcp/index.ts registrations
- Prettier format check passes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…e-tool-count-badge-and

fix: update README tool count badge and tools reference
…ilure

The post-merge verification ran `npm run typecheck` but the root package.json
only had `type-check` (with hyphen). Added a `typecheck` alias that delegates
to `pnpm run type-check` so both script names work.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…erification-failure-for

fix: add typecheck script alias for post-merge verification
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ites-for-scaffold

test: add test suites for scaffold, extend, theme, and bundle tools
Resolve README.md conflict: keep 87+ tool count from feature branch while
incorporating dev's expanded tool description list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…me-social-card-to

docs: update README social card to PNG and fix tool count to 87+
… typegenerate, typescript, and validate tools

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ites-for-remaining-9

test: add test suites for 9 remaining tool files
- Replace 10 nearly-identical if-blocks with ENV_MAP_STRING and
  ENV_MAP_NULLABLE lookup tables iterated in a loop
- Remove deprecated mcpwc.config.json fallback and its warning message
- readConfigFile() now only reads helixir.mcp.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…figts-env-var-loading

fix: refactor config.ts env var loading to table-driven pattern
…indsurf-mcpjson-helper

feat: add Cursor/Windsurf mcp.json helper command to VS Code extension
Standardize all required args[] accesses in cli/index.ts to check
args.length first, then access with type assertion. This applies to
cmdHealth (trend), cmdMigrate, cmdSuggest, cmdBundle, cmdCompare,
cmdValidate, and cmdCdn — replacing the pattern of access-then-check
with the consistent check-before-access pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hecking-for-cli-array

fix: add bounds checking for CLI array index accesses
- mixin-resolver: chain resolution, architecture classification, aggregated markers, ResolvedSource structure
- naming-consistency: prefix detection, per-component scoring, confidence levels, normalization
- source-accessibility: PATTERNS export, scanSourceForA11yPatterns, scoreSourceMarkers, isInteractiveComponent, resolveComponentSourceFilePath
- slot-architecture: default/named slot scoring, type constraints, kebab-to-camel coherence, jsdocTags detection
- api-surface: method/attribute/default/description scoring, null cases, normalization
- type-coverage: property/event/method type scoring, bare Event exclusion, single-dimension normalization
- event-architecture: kebab-case validation, typed payload scoring, description scoring
- css-architecture: CSS property/parts documentation, design token naming pattern validation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix mixin-resolver: LitElement is a framework base class that
  getInheritanceChain() skips entirely — it does not appear in
  unresolved; add a separate test for non-framework superclasses
- Fix source-accessibility: TABINDEX_SOURCE used setAttribute which
  does not match the tabindex\s*[=:] pattern; use property assignment
  form that the regex actually matches

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ites-for-8-untested

test: add test suites for 8 untested analyzers
promote: dev → staging (16 features, 153 commits)
Add scaffold_component and extend_component to the expected coreTools
list. These tools were wired into the MCP server in PR #207 but the
test assertion was never updated, causing CI failures on all subsequent PRs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-count-badge-in-readme

fix: correct tool count badge in README (73 → actual count)
Generates sbom.json during each publish run using @cyclonedx/cyclonedx-npm,
uploads it as a GitHub Actions artifact for enterprise compliance audits.
Adds sbom.json to .gitignore and documents availability in README.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ration-to-publish

ci: add SBOM generation to publish workflow
himerus and others added 28 commits May 10, 2026 20:47
229 .automaker/ entries (corrupted action-item JSONs + archived
feature handoffs from the prior orchestration system) plus 9
.beads/ entries (config + hooks + interactions log).

Both directories are already in .gitignore. Zero references
remain in packages/, scripts/, or tooling source.
- New local-review-gate.sh hook chained onto PreToolUse:Bash matcher
- 13 new curated specialist agents (data-architect, devex-architect,
  mcp-protocol-specialist, observability-specialist, platform-architect,
  principal-engineer, principal-product-engineer, release-captain,
  security-architect, shell-scripting-specialist, ast-parser-specialist,
  figma-dx-specialist, adversarial-test-specialist)
- Refreshed codex-adversarial + rea-orchestrator agents to v0.28.1
  canonical bodies
- Refreshed codex-review command + .husky/commit-msg
- Manifest synced to 0.28.1
- npx rea doctor: all green
…sions

Splits the single weight-10 Accessibility dim into 5 sub-dims (WCAG
Conformance, APG Keyboard Contract, Focus Indicator, Form Association,
Accessible Label Pattern) plus 3 new orthogonal dims (Forced Colors Mode,
Form Validity Reporting, AAA Audit Self-Certification). Total dim count
14 → 21, total weight 105 → 107, critical dims 5 → 6.

New shared evidence detector reads helixMeta blocks from CEM,
aaa-verdicts.json snapshots, AAA-AUDIT.md sidecars, and source-level
signals (static formAssociated, attachInternals(), :focus-visible,
@media forced-colors) when libraryRoot is provided. Non-helix libraries
return unknown confidence on dims lacking evidence — surfaced under
M2-strict mode rather than scoring silent As.

New MCP tool: detect_helix_evidence. score_component,
score_all_components, and analyze_accessibility accept an optional
libraryRoot argument and surface a helixEvidence block when supplied.

Five new defect-corpus classes (15-19) with fixtures + paired
falsifiability cases in bst-cto-kb. Real-world smoke run against helix
hx-button captured under tests/__fixtures__/smoke/ — surfaces a
genuine helix inconsistency (button declares static formAssociated
but never calls attachInternals/setValidity) the old scorer missed.

Deprecates scoring.weights.accessibility (still honored through 0.7.x;
multiplier fans out across the five split a11y dims with one-time
console.warn). Specific sub-dim weights win over the legacy fanout.

Test suite: 3426 passing (was 3298). tsc clean. eslint clean on new
code. Benchmark fixture regenerated to reflect new dim breakdown —
library average rose 85.47 → 90.96 because decorative components
correctly return unknown on source-dependent dims rather than gaslit Fs.

Closes M6 task #19 (telemetry surfacing — readiness pipeline can
exercise the new tool surface).
Adds the changeset for the Accessibility dimension decomposition
feat that landed in cf8fe6e. Rolls into the next helixir minor
release alongside the 9 already-queued entries.

NOTE: 9 queued changesets (clever-foxes-swim, css-api-resolver,
dark-mode-checker, etc.) still cite '@helixir/core' alongside
'helixir' in their frontmatter. Since commit a6475b0 (2026-03-13)
added '@helixir/core' to the changeset ignore list, those mixed
entries fail 'changeset status' and will block the next 'changeset
version' run. Worth a follow-up PR to migrate them to 'helixir'-only.
This new changeset uses the corrected single-package shape.
Phase 4 missed updating tests/integration/server.test.ts (which asserts
the full MCP tool count from a live tools/list call). The dimensional
upgrade in cf8fe6e added detect_helix_evidence as the 72nd core tool;
the integration test expected 71. Bumps the count comment and the
expectedTools array.
The 'derives certified=false' test writeFileSync mutates the lib-bare
aaa-verdicts.json without teardown, leaving the committed fixture
churned between runs. beforeAll snapshots the original content,
afterAll restores it. Test count unchanged (14 passing).
Round 1 of local-review feedback against cf8fe6e.

P1 — Score inflation for unknown a11y dims (health.ts:1150).
The dispatcher's fromDim() adapter marked any !measured scorer result
as notApplicable, which causes computeWeightedScore to drop the
dimension from the weighted denominator. Non-helix / CEM-only libraries
silently scored as if the source-dependent a11y dims didn't exist —
exactly the gaslighting case the dimensional upgrade was meant to
close. Now: only confidence:'untested' (legitimate "no external data")
collapses out of the weighted denominator. confidence:'unknown' keeps
measured:true so it pulls the score down at 0, surfacing the gap.
calculateGrade already treats unknown as untested for the critical-dim
gate, so grade semantics are unchanged — only the displayed score is
now honest.

P2 — AAA-AUDIT.md freshness ignores stylesheet edits
(helix-aaa-evidence.ts:467). Accessibility changes (focus-ring,
forced-colors) commonly land in the sibling .styles.ts file. Audit
freshness now compares against the LATEST mtime across both the
component source AND the styles sidecar.

P2 — Source-only helix evidence dropped from analyze_accessibility
(accessibility.ts:320). When libraryRoot is provided but the library
has no helixMeta block and no aaa-verdicts.json snapshot (just
source-level signals like focus-visible CSS and attachInternals),
buildHelixAccessibilityReport now still attaches the helixEvidence
block. Previously these callers silently lost detected evidence.

Test contract updates:
- multi-dimensional-health.test.ts: 5 assertions flipped from
  measured:false to measured:true on the unknown-confidence dims;
  added score:0 assertions to lock in the surfacing behavior.
- dimensional-upgrade-end-to-end.test.ts: source-dependent loop
  now asserts confidence === 'unknown' rather than measured:false,
  with the AAA Audit Self-Certification exception (confidence:
  'untested', weight 0).

All 3426 vitest tests passing. tsc clean. eslint clean.
Round 2 of local-review feedback against 5aa502b.

P1 — APG patterns lost in published package.
apg-patterns.json lived as a sibling JSON next to the loader and was
read at runtime via readFileSync('../evidence/apg-patterns.json').
tsc doesn't copy *.json into build/, and helixir's published `files`
allowlist excludes src/ — so any installed copy of the package found
the JSON missing, caught the error, and returned an empty catalogue.
Both scoreApgKeyboard and scoreAccessibleLabel silently lost every
verified APG scoring path. Tests passed because they resolve against
src/ at test time, masking the prod regression.

Fix: inline the 26 APG patterns into the loader module as a frozen
TypeScript const. tsc compiles the .ts into build/, the data ships
with the package, and the runtime read disappears. apg-patterns.json
deleted. _resetApgPatternsCache kept as a no-op for test back-compat.

P2 — Passive APG patterns scored as missing-contract.
tabpanel, group, and similar landmark patterns intentionally carry no
required activate/navigate/dismiss keys — the keyboard contract lives
on the parent (tablist, the form, etc.). The previous scorer required
BOTH a parsed keyboardContract AND a populated APG pattern to reach
the verified branch; correct tabpanel/group components fell through
to 0/unknown on a critical dimension. Added isPassiveApgPattern()
predicate to the loader; the scorer now short-circuits to
verified/100 when the declared pattern is passive.

P2 — aaa-audit-cert.ts ignored helixMeta.aaa.certified.
The scorer accepted cert claims only from verdictSnapshot.certified.
Sibling detectors (wcag-conformance, summarizeHelixEvidence) also
accept helixMeta.aaa.certified — so a CEM-only helix consumer that
hadn't yet generated aaa-verdicts.json saw the cert recognized
everywhere except this scorer, which emitted the
audit-md-present-but-no-cert-claim warning. Now reads cert from
either source.

All 3426 vitest tests passing. tsc clean. eslint clean on new code.
Round 3 of local-review feedback against 241039c.

P1 — audit_library scored without libraryRoot.
generateAuditReport() called scoreComponentMultiDimensional with no
libraryRoot, so the library-wide audit ignored all source-level
evidence (focus-visible CSS, attachInternals, forced-colors blocks).
Same component scored via score_component(..., libraryRoot) produced
a materially different score — two public entry points diverging on
the same input. audit-report now defaults libraryRoot to
config.projectRoot, matching score_component's resolution. Callers
can override via options.libraryRoot.

P2 — parseKeyboardContract failed on legacy JSDoc layouts.
The strip regex was anchored at the start of the trimmed input. JSDoc
descriptions with a summary paragraph followed by `@keyboard-contract`
mid-text (the common helix legacy shape) bypassed the strip, then the
clause parser rejected the line that still began with the tag.
Documented contracts fell through to the no-contract branch.
parseKeyboardContract now searches anywhere in the input for the tag
and parses from that offset.

P2 — form-association incorrectly accepted partial removals.
When helixMeta.formAssociated === false the scorer only checked
hasStaticFormAssociated. A subclass that dropped the static flag but
kept attachInternals()/setValidity() scored 100/verified despite
still behaving as a form control. Now checks all three FACE signals
and surfaces every contradicting signal in the notes
(`static-formAssociated-still-present`, `attachInternals-still-called`,
`setValidity-still-called`).

All 3426 vitest tests passing. tsc + eslint clean.
Round 4 of local-review feedback against ba27175.

P1 — Helper rows inflated WCAG cert criteria count.
buildVerdictSnapshot counted every Supports entry toward the
9-criterion certification threshold. Helix's verdicts file mixes real
SCs (1.4.6, 2.4.13) with helper rows (forced-colors, apg-keyboard)
that are NOT WCAG criteria — so a component with 5 real SCs + 4 helper
Supports rows scored certified=true with bogus 100/verified. Now only
keys matching /^\d+\.\d+(?:\.\d+)?$/ count.

P1 — wcag-conformance ignored partial verdict snapshots.
When verdictSnapshot existed but certified=false the scorer fell
through to helixMeta or generic CEM heuristic, discarding the
snapshot's signal. Added Branch 1b: partial snapshots produce
interpolated score (Math.round(criteria.length / 9 * 70), capped at
70) with overclaim detection. Snapshot remains more authoritative
than CEM helixMeta when both exist.

P2 — Source-check regexes matched comments.
runSourceChecks tested raw .ts and .styles.ts content, so
// this.attachInternals() and /* @media (forced-colors: active) */
registered as real signals and silently inflated source-check
positives. Added stripComments() helper that removes /* ... */ and
// ... before regex matching.

P2 — Single-package layouts silently dropped evidence.
loadManifestIndex and loadVerdictsForRoot hardcoded scanning under
libraryRoot/packages/* only. Single-package repos with manifest /
aaa-verdicts.json at libraryRoot got no evidence (sourceChecks
undefined, verdictSnapshot undefined). Both functions now also probe
libraryRoot itself as a candidate package root.

Fixture update: lib-good/.../aaa-verdicts.json had 8 real SCs + 2
helper rows; raised to 9 real SCs (added 3.3.6) to match the cert
threshold under the new SC-key filter.

All 3426 vitest tests passing. tsc + eslint clean.
Round 5 of local-review feedback against 980b5d8.

P1 — Loose focus-visible regex matched degraded rules.
FOCUS_VISIBLE_LOOSE_RE matched `:focus-visible { outline: none; }`
and similar, so a degraded subclass that removed the outline scored
60/heuristic ("rule found but not 2px"). Defect class 17 was supposed
to assert 0. Tightened to reject outline values of none / 0 / unset /
initial / revert in the lookahead.

P1 — Top-level CEM formAssociated ignored.
scoreFormAssociation only read helixMeta.formAssociated + the source
static-flag regex. Standard CEM carries `formAssociated: boolean` at
the declaration level (cem.ts:91-95). Libraries that emit CEM without
a helixMeta block dropped to unknown despite a clear claim. Both
scoreFormAssociation and scoreFormValidityReporting now accept the
top-level CEM field as a co-equal signal.

P2 — mergeCems libraryId prefix broke evidence lookups.
mergeCems rewrites colliding tags to `packageName:tagName`. Verdicts
and manifest files are keyed by the bare tag, so multi-library
sessions with collisions silently returned no verdictSnapshot and no
sourceChecks. Detector now strips the `packageName:` prefix before
file lookups.

P2 — get_health_summary not threading libraryRoot.
Same divergence as audit_library (round 3) but for a different entry
point. Schema + handler now accept and pass libraryRoot, defaulting
to config.projectRoot when omitted.

All 3426 vitest tests passing. tsc + eslint clean. Round-count 5 with
zero flips on prior decisions — codex finds new code paths each pass,
not contradictions. REA_SKIP_CODEX_REVIEW not warranted.
Round 6 of local-review feedback against c00910d.

P1 — audit_library libraryRoot auto-defaulted to projectRoot.
Round-3's fix (audit-report.ts defaulting to config.projectRoot)
contaminated CDN-loaded libraries with workspace evidence. A library
loaded via CDN has no source on disk, but the auto-default made
audit_library read manifests/verdicts from the workspace root,
producing scores that referenced unrelated files. audit-report now
honors options.libraryRoot only — no fallback. When omitted,
source-dependent dims return unknown and the weighted score honestly
reflects the missing evidence. audit_library MCP tool now exposes
libraryRoot as an explicit (optional) arg.

P2 — get_health_summary same libraryRoot auto-default removed.
Same fix pattern as audit_library — schema accepts libraryRoot, no
auto-default to projectRoot. Tool inputSchema updated to advertise
the new arg with a CDN-aware description.

P2 — form-association N/A guard checked only the static flag.
The "nothing claimed, nothing detected → not-applicable" branch
returned 100/heuristic when !hasStaticFormAssociated alone, hiding
partial FACE implementations that omit the static declaration but
still call attachInternals()/setValidity(). Guard now requires all
three FACE signals to be absent. When any FACE signal is present
without a claim, the scorer returns 50/heuristic with explicit drift
notes (face-signals-without-claim, attachInternals-without-claim,
etc.).

audit_library description also corrected: dropped "11 dimensions"
(now 21) for "all dimensions of the registered scoring system" —
matches the registry-driven reality.

All 3426 vitest tests passing. tsc + eslint clean. 6 rounds, zero
flips — codex continues exploring new paths.
Round 7 of local-review feedback against f10747c.

P1 — Multi-package CEM evidence resolver was unscoped.
Round-5 stripped the `pkg:tag` prefix to make lookups succeed in
namespaced CEMs. But the lookup itself was unscoped — both
`pkg-a:hx-button` and `pkg-b:hx-button` resolved to whichever package
indexed the bare tag first. Multi-package monorepos with collisions
got wrong source/verdicts for their components. detectHelixAaaEvidence
now reads `decl.packageName` (stamped by mergeCems) and scopes the
verdict + manifest lookups to `<libraryRoot>/packages/<packageName>/`
first, falling back to the library-wide search only when no package
scope is available.

P2 — Styles sidecar derivation only matched `.ts`.
Both runSourceChecks and checkAuditMarkdown computed the sidecar path
via `replace(/\.ts$/, '.styles.ts')`. For `.tsx`/`.js`/`.jsx`/`.mts`/
`.mjs`/etc module entries the replace was a no-op, so stylesContent
fused with the component file itself, producing false focus-ring and
forced-colors evidence (and false audit-freshness comparisons).
Generalized to a regex over all supported module extensions.

P2 — form-validity-reporting N/A short-circuit.
Symmetric to the round-3 form-association fix and round-6's N/A guard
tightening. claimedFalse no longer immediately returns 100/verified —
when sourceChecks indicate any FACE signal (static flag,
attachInternals, setValidity), the scorer returns 50/heuristic with
drift notes (setValidity-still-called, attachInternals-still-called,
static-formAssociated-still-present).

All 3426 vitest tests passing. tsc + eslint clean. Round 7, zero
flips — codex still finding distinct defects on new paths.
Round 8 of local-review feedback against 3b29a4a — the single
remaining P2 (verdict: concerns, not blocking).

verdictsCache and manifestIndexCache were module-globals keyed by
libraryRoot only — once a slot was populated, regenerated
aaa-verdicts.json or new components added to custom-elements.json
were ignored for the process lifetime. Long-lived MCP server
deployments (the supported runtime shape) couldn't see fresh
evidence without restart (codex push-gate P2 round 8, 2026-05-10).

Each cache slot now stores a `fingerprint` string derived from the
mtime of every candidate file the slot draws from. On lookup, if the
current fingerprint differs from the cached fingerprint, the slot
rebuilds. Missing files contribute a stable "missing" token so
add/remove cycles also invalidate.

All 3426 vitest tests passing. tsc + eslint clean.

Round count: 8. Trajectory: R1=3, R2=3, R3=3, R4=4, R5=5, R6=3, R7=3,
R8=1. Convergence achieved — last verdict was `concerns` not
`blocking`. Pushing after this fix to seek clean round 9 (and unblock
the local-review push gate).
Round 9 of local-review feedback against 2f5ff51.

P1 — Cross-package evidence contamination.
Round-7's `?? library-wide-fallback` pattern leaked evidence across
packages when a scoped package had no verdict/manifest of its own:
`pkg-b:x-foo` would silently resolve to `pkg-a:x-foo`'s evidence.
Once a packageName is present on the declaration (mergeCems stamped
it), evidence MUST come from that package only — no fallback. If
the scoped package has no verdict/manifest, return no evidence.

P2 — Stale helixMeta cert overrode the verdict snapshot.
summarizeHelixEvidence used `helixMeta.aaa.certified || snapshot.certified`,
which kept a stale CEM cert claim alive even when aaa-verdicts.json
reported uncertified. analyze_accessibility reported AAA-certified
while the scorer treated the component as uncertified. Snapshot is
post-audit (derived from verdicts) and authoritative when present;
helixMeta is the fallback only.

P2 — Forced-colors opt-out misclassified as unknown.
The `!checks → unknown` guard ran before the `claim === false →
verified opt-out` branch. CEM-only / CDN consumers (no libraryRoot,
sourceChecks undefined) with `forcedColorsSupported: false` returned
unknown — turning an explicit declaration into a measurement gap.
Branches reordered: opt-out is CEM-decided and runs first.

All 3426 vitest tests passing. tsc + eslint clean. Round 9 — 3
substantive findings on previously-untouched paths, no flips.
Round 10 of local-review feedback against 5bcaba8.

P1 — packageName is a libraryId, not a directory.
Round-7 used decl.packageName as a filesystem hint to scope evidence
lookups to `<libraryRoot>/packages/<packageName>/`. But mergeCems
stamps packageName from the runtime libraryId ('local',
'design-system', whatever the consumer registered) — not the
on-disk package directory. For the primary helix path (single
library, single package), every merged-CEM lookup resolved to
`packages/local/` and returned no evidence at all. Reverted to
library-wide search with prefix stripping. Multi-library setups with
cross-library tag collisions hit first-match resolution — a known
limitation documented in code. A proper provenance map (libraryId →
packageRoot) in the cemStore is the right longer-term fix.

P3 — aaa-audit-cert OR-precedence on stale CEM cert.
The last holdout from round 9's snapshot-precedence work. Like
summarizeHelixEvidence and scoreWcagConformance, this scorer now
treats the verdict snapshot as authoritative when present and falls
back to helixMeta.aaa.certified only when no snapshot exists.
Consistency across all three call sites.

All 3426 vitest tests passing. tsc + eslint clean. Round 10, zero
flips — the round-7 scope was wrong from the start (codex caught it
two rounds later when the package-name semantics became clearer).
Round 11 of local-review feedback against db325e8 — verdict concerns
(not blocking), but all three findings are real and worth resolving
before push.

P2 — Healthy components surfaced informational notes as issues.
fromDim() copied every scorer note into MultiDimensionalHealth.issues,
including success-path messages like `aaa-cert-fresh-and-supported`
and `not-form-associated-correctly-declared`. Notes now flow into
issues only when score < 100 OR confidence === 'unknown' — failure
modes only.

P2 — Non-form components lost the Form Validity Reporting dim.
When sourceChecks confirmed no FACE signals AND no claim was present,
the scorer returned `unknown/measured:false`, which the aggregator
treats as measured-zero. Non-form components (buttons, cards, banners)
lost the dim entirely under the new dispatcher. Now collapses to
100/verified N/A with `form-validity-not-applicable` note, symmetric
with scoreFormAssociation's same-evidence-shape handling. Test
contract updated.

P3 — JSDoc @keyboard-contract parsing moved into the detector.
scoreApgKeyboard fell back to parseKeyboardContract(decl.description)
inline; detect_helix_evidence and analyze_accessibility only see
HelixAaaEvidence and never saw the fallback parse — so score_component
awarded keyboard credit that the inspection tools couldn't explain.
Detector now parses the JSDoc fallback and stamps it onto
evidence.helixMeta.keyboardContract so every consumer sees the same
data.

All 3426 vitest tests passing. tsc + eslint clean.

Round 11 verdict was already 'concerns' (push-gate allows through);
fixing anyway because the user wants this rock-solid before release.
The push-gate re-ran codex against a different base (last-n-commits
rebased on 5aa502b) and surfaced four new findings the round-12
review (which used origin/dev as base) didn't see. This is the
documented codex non-determinism through the push-gate.

P1 — CEM formAssociated vs stale helixMeta precedence.
Both scoreFormAssociation and scoreFormValidityReporting read the
two signals as co-equal via `||`. When the CEM and helixMeta
disagree, the CEM is the canonical AST-driven signal and should win;
helixMeta is a hand-curated JSDoc tag that can go stale. Now CEM
takes precedence; helixMeta is the fallback only.

P2 — stripComments stripped inside string literals.
The naive `replace(/\/\/[^\n]*/g, ' ')` corrupted real source signals
embedded in strings (e.g. `const url = "https://..."`). Replaced with
a string-aware single-pass walker that preserves string literals
(single, double, template) while still stripping comment bodies.

P2 — Form Validity Reporting N/A from local-file absence.
Mirrors the round-12 P1 (CEM precedence) — the effectiveFormAssociated
derivation now honors the CEM field over stale helixMeta, so
inherited FACE components emitted with `formAssociated:true` get
scored correctly even when their JSDoc doesn't repeat the claim.

P3 — JSDoc `@keyboard-contract` grammar duplication.
Round-11 added a copy of parseKeyboardContract to the detector to
surface the fallback on HelixAaaEvidence. Codex flagged the
duplicated grammar as a drift hazard. Extracted to a new shared
module `dimensions/keyboard-contract-parser.ts`. Both the detector
and the scorer import the same function; verify-extension.ts (the
third consumer) will pick up the same when it's wired in a
follow-up.

All 3426 vitest tests passing. tsc + eslint clean.

This is the documented "codex non-determinism through the push-gate"
pattern from project memory — different base ref, different verdict
for identical commits. The round-12 origin/dev review remains the
authoritative pass; the push-gate found genuinely-real adjacent
issues by reviewing a smaller window with more focus.
P2 — Form Validity N/A required helixMeta presence.
Round-11 collapsed Form Validity Reporting to 100/verified N/A when
sourceChecks confirmed no FACE signals — but this misclassifies
inherited FACE subclasses whose contract lives on a parent's source
file the detector doesn't walk. N/A now requires BOTH no FACE signals
AND a helixMeta block present (the absence of a claim from a
helix-aware author signals the component isn't form-associated;
absence of any annotation leaves the status genuinely unknown).

P2 — Verdicts file merge cross-contaminated tags.
Round-4 added root-level fallback for single-package repos via a
candidate-paths list (packages/<pkg>/aaa-verdicts.json,
libraryRoot/aaa-verdicts.json). The merge then OR'd SC maps across
files when the same tag appeared in both — producing contradictory
verdicts on the same criterion. Now first-valid-source-wins per tag,
with packages-first ordering (root is the fallback catch-all).

Test contract: split the round-11 N/A test into two cases — one with
helixMeta (still N/A) and one without (now unknown).

All 3427 vitest tests passing (added 1 net test). tsc + eslint clean.
P1 — CSS comments inside template-literal styles weren't stripped.
The push-gate-round-1 stripComments rewrite made the walker
string-aware so it preserved string contents (fix for `//` inside
URLs). But that also preserved CSS comments inside tagged template
literals like `css\`...\``, since template literals ARE strings.
focus-visible / forced-colors regexes could match commented-out CSS.
Walker now strips `/* ... */` comments INSIDE template literals
specifically (backtick-delimited) while leaving single/double-quoted
string contents fully intact.

P2 — Silent helixMeta misclassified as proof of non-FACE.
Push-gate-round-2 added a `helixMeta !== undefined` guard on the
Form Validity N/A branch — but that's still too loose. A helixMeta
block silent on form-association doesn't prove the component isn't
FACE; could be an inherited subclass with annotation-light JSDoc.
N/A now requires an affirmative non-FACE signal: either an explicit
non-form ariaPattern (button, link, dialog, banner, tab, etc.) OR
the explicit formAssociated:false claim already handled at branch 1.
Silent helixMeta now leaves form-validity as 0/unknown — honest.

All 3427 vitest tests passing. tsc + eslint clean.
pnpm 11.0.9 was released and dropped Node 20 support. The GH Actions
workflows install pnpm BEFORE setting up Node, so pnpm/action-setup@v4
runs with the runner's default Node 20 and fails self-install. Pinning
packageManager forces the action to install the exact version, matching
the local + lockfile version.

CI run 25650765953 (and 6 others) on dev all failed with
ERR_PNPM_UNSUPPORTED_ENGINE — this commit fixes the chain.
pnpm/action-setup@v4 errored with "Multiple versions of pnpm specified"
because both the workflow's `version: '>=9'` and package.json's new
`packageManager` field set a version. packageManager is authoritative
and the lockfile-driven source — strip the workflow overrides so the
action reads packageManager exclusively.

Affected workflows: cem-validate, build, lint, format, security, test
(6 files).
20 pre-existing changesets cited '@helixir/core' (which has been in the
changeset ignore list since 2026-03-13's a6475b0). The mixed entries
broke `changeset status` and would have blocked `changeset version` on
the next release roll. Each changeset now cites 'helixir' directly with
the same bump level — '@helixir/core' bumps automatically via
updateInternalDependencies when helixir bumps.

No semver semantics changed; this is a metadata cleanup.

`pnpm changeset status` now succeeds and reports the expected bumps:
  helixir → minor (this dimensional-upgrade release)
  helixir-vscode → patch (workspace consumer)
  @helixui/mcp → major (workspace consumer; major bump from inheritance
    chain — release manager may need to gate)
CEM Validate caught stale custom-elements.json — the new exports from
the dimensional upgrade (Phase 1+2 sources under packages/core/src/
handlers/evidence/ and handlers/dimensions/) needed to land in the
manifest. Ran `pnpm run cem:generate`.

Also adds pnpm override `fast-uri: '>=3.1.2'` to clear the security
audit ReDoS finding (GHSA-v39h-62p7-jpjc — fast-uri@3.1.0 in
@modelcontextprotocol/sdk + @bookedsolid/rea transitive trees).
Remaining high-severity audit findings (tar, picomatch, path-to-regexp,
lodash, flatted) are pre-existing on dev (security workflow has been
red since 2026-05-04) and out of scope for this PR.
promote: dev → staging (helixir 0.6.0 — Accessibility dimensional upgrade)
@himerus himerus changed the title release: staging → main (symlink fix, audit features, 48 commits) release: staging → main (helixir 0.6.0 — Accessibility dimensional upgrade, 261 commits) May 11, 2026
@himerus himerus merged commit 340c0dc into main May 11, 2026
30 of 32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant