Skip to content

release: staging → main (16 features, 4 epics)#224

Closed
himerus wants to merge 36 commits intomainfrom
staging
Closed

release: staging → main (16 features, 4 epics)#224
himerus wants to merge 36 commits intomainfrom
staging

Conversation

@himerus
Copy link
Copy Markdown
Contributor

@himerus himerus commented Mar 26, 2026

Release: staging → main

Features included

Epics closed

  • Release Readiness: Pre-Release Quality Gate
  • Test Coverage Hardening
  • Code Quality Sweep
  • VS Code Extension: Marketplace Distribution

Promotion type

staging → main (merge commit, no squash)

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added VS Code extension for integrated web component intelligence
    • Added TypeGenerate tool for type declaration generation
    • Added Theme management tools: create and apply design tokens
    • Added Scaffold tool for rapid component creation
    • Added Extend tool for component inheritance
    • Expanded Styling toolset with 29+ validators and diagnostics
  • Improvements

    • Updated MCP Protocol SDK to v1.27.1
    • Enhanced error handling with better categorization and messages
    • Improved configuration flexibility with extended environment variable support
    • Documented tooling capabilities in expanded references
  • Tests

    • Added comprehensive test coverage for all tools and analyzers

himerus and others added 30 commits March 26, 2026 05:49
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace webp-only social card with PNG exported from Playwright.
helixir purple (#8b5cf6) branded at 1200x630.

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

fix: align @modelcontextprotocol/sdk versions across monorepo packages
…dcomponent-and

fix: wire scaffold_component and extend_component into MCP server
Adds the helixir-vscode VS Code extension package at packages/vscode/.
The extension registers helixir as an MCP server definition provider
(vscode.lm, VS Code ≥ 1.99.0) so AI assistants receive full component
library awareness automatically when a workspace is opened.

Files created:
- packages/vscode/package.json — extension manifest with publisher,
  mcpServerDefinitionProviders contribution, Run Health Check command,
  helixir.configPath setting, and vsce/ovsx scripts
- packages/vscode/tsconfig.json — extends root tsconfig, noEmit for
  type-check-only (esbuild handles transpilation)
- packages/vscode/esbuild.config.mjs — dual-bundle config: extension.js
  (CJS, vscode externalized) + mcp-server.js (ESM, bundles helixir)
- packages/vscode/src/extension.ts — activate/deactivate exports, wires
  MCP provider and Run Health Check command
- packages/vscode/src/mcpProvider.ts — registerMcpServerDefinitionProvider
  call; spawns bundled mcp-server.js via stdio with MCP_WC_PROJECT_ROOT
  set to the current workspace folder; degrades gracefully on older VS Code
- packages/vscode/src/mcp-server-entry.ts — imports helixir/mcp and calls
  main(); bundled into the self-contained dist/mcp-server.js
- packages/vscode/.vscodeignore — excludes src, tsconfig, node_modules,
  and build artefacts from the .vsix package
- packages/vscode/README.md — marketplace listing with setup, config
  reference, commands table, and troubleshooting guide

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ckagesvscode-extension

feat: scaffold packages/vscode VS Code extension MVP
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fault case

Replaces the '/* TODO: set value */' literal in the default branch of
lightPlaceholder() with var(${tokenName}), which produces valid CSS that
degrades gracefully when a token category is unknown.

Adds a test case that exercises the default code path by creating a CEM
with a token name that does not match any known CATEGORY_PATTERNS entry,
causing it to land in the 'other' bucket and hit the default switch case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add comprehensive test coverage for packages/core/src/tools/styling.ts —
previously the single largest untested file in the codebase (1089 lines,
zero coverage).

- Tests all 29 MCP tool handlers via mocked dependencies
- Verifies happy paths, missing-arg validation errors, and error propagation
- Covers isStylingTool guard and the handleStylingCall dispatcher
- Designed to achieve 80%+ line coverage per vitest thresholds

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…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
himerus and others added 6 commits March 26, 2026 18:20
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)
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Walkthrough

Introduces a VS Code extension for the Helixir MCP server with comprehensive CLI/config refactoring, error handling standardization via MCPError, tool dispatcher support for scaffold/extend operations, and extensive test coverage across analyzers and tool handlers. Documentation updated to reflect 87+ tools (from 30+).

Changes

Cohort / File(s) Summary
Documentation & Configuration
README.md, packages/vscode/README.md, packages/vscode/.vscodeignore
Updated main README with social card, status badges, and expanded tool count (30+ → 87+); added VS Code extension README with activation/configuration flow; configured package ignore patterns for extension publishing.
Dependency & Script Updates
package.json, packages/core/package.json, packages/mcp/package.json, build, node_modules
Updated MCP SDK versions from ^1.26.0 to ^1.27.1; added typecheck script alias; added path reference files.
Core Configuration Refactoring
packages/core/src/config.ts
Removed legacy mcpwc.config.json fallback; refactored environment variable handling from explicit if blocks to mapping-driven loops for string and nullable fields; expanded nullable field coverage including MCP_WC_CDN_STYLESHEET.
Error Handling Standardization
packages/core/src/handlers/cem.ts, packages/core/src/handlers/dependencies.ts, packages/core/src/handlers/extend.ts
Replaced generic Error with MCPError + ErrorCategory.VALIDATION/NOT_FOUND for component/token resolution failures, improving error categorization and consistency.
Theme Handler Logic Update
packages/core/src/handlers/theme.ts
Changed lightPlaceholder() default case from TODO comment string to CSS var() reference for unrecognized token categories.
CLI & MCP Tool Dispatch Updates
src/cli/index.ts, src/mcp/index.ts
Strengthened positional-argument validation with explicit length checks and type casts; integrated handleToolError for consistent error reporting; registered scaffold and extend tool groups with corresponding dispatch logic and validation gates.
VS Code Extension: Entry Points & Configuration
packages/vscode/src/extension.ts, packages/vscode/src/mcp-server-entry.ts, packages/vscode/src/mcpProvider.ts, packages/vscode/tsconfig.json, packages/vscode/esbuild.config.mjs, packages/vscode/package.json
Added extension lifecycle management (activate/deactivate), MCP server definition provider, bundled MCP server entry with error handling and shebang, dual-bundle esbuild configuration (extension + server), TypeScript config, and extension manifest with activation hook, command registrations, and configuration settings.
VS Code Extension: Commands
packages/vscode/src/commands/configureCursorWindsurf.ts
Implemented editor detection (Cursor vs. Windsurf) and auto-configuration of mcp.json with helixir server entry, including directory creation and config merging logic.
Analyzer Test Suites
tests/handlers/analyzers/api-surface.test.ts, tests/handlers/analyzers/css-architecture.test.ts, tests/handlers/analyzers/event-architecture.test.ts, tests/handlers/analyzers/mixin-resolver.test.ts, tests/handlers/analyzers/naming-consistency.test.ts, tests/handlers/analyzers/slot-architecture.test.ts, tests/handlers/analyzers/source-accessibility.test.ts, tests/handlers/analyzers/type-coverage.test.ts
Comprehensive test coverage for all analyzer modules, validating scoring logic, confidence classification, sub-metric structure, edge cases (empty inputs, partial documentation), and bounds checking (0–100 scores).
Tool Dispatcher Test Suites
tests/tools/bundle.test.ts, tests/tools/cdn.test.ts, tests/tools/composition.test.ts, tests/tools/extend.test.ts, tests/tools/framework.test.ts, tests/tools/scaffold.test.ts, tests/tools/story.test.ts, tests/tools/styling.test.ts, tests/tools/theme.test.ts, tests/tools/tokens.test.ts, tests/tools/typegenerate.test.ts, tests/tools/typescript.test.ts, tests/tools/validate.test.ts
Extensive test coverage for tool dispatchers, validating input schema enforcement, argument validation, success/error response handling, handler invocation, and error propagation across all supported tools.
Handler & Theme Tests
tests/handlers/theme.test.ts
Added test validating that unmatched token names generate CSS var() fallback instead of TODO placeholder.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • #77: Hardened path containment for CEM-derived file reads with traversal protection tests and resolver behavior.
  • #194: Implemented the extend_component feature with handler, tool dispatch, and comprehensive test coverage.
  • #192: Added theme handlers and tools (createTheme/applyThemeTokens) with dispatcher and test integration.

Suggested labels

skip-changeset

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description provides comprehensive information including features, epics, and promotion details, but does not follow the repository's required template structure with sections like 'Type of change', 'Tests added', and the required checklist items. Restructure the description to match the template: add 'Type of change' checkboxes, 'Related issue' section, 'Tests added' checkboxes, and complete the required checklist with verification status for all items (DCO, type-check, lint, format, tests, build, examples, CHANGELOG).
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'release: staging → main (16 features, 4 epics)' clearly describes the primary change: a release/promotion from staging to main with feature and epic counts.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch staging

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 23

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@build`:
- Line 1: Remove the accidental path-marker file named "build" from version
control (delete the file and run git rm --cached build if already committed) and
update .gitignore to prevent re-adding it (add a "build" or "/build" entry to
ignore the artifact); ensure you only ignore the file and not needed build
outputs if you intend to keep a build/ directory tracked—adjust the .gitignore
entry accordingly.

In `@node_modules`:
- Line 1: Remove the accidental tracked file named "node_modules" that contains
a local absolute path: run git rm --cached node_modules (or git rm node_modules
if you want it removed from disk), commit the removal, and push; then add
"node_modules/" to .gitignore (if not already present) and commit that change to
prevent re-adding; finally verify no other tracked node_modules artifacts remain
(git ls-files | grep node_modules) and remove them similarly.

In `@packages/core/package.json`:
- Line 19: The pnpm lockfile is out of sync with the updated dependency
`@modelcontextprotocol/sdk` in package manifests (packages/core and packages/mcp);
regenerate and commit an updated pnpm-lock.yaml by running a workspace install
so the specifiers change from ^1.26.0 to ^1.27.1. From the repository root run
the pnpm workspace install (e.g., pnpm install or pnpm -w install) to update
pnpm-lock.yaml, verify the lock now contains `@modelcontextprotocol/sdk`@^1.27.1
for packages/core and packages/mcp, then commit the updated pnpm-lock.yaml.

In `@packages/vscode/README.md`:
- Line 55: Update the outdated "30+ tools" reference in the README sentence
starting with "The server reads your `custom-elements.json`..." to the current
tools count (replace "30+ tools" with the accurate number or a dynamic phrase
like "40+ tools" or "dozens of tools"), and ensure the new count matches other
mentions in the repository so the README remains consistent.
- Line 10: The README line currently reads "**30+ MCP tools** — component
discovery, health scoring, breaking-change detection, TypeScript diagnostics,
design token lookup, and more"; update that phrase to match the main README by
replacing "30+ MCP tools" with "87+ MCP tools" (i.e., change the string "**30+
MCP tools**" to "**87+ MCP tools**" so the two READMEs are consistent).
- Line 64: Documentation references the environment variable MCP_WC_CONFIG_PATH
but the config system doesn't map or expose it; either remove the doc line or
add mapping and schema support. To fix, add MCP_WC_CONFIG_PATH to the
ENV_MAP_STRING mapping in the config loader (ENV_MAP_STRING) and add a
corresponding property on the McpWcConfig interface/schema, then read it into
the config initialization so code in mcpProvider.ts (which sets
MCP_WC_CONFIG_PATH) is supported; alternatively, remove the MCP_WC_CONFIG_PATH
entry from packages/vscode/README.md if you prefer not to support it.

In `@packages/vscode/src/commands/configureCursorWindsurf.ts`:
- Around line 77-88: The code in configureCursorWindsurf.ts currently uses
blocking fs.existsSync and fs.readFileSync when reading configFilePath; change
this to use asynchronous APIs (either fs.promises or vscode.workspace.fs) so the
extension host isn't blocked: replace existsSync/readFileSync/try-catch with an
async/await flow that uses fs.promises.access or workspace.fs.stat to check
existence, then read the file with fs.promises.readFile or
workspace.fs.readFile, parse JSON inside a try/catch and on parse error set
existing = { mcpServers: {} } exactly as before; make the enclosing function (or
the call site) async and await the read so behavior and error handling for
malformed JSON remain the same.
- Around line 75-105: The mkdirSync and writeFileSync calls can throw and need
to be wrapped in try/catch to avoid crashing; around the block that ensures the
config directory and writes the JSON (the fs.mkdirSync(configDir, { recursive:
true }) and fs.writeFileSync(configFilePath, JSON.stringify(existing, null, 2) +
'\n', 'utf8')), add a try/catch that logs or surfaces the error (e.g., using the
existing logger or vscode.window.showErrorMessage) and returns/aborts the
operation on failure; preserve the existing variable names (existing,
existing.mcpServers['helixir'], configDir, configFilePath) and ensure any thrown
error message is included in the log shown to the user.

In `@packages/vscode/src/mcp-server-entry.ts`:
- Around line 13-16: Add a global unhandled promise rejection handler so
asynchronous failures outside main() are logged consistently: register
process.on('unhandledRejection', ...) early (e.g., next to the main().catch(...)
block) to write the rejection reason to stderr via process.stderr.write with a
descriptive prefix and then exit with code 1 (mirroring the existing main()
error handling); ensure the handler handles unknown rejection shapes (e.g.,
convert reason to string) and avoids swallowing the error.

In `@packages/vscode/src/mcpProvider.ts`:
- Around line 16-52: The fallback to process.cwd() inside
provideMcpServerDefinitions (used to set workspaceFolder and
MCP_WC_PROJECT_ROOT) can point to an unrelated directory when no workspace is
open; update registerMcpProvider to detect this fallback and notify the user
(e.g., use vscode.window.showWarningMessage or console.warn) explaining that no
workspace folder is open and MCP will use process.cwd(), and include the
computed workspaceFolder value in the message so users know which path is being
used; keep the MCP_WC_PROJECT_ROOT env behavior unchanged but ensure the warning
is only shown when vscode.workspace.workspaceFolders is undefined or empty.

In `@README.md`:
- Line 20: The tools badge in the README currently shows "tools-73" which
conflicts with the documented "87+ MCP tools"; update the badge text to
"tools-87+" (i.e., change the badge fragment in the markdown line containing
[![Tools](https://img.shields.io/badge/tools-73-purple)] to
[![Tools](https://img.shields.io/badge/tools-87+-purple)]) so the displayed
count matches the "87+ MCP tools out of the box" statement.

In `@tests/handlers/analyzers/naming-consistency.test.ts`:
- Around line 463-473: The test currently inherits cssPrefixConfidence=1.0 from
HX_CONVENTIONS so the 'verified' result may come from CSS prefix confidence;
update the test setup: create highConfConventions by spreading HX_CONVENTIONS
but explicitly set cssPrefixConfidence to a low value (e.g., 0.0 or below the
verified threshold) and set eventPrefixConfidence to 0.9, then call
analyzeNamingConsistency(decl, highConfConventions) and assert
result!.confidence === 'verified' so the outcome proves the event-prefix
threshold behavior; reference variables: highConfConventions, HX_CONVENTIONS,
eventPrefixConfidence, cssPrefixConfidence, analyzeNamingConsistency, makeDecl,
result!.confidence.

In `@tests/handlers/analyzers/slot-architecture.test.ts`:
- Around line 278-284: The test currently only checks that
analyzeSlotArchitecture(JSDOC_SLOT_DECL) returns a slot named 'icon', but
doesn't assert that the JSDoc-derived type was actually detected; update the
test that defines iconSlot (from analyzeSlotArchitecture and JSDOC_SLOT_DECL) to
assert the type detection by checking the relevant property (e.g.,
expect(iconSlot?.hasTypeConstraint).toBe(true) or assert the slot-type
metric/value on iconSlot) so the test fails if jsdocTags parsing stops
populating type constraints.

In `@tests/handlers/analyzers/source-accessibility.test.ts`:
- Around line 208-211: The test 'detects screenReaderSupport from .sr-only
class' currently uses SCREEN_READER_SOURCE which also contains
aria-labelledby/aria-describedby and masks regressions; replace it with a
minimal fixture that only contains an element with the .sr-only class (e.g.,
define SR_ONLY_SOURCE containing a single element with class="sr-only") and call
scanSourceForA11yPatterns(SR_ONLY_SOURCE) in the test, asserting
markers.screenReaderSupport is true so the test only exercises the .sr-only
detector.
- Around line 452-475: The tests use a hard-coded WORKTREE causing
non-deterministic results; replace the machine-local WORKTREE usage with a
self-contained temporary fixture root (or a repo-relative test-fixtures
directory) created inside the test, create the minimal files needed (e.g.
packages/core/src/config.ts and/or config.js) and point
resolveComponentSourceFilePath at that fixture; update the assertions to
explicitly expect a non-null resolved path for the .js -> .ts case (e.g.
expect(result).not.toBeNull() and expect(result!.endsWith('.ts') ||
result!.endsWith('.js')).toBe(true)), and ensure the other cases use the temp
fixture so null results are for the intended reasons (nonexistent path and
outside-root) rather than relying on a local WORKTREE.

In `@tests/handlers/analyzers/type-coverage.test.ts`:
- Around line 136-140: The test description and assertion disagree: the spec
says it should "return null when only methods exist" but the assertion expects a
non-null score; update the test so they match by either renaming the test to
reflect that methods-only is scoreable (change the it description) or invert the
expectation to expect(result).toBeNull(); locate the test using
analyzeTypeCoverage and the METHODS_ONLY fixture in the failing it(...) block
and apply the chosen fix so the description and assertion are consistent.
- Around line 221-231: The test currently only asserts eventMetric exists but
doesn't verify bare "Event" is treated as untyped; call
analyzeTypeCoverage(BARE_EVENT_TYPE) and replace
expect(eventMetric).toBeDefined() with an assertion that the "Event typed
payloads" metric counts exactly the one CustomEvent<T> (e.g.
expect(eventMetric!.typedCount).toBe(1)) and that the total events equals 3
(e.g. expect(eventMetric!.total).toBe(3)); if the metric uses different property
names, assert the same contract using the available fields (such as value,
count, typed, typedCount) on the eventMetric object so the test fails if bare
'Event' is counted as typed.

In `@tests/tools/composition.test.ts`:
- Around line 217-219: Remove the redundant setup that clears mocks for the
single-test suite: delete the beforeEach block that calls vi.clearAllMocks(),
since the suite contains only one test which uses mockImplementationOnce and
does not require global mock clearing; locate the beforeEach(...) containing
vi.clearAllMocks and remove it to reduce unnecessary overhead.
- Around line 73-75: The test currently uses a non-null assertion when
retrieving the tool definition (COMPOSITION_TOOL_DEFINITIONS.find(...)!) which
can throw an unhelpful runtime error; instead, first retrieve the definition
into a variable (e.g., def), assert its existence with expect(def).toBeDefined()
(or expect(def).not.toBeUndefined()), and only then access its properties
(inputSchema.required) — you can then narrow the type or use a local const
(e.g., const defDefined = def!) after the existence assertion to continue with
expect(defDefined.inputSchema.required).toContain('tagNames').
- Around line 109-116: Before calling JSON.parse in the 'returns JSON-parseable
content' test, assert the dispatcher returned success and not an error: check
that handleCompositionCall(...) result has no error (e.g.
expect(result.error).toBeFalsy() or expect(result.status).toBe('success')) and
that result.content and result.content[0] exist (e.g.
expect(result.content?.length).toBeGreaterThan(0)) before parsing
result.content[0].text.
- Around line 118-143: These three identical test cases in
tests/tools/composition.test.ts repeatedly call handleCompositionCall with
different tagNames arrays; collapse them into a table-driven test (e.g.,
test.each or it.each) that iterates over the valid tagNames arrays and asserts
result.isError is falsy. Locate the three it blocks titled 'accepts 2 tagNames',
'accepts 3 tagNames', and 'accepts 4 tagNames (maximum)' and replace them with a
single parametrized test that calls
handleCompositionCall('get_composition_example', { tagNames: tags }, RICH_CEM)
for each tags array and expects result.isError.toBeFalsy().

In `@tests/tools/tokens.test.ts`:
- Around line 17-31: The mocks for getDesignTokens and findToken set a hardcoded
count which can drift from the actual filtered tokens; update both mock
implementations (getDesignTokens and findToken) to compute count from the
filtered tokens array (e.g., const filtered = [ ... ].filter(...); return {
tokens: filtered, count: filtered.length, ... }) so count always matches the
returned tokens for the category filter in getDesignTokens and the query filter
in findToken.

In `@tests/tools/validate.test.ts`:
- Around line 154-203: The HTML boundary tests miscount because the wrapper tags
("<hx-button>" and "</hx-button>") add extra characters to longHtml and
tooLongHtml; update the fixtures in the tests that call handleValidateCall
(variables longHtml and tooLongHtml) so the repeated 'x'.repeat(...) accounts
for the wrapper length (i.e. compute repeat count = desiredTotalLength -
wrapperLength) or build the repeated content first and then wrap it, ensuring
the final string lengths are exactly 49,980 and 50,000 (or your intended
boundaries) before asserting the acceptance/rejection behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 95c46f60-ef35-4242-bd25-eb66d97f0a23

📥 Commits

Reviewing files that changed from the base of the PR and between 35b76a7 and b476e1c.

⛔ Files ignored due to path filters (1)
  • assets/social-card.png is excluded by !**/*.png
📒 Files selected for processing (45)
  • README.md
  • assets/social-card.webp
  • build
  • node_modules
  • package.json
  • packages/core/package.json
  • packages/core/src/config.ts
  • packages/core/src/handlers/cem.ts
  • packages/core/src/handlers/dependencies.ts
  • packages/core/src/handlers/extend.ts
  • packages/core/src/handlers/theme.ts
  • packages/mcp/package.json
  • packages/vscode/.vscodeignore
  • packages/vscode/README.md
  • packages/vscode/esbuild.config.mjs
  • packages/vscode/package.json
  • packages/vscode/src/commands/configureCursorWindsurf.ts
  • packages/vscode/src/extension.ts
  • packages/vscode/src/mcp-server-entry.ts
  • packages/vscode/src/mcpProvider.ts
  • packages/vscode/tsconfig.json
  • src/cli/index.ts
  • src/mcp/index.ts
  • tests/handlers/analyzers/api-surface.test.ts
  • tests/handlers/analyzers/css-architecture.test.ts
  • tests/handlers/analyzers/event-architecture.test.ts
  • tests/handlers/analyzers/mixin-resolver.test.ts
  • tests/handlers/analyzers/naming-consistency.test.ts
  • tests/handlers/analyzers/slot-architecture.test.ts
  • tests/handlers/analyzers/source-accessibility.test.ts
  • tests/handlers/analyzers/type-coverage.test.ts
  • tests/handlers/theme.test.ts
  • tests/tools/bundle.test.ts
  • tests/tools/cdn.test.ts
  • tests/tools/composition.test.ts
  • tests/tools/extend.test.ts
  • tests/tools/framework.test.ts
  • tests/tools/scaffold.test.ts
  • tests/tools/story.test.ts
  • tests/tools/styling.test.ts
  • tests/tools/theme.test.ts
  • tests/tools/tokens.test.ts
  • tests/tools/typegenerate.test.ts
  • tests/tools/typescript.test.ts
  • tests/tools/validate.test.ts

@@ -0,0 +1 @@
/Volumes/Development/booked/helixir/build No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove accidental build path-marker file.

Line 1 adds a file named build with a local absolute path. This can conflict with expected build/ directory outputs and should be removed from version control.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@build` at line 1, Remove the accidental path-marker file named "build" from
version control (delete the file and run git rm --cached build if already
committed) and update .gitignore to prevent re-adding it (add a "build" or
"/build" entry to ignore the artifact); ensure you only ignore the file and not
needed build outputs if you intend to keep a build/ directory tracked—adjust the
.gitignore entry accordingly.

@@ -0,0 +1 @@
/Volumes/Development/booked/helixir/node_modules No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove accidental local-path artifact (node_modules file).

Line 1 adds a tracked file named node_modules containing an absolute local path. This can block dependency installation (directory/file collision) and leaks local filesystem details. Remove this file from the repo.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@node_modules` at line 1, Remove the accidental tracked file named
"node_modules" that contains a local absolute path: run git rm --cached
node_modules (or git rm node_modules if you want it removed from disk), commit
the removal, and push; then add "node_modules/" to .gitignore (if not already
present) and commit that change to prevent re-adding; finally verify no other
tracked node_modules artifacts remain (git ls-files | grep node_modules) and
remove them similarly.

"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.26.0",
"@modelcontextprotocol/sdk": "^1.27.1",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Checking `@modelcontextprotocol/sdk` specifiers in manifests and lockfile..."
rg -n '"@modelcontextprotocol/sdk"\s*:' package.json packages/core/package.json packages/mcp/package.json pnpm-lock.yaml

Repository: bookedsolidtech/helixir

Length of output: 348


🏁 Script executed:

#!/bin/bash
# Search for `@modelcontextprotocol/sdk` in pnpm-lock.yaml with more flexible pattern
rg -n "modelcontextprotocol" pnpm-lock.yaml | head -20

Repository: bookedsolidtech/helixir

Length of output: 279


🏁 Script executed:

#!/bin/bash
# Check context around lines 18, 73, 104 to see what versions are referenced
rg -B2 -A2 "^\s*'@modelcontextprotocol/sdk':" pnpm-lock.yaml | head -40

Repository: bookedsolidtech/helixir

Length of output: 461


Regenerate pnpm-lock.yaml to sync with the updated SDK version.

The manifest files specify @modelcontextprotocol/sdk@^1.27.1, but pnpm-lock.yaml still has stale specifiers:

  • packages/core: specifier ^1.26.0 (should be ^1.27.1)
  • packages/mcp: specifier ^1.26.0 (should be ^1.27.1)

CI with frozen-lockfile will fail until these specifiers are regenerated and committed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/package.json` at line 19, The pnpm lockfile is out of sync with
the updated dependency `@modelcontextprotocol/sdk` in package manifests
(packages/core and packages/mcp); regenerate and commit an updated
pnpm-lock.yaml by running a workspace install so the specifiers change from
^1.26.0 to ^1.27.1. From the repository root run the pnpm workspace install
(e.g., pnpm install or pnpm -w install) to update pnpm-lock.yaml, verify the
lock now contains `@modelcontextprotocol/sdk`@^1.27.1 for packages/core and
packages/mcp, then commit the updated pnpm-lock.yaml.

## Features

- **MCP server auto-registration** — the helixir MCP server starts automatically with VS Code, no manual configuration required
- **30+ MCP tools** — component discovery, health scoring, breaking-change detection, TypeScript diagnostics, design token lookup, and more
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent tool count with main README.

This README says "30+ MCP tools" but the main README states "87+ MCP tools". Update for consistency.

📝 Suggested fix
-- **30+ MCP tools** — component discovery, health scoring, breaking-change detection, TypeScript diagnostics, design token lookup, and more
+- **87+ MCP tools** — component discovery, health scoring, breaking-change detection, TypeScript diagnostics, design token lookup, and more
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- **30+ MCP tools** — component discovery, health scoring, breaking-change detection, TypeScript diagnostics, design token lookup, and more
- **87+ MCP tools** — component discovery, health scoring, breaking-change detection, TypeScript diagnostics, design token lookup, and more
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode/README.md` at line 10, The README line currently reads "**30+
MCP tools** — component discovery, health scoring, breaking-change detection,
TypeScript diagnostics, design token lookup, and more"; update that phrase to
match the main README by replacing "30+ MCP tools" with "87+ MCP tools" (i.e.,
change the string "**30+ MCP tools**" to "**87+ MCP tools**" so the two READMEs
are consistent).


When the extension activates, it registers a **MCP server definition provider** (`helixir`) with VS Code's language model API (`vscode.lm`). VS Code spawns the bundled helixir MCP server (`dist/mcp-server.js`) as a child process over stdio.

The server reads your `custom-elements.json` and exposes 30+ tools that AI models can call to look up component APIs, run health scans, generate type declarations, and more.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Another reference to outdated tool count.

Line 55 also mentions "30+ tools" — update to match the current count.

📝 Suggested fix
-The server reads your `custom-elements.json` and exposes 30+ tools that AI models can call to look up component APIs, run health scans, generate type declarations, and more.
+The server reads your `custom-elements.json` and exposes 87+ tools that AI models can call to look up component APIs, run health scans, generate type declarations, and more.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
The server reads your `custom-elements.json` and exposes 30+ tools that AI models can call to look up component APIs, run health scans, generate type declarations, and more.
The server reads your `custom-elements.json` and exposes 87+ tools that AI models can call to look up component APIs, run health scans, generate type declarations, and more.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vscode/README.md` at line 55, Update the outdated "30+ tools"
reference in the README sentence starting with "The server reads your
`custom-elements.json`..." to the current tools count (replace "30+ tools" with
the accurate number or a dynamic phrase like "40+ tools" or "dozens of tools"),
and ensure the new count matches other mentions in the repository so the README
remains consistent.

Comment on lines +109 to +116
it('returns JSON-parseable content', () => {
const result = handleCompositionCall(
'get_composition_example',
{ tagNames: ['hx-button'] },
FAKE_CEM,
);
expect(() => JSON.parse(result.content[0].text)).not.toThrow();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Assert success before parseability check.

If the dispatcher ever returns a JSON-formatted error payload, this test could pass while behavior is still wrong. Add an explicit non-error assertion first.

Suggested fix
   it('returns JSON-parseable content', () => {
     const result = handleCompositionCall(
       'get_composition_example',
       { tagNames: ['hx-button'] },
       FAKE_CEM,
     );
+    expect(result.isError).toBe(false);
     expect(() => JSON.parse(result.content[0].text)).not.toThrow();
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/tools/composition.test.ts` around lines 109 - 116, Before calling
JSON.parse in the 'returns JSON-parseable content' test, assert the dispatcher
returned success and not an error: check that handleCompositionCall(...) result
has no error (e.g. expect(result.error).toBeFalsy() or
expect(result.status).toBe('success')) and that result.content and
result.content[0] exist (e.g. expect(result.content?.length).toBeGreaterThan(0))
before parsing result.content[0].text.

Comment on lines +118 to +143
it('accepts 2 tagNames', () => {
const result = handleCompositionCall(
'get_composition_example',
{ tagNames: ['hx-card', 'hx-button'] },
RICH_CEM,
);
expect(result.isError).toBeFalsy();
});

it('accepts 3 tagNames', () => {
const result = handleCompositionCall(
'get_composition_example',
{ tagNames: ['hx-card', 'hx-button', 'hx-badge'] },
RICH_CEM,
);
expect(result.isError).toBeFalsy();
});

it('accepts 4 tagNames (maximum)', () => {
const result = handleCompositionCall(
'get_composition_example',
{ tagNames: ['hx-card', 'hx-button', 'hx-badge', 'hx-icon'] },
RICH_CEM,
);
expect(result.isError).toBeFalsy();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Refactor repetitive valid-input tests into a table.

These three tests are structurally identical and can be collapsed to reduce maintenance noise.

Refactor example
-  it('accepts 2 tagNames', () => {
-    const result = handleCompositionCall(
-      'get_composition_example',
-      { tagNames: ['hx-card', 'hx-button'] },
-      RICH_CEM,
-    );
-    expect(result.isError).toBeFalsy();
-  });
-
-  it('accepts 3 tagNames', () => {
-    const result = handleCompositionCall(
-      'get_composition_example',
-      { tagNames: ['hx-card', 'hx-button', 'hx-badge'] },
-      RICH_CEM,
-    );
-    expect(result.isError).toBeFalsy();
-  });
-
-  it('accepts 4 tagNames (maximum)', () => {
-    const result = handleCompositionCall(
-      'get_composition_example',
-      { tagNames: ['hx-card', 'hx-button', 'hx-badge', 'hx-icon'] },
-      RICH_CEM,
-    );
-    expect(result.isError).toBeFalsy();
-  });
+  [
+    ['accepts 2 tagNames', ['hx-card', 'hx-button']],
+    ['accepts 3 tagNames', ['hx-card', 'hx-button', 'hx-badge']],
+    ['accepts 4 tagNames (maximum)', ['hx-card', 'hx-button', 'hx-badge', 'hx-icon']],
+  ].forEach(([name, tagNames]) => {
+    it(name as string, () => {
+      const result = handleCompositionCall(
+        'get_composition_example',
+        { tagNames: tagNames as string[] },
+        RICH_CEM,
+      );
+      expect(result.isError).toBeFalsy();
+    });
+  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('accepts 2 tagNames', () => {
const result = handleCompositionCall(
'get_composition_example',
{ tagNames: ['hx-card', 'hx-button'] },
RICH_CEM,
);
expect(result.isError).toBeFalsy();
});
it('accepts 3 tagNames', () => {
const result = handleCompositionCall(
'get_composition_example',
{ tagNames: ['hx-card', 'hx-button', 'hx-badge'] },
RICH_CEM,
);
expect(result.isError).toBeFalsy();
});
it('accepts 4 tagNames (maximum)', () => {
const result = handleCompositionCall(
'get_composition_example',
{ tagNames: ['hx-card', 'hx-button', 'hx-badge', 'hx-icon'] },
RICH_CEM,
);
expect(result.isError).toBeFalsy();
});
[
['accepts 2 tagNames', ['hx-card', 'hx-button']],
['accepts 3 tagNames', ['hx-card', 'hx-button', 'hx-badge']],
['accepts 4 tagNames (maximum)', ['hx-card', 'hx-button', 'hx-badge', 'hx-icon']],
].forEach(([name, tagNames]) => {
it(name as string, () => {
const result = handleCompositionCall(
'get_composition_example',
{ tagNames: tagNames as string[] },
RICH_CEM,
);
expect(result.isError).toBeFalsy();
});
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/tools/composition.test.ts` around lines 118 - 143, These three
identical test cases in tests/tools/composition.test.ts repeatedly call
handleCompositionCall with different tagNames arrays; collapse them into a
table-driven test (e.g., test.each or it.each) that iterates over the valid
tagNames arrays and asserts result.isError is falsy. Locate the three it blocks
titled 'accepts 2 tagNames', 'accepts 3 tagNames', and 'accepts 4 tagNames
(maximum)' and replace them with a single parametrized test that calls
handleCompositionCall('get_composition_example', { tagNames: tags }, RICH_CEM)
for each tags array and expects result.isError.toBeFalsy().

Comment on lines +217 to +219
beforeEach(() => {
vi.clearAllMocks();
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Optional cleanup: remove redundant beforeEach in single-test suite.

This block currently has one test using mockImplementationOnce; clearing mocks here is unnecessary overhead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/tools/composition.test.ts` around lines 217 - 219, Remove the redundant
setup that clears mocks for the single-test suite: delete the beforeEach block
that calls vi.clearAllMocks(), since the suite contains only one test which uses
mockImplementationOnce and does not require global mock clearing; locate the
beforeEach(...) containing vi.clearAllMocks and remove it to reduce unnecessary
overhead.

Comment on lines +17 to +31
getDesignTokens: vi.fn(async (_config: unknown, category?: string) => ({
tokens: [
{ name: '--color-primary', value: '#0066cc', category: 'color' },
{ name: '--color-secondary', value: '#666', category: 'color' },
{ name: '--spacing-md', value: '1rem', category: 'spacing' },
].filter((t) => !category || t.category === category),
count: category ? 2 : 3,
categories: ['color', 'spacing'],
})),
findToken: vi.fn(async (_config: unknown, query: string) => ({
tokens: [
{ name: '--color-primary', value: '#0066cc', category: 'color' },
].filter((t) => t.name.includes(query) || t.value.includes(query)),
count: 1,
query,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Mock count values can diverge from returned token arrays.

On Line 23 and Line 30, count is hardcoded instead of derived from the filtered tokens. That can let metadata regressions slip through tests.

Suggested fix
 vi.mock('../../packages/core/src/handlers/tokens.js', () => ({
-  getDesignTokens: vi.fn(async (_config: unknown, category?: string) => ({
-    tokens: [
+  getDesignTokens: vi.fn(async (_config: unknown, category?: string) => {
+    const tokens = [
       { name: '--color-primary', value: '#0066cc', category: 'color' },
       { name: '--color-secondary', value: '#666', category: 'color' },
       { name: '--spacing-md', value: '1rem', category: 'spacing' },
-    ].filter((t) => !category || t.category === category),
-    count: category ? 2 : 3,
-    categories: ['color', 'spacing'],
-  })),
-  findToken: vi.fn(async (_config: unknown, query: string) => ({
-    tokens: [
+    ].filter((t) => !category || t.category === category);
+    return {
+      tokens,
+      count: tokens.length,
+      categories: ['color', 'spacing'],
+    };
+  }),
+  findToken: vi.fn(async (_config: unknown, query: string) => {
+    const tokens = [
       { name: '--color-primary', value: '#0066cc', category: 'color' },
-    ].filter((t) => t.name.includes(query) || t.value.includes(query)),
-    count: 1,
-    query,
-  })),
+    ].filter((t) => t.name.includes(query) || t.value.includes(query));
+    return {
+      tokens,
+      count: tokens.length,
+      query,
+    };
+  }),
 }));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getDesignTokens: vi.fn(async (_config: unknown, category?: string) => ({
tokens: [
{ name: '--color-primary', value: '#0066cc', category: 'color' },
{ name: '--color-secondary', value: '#666', category: 'color' },
{ name: '--spacing-md', value: '1rem', category: 'spacing' },
].filter((t) => !category || t.category === category),
count: category ? 2 : 3,
categories: ['color', 'spacing'],
})),
findToken: vi.fn(async (_config: unknown, query: string) => ({
tokens: [
{ name: '--color-primary', value: '#0066cc', category: 'color' },
].filter((t) => t.name.includes(query) || t.value.includes(query)),
count: 1,
query,
getDesignTokens: vi.fn(async (_config: unknown, category?: string) => {
const tokens = [
{ name: '--color-primary', value: '#0066cc', category: 'color' },
{ name: '--color-secondary', value: '#666', category: 'color' },
{ name: '--spacing-md', value: '1rem', category: 'spacing' },
].filter((t) => !category || t.category === category);
return {
tokens,
count: tokens.length,
categories: ['color', 'spacing'],
};
}),
findToken: vi.fn(async (_config: unknown, query: string) => {
const tokens = [
{ name: '--color-primary', value: '#0066cc', category: 'color' },
].filter((t) => t.name.includes(query) || t.value.includes(query));
return {
tokens,
count: tokens.length,
query,
};
}),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/tools/tokens.test.ts` around lines 17 - 31, The mocks for
getDesignTokens and findToken set a hardcoded count which can drift from the
actual filtered tokens; update both mock implementations (getDesignTokens and
findToken) to compute count from the filtered tokens array (e.g., const filtered
= [ ... ].filter(...); return { tokens: filtered, count: filtered.length, ... })
so count always matches the returned tokens for the category filter in
getDesignTokens and the query filter in findToken.

Comment on lines +154 to +203
it('accepts html up to 50000 characters', () => {
const longHtml = '<hx-button>' + 'x'.repeat(49_980) + '</hx-button>';
const result = handleValidateCall(
'validate_usage',
{ tagName: 'hx-button', html: longHtml },
BUTTON_CEM,
);
expect(result.isError).toBeFalsy();
});
});

// ─── handleValidateCall — error cases ─────────────────────────────────────────

describe('handleValidateCall — error cases', () => {
it('returns error for unknown tool name', () => {
const result = handleValidateCall('nonexistent_tool', {}, EMPTY_CEM);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Unknown validate tool');
});

it('returns error for empty tool name', () => {
const result = handleValidateCall('', {}, EMPTY_CEM);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Unknown validate tool');
});

it('returns error when tagName is missing', () => {
const result = handleValidateCall(
'validate_usage',
{ html: '<hx-button>Click</hx-button>' },
BUTTON_CEM,
);
expect(result.isError).toBe(true);
});

it('returns error when html is missing', () => {
const result = handleValidateCall(
'validate_usage',
{ tagName: 'hx-button' },
BUTTON_CEM,
);
expect(result.isError).toBe(true);
});

it('returns error when html exceeds 50000 characters', () => {
const tooLongHtml = '<hx-button>' + 'x'.repeat(50_000) + '</hx-button>';
const result = handleValidateCall(
'validate_usage',
{ tagName: 'hx-button', html: tooLongHtml },
BUTTON_CEM,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

HTML length boundary fixtures are miscalculated.

Line 155 and Line 199 add wrapper tags on top of repeated content, so the generated strings are longer than the intended 50,000 and 50,001 boundaries. This makes the boundary assertions unreliable.

Suggested fix
   it('accepts html up to 50000 characters', () => {
-    const longHtml = '<hx-button>' + 'x'.repeat(49_980) + '</hx-button>';
+    const open = '<hx-button>';
+    const close = '</hx-button>';
+    const longHtml = open + 'x'.repeat(50_000 - open.length - close.length) + close;
     const result = handleValidateCall(
       'validate_usage',
       { tagName: 'hx-button', html: longHtml },
       BUTTON_CEM,
     );
     expect(result.isError).toBeFalsy();
   });
@@
   it('returns error when html exceeds 50000 characters', () => {
-    const tooLongHtml = '<hx-button>' + 'x'.repeat(50_000) + '</hx-button>';
+    const open = '<hx-button>';
+    const close = '</hx-button>';
+    const tooLongHtml = open + 'x'.repeat(50_001 - open.length - close.length) + close;
     const result = handleValidateCall(
       'validate_usage',
       { tagName: 'hx-button', html: tooLongHtml },
       BUTTON_CEM,
     );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('accepts html up to 50000 characters', () => {
const longHtml = '<hx-button>' + 'x'.repeat(49_980) + '</hx-button>';
const result = handleValidateCall(
'validate_usage',
{ tagName: 'hx-button', html: longHtml },
BUTTON_CEM,
);
expect(result.isError).toBeFalsy();
});
});
// ─── handleValidateCall — error cases ─────────────────────────────────────────
describe('handleValidateCall — error cases', () => {
it('returns error for unknown tool name', () => {
const result = handleValidateCall('nonexistent_tool', {}, EMPTY_CEM);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Unknown validate tool');
});
it('returns error for empty tool name', () => {
const result = handleValidateCall('', {}, EMPTY_CEM);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Unknown validate tool');
});
it('returns error when tagName is missing', () => {
const result = handleValidateCall(
'validate_usage',
{ html: '<hx-button>Click</hx-button>' },
BUTTON_CEM,
);
expect(result.isError).toBe(true);
});
it('returns error when html is missing', () => {
const result = handleValidateCall(
'validate_usage',
{ tagName: 'hx-button' },
BUTTON_CEM,
);
expect(result.isError).toBe(true);
});
it('returns error when html exceeds 50000 characters', () => {
const tooLongHtml = '<hx-button>' + 'x'.repeat(50_000) + '</hx-button>';
const result = handleValidateCall(
'validate_usage',
{ tagName: 'hx-button', html: tooLongHtml },
BUTTON_CEM,
it('accepts html up to 50000 characters', () => {
const open = '<hx-button>';
const close = '</hx-button>';
const longHtml = open + 'x'.repeat(50_000 - open.length - close.length) + close;
const result = handleValidateCall(
'validate_usage',
{ tagName: 'hx-button', html: longHtml },
BUTTON_CEM,
);
expect(result.isError).toBeFalsy();
});
});
// ─── handleValidateCall — error cases ─────────────────────────────────────────
describe('handleValidateCall — error cases', () => {
it('returns error for unknown tool name', () => {
const result = handleValidateCall('nonexistent_tool', {}, EMPTY_CEM);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Unknown validate tool');
});
it('returns error for empty tool name', () => {
const result = handleValidateCall('', {}, EMPTY_CEM);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Unknown validate tool');
});
it('returns error when tagName is missing', () => {
const result = handleValidateCall(
'validate_usage',
{ html: '<hx-button>Click</hx-button>' },
BUTTON_CEM,
);
expect(result.isError).toBe(true);
});
it('returns error when html is missing', () => {
const result = handleValidateCall(
'validate_usage',
{ tagName: 'hx-button' },
BUTTON_CEM,
);
expect(result.isError).toBe(true);
});
it('returns error when html exceeds 50000 characters', () => {
const open = '<hx-button>';
const close = '</hx-button>';
const tooLongHtml = open + 'x'.repeat(50_001 - open.length - close.length) + close;
const result = handleValidateCall(
'validate_usage',
{ tagName: 'hx-button', html: tooLongHtml },
BUTTON_CEM,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/tools/validate.test.ts` around lines 154 - 203, The HTML boundary tests
miscount because the wrapper tags ("<hx-button>" and "</hx-button>") add extra
characters to longHtml and tooLongHtml; update the fixtures in the tests that
call handleValidateCall (variables longHtml and tooLongHtml) so the repeated
'x'.repeat(...) accounts for the wrapper length (i.e. compute repeat count =
desiredTotalLength - wrapperLength) or build the repeated content first and then
wrap it, ensuring the final string lengths are exactly 49,980 and 50,000 (or
your intended boundaries) before asserting the acceptance/rejection behavior.

@himerus
Copy link
Copy Markdown
Contributor Author

himerus commented Mar 27, 2026

Closing: staging branch contains self-referential symlinks (build, node_modules) that break all CI. A new dev→staging promotion will carry the fix.

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