Conversation
…r-returns-todo fix: replace TODO placeholder with var() fallback in theme handler
…adme-social-card-to
…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
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Accessibilityscoring 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 surfaceunknownconfidence on non-helix libraries rather than gaslighting A's.Merging this PR triggers
changesets/action@v1which will open the "chore: version packages" version PR with bumped manifests + changelog entries.Release shape (per
pnpm changeset status)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)Forced Colors Mode(important, 1)Form Validity Reporting(advanced, 1)AAA Audit Self-Certification(advanced, 0 — informational)scoring.weights.accessibilityconfig key still works through 0.7.x via deprecation fan-outNew MCP tool:
detect_helix_evidence(tagName, libraryRoot?, libraryId?)Existing tools gain optional
libraryRoot:score_component,score_all_components,audit_library,analyze_accessibility,get_health_summaryReal-world catch surfaced by the new scorers: helix
hx-buttondeclaresstatic formAssociated = truein its CEM but never callsattachInternals()/setValidity()— Form Association scorer flags 20/heuristic withattach-internals-missing+set-validity-not-callednotes. Documented in defect-corpus class 18 in bst-cto-kb.Engineering process
The dimensional upgrade went through:
REA_SKIP_CODEX_REVIEWskip on push-gate round 4 per the project memory's 3-flip rule (packageName-scoping decision flipped R7→R10→push-gate-R4)Test plan
pnpm exec vitest run— 3429 passing (was 3298), 0 failingpnpm exec tsc --noEmit— cleanpnpm exec eslint .— clean on new codepnpm run build— cleanpnpm changeset status— clean (was broken by 20 pre-existing mixed @helixir/core changesets that this PR migrated)tests/__fixtures__/smoke/pnpm changeset publish→ helixir 0.6.0 on npm