Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8e131fe
docs: add social card image + update README badges
himerus Mar 26, 2026
ba6ec2b
docs: update social card with color-branded export
himerus Mar 26, 2026
933fa0a
refactor: fix: align @modelcontextprotocol/sdk versions across monore…
himerus Mar 26, 2026
311f1c4
Merge pull request #206 from bookedsolidtech/feature/fix-align-modelc…
himerus Mar 26, 2026
f94ea7b
refactor: fix: wire scaffold_component and extend_component into MCP …
himerus Mar 26, 2026
96cb5e2
Merge pull request #207 from bookedsolidtech/feature/fix-wire-scaffol…
himerus Mar 26, 2026
692886b
feat: scaffold packages/vscode VS Code extension MVP
himerus Mar 26, 2026
0d24d51
Merge pull request #208 from bookedsolidtech/feature/feat-scaffold-pa…
himerus Mar 26, 2026
a7e99c0
docs: update README social card to PNG and fix tool count to 87+
himerus Mar 26, 2026
02ee086
fix: replace TODO placeholder with var() fallback in theme handler de…
himerus Mar 26, 2026
fd40ef2
test: add test suite for styling tools (29 tools, 75 tests)
himerus Mar 26, 2026
cd70600
Merge pull request #210 from bookedsolidtech/feature/fix-theme-handle…
himerus Mar 26, 2026
41f5237
Merge remote-tracking branch 'origin/dev' into feature/docs-update-re…
himerus Mar 26, 2026
e9e4a58
Merge pull request #211 from bookedsolidtech/feature/test-add-test-su…
himerus Mar 26, 2026
42524fc
fix: replace plain new Error() throws with MCPError
himerus Mar 26, 2026
d5d6552
Merge pull request #214 from bookedsolidtech/feature/fix-replace-plai…
himerus Mar 26, 2026
7c05077
fix: update README tool count badge and add missing tools to reference
himerus Mar 26, 2026
97a3edb
Merge pull request #215 from bookedsolidtech/feature/fix-update-readm…
himerus Mar 26, 2026
d695d62
fix: add typecheck script alias to resolve post-merge verification fa…
himerus Mar 26, 2026
8b8d40b
Merge pull request #216 from bookedsolidtech/feature/fix-post-merge-v…
himerus Mar 26, 2026
51905e0
test: add test suites for scaffold, extend, theme, and bundle tools
himerus Mar 26, 2026
1909566
Merge pull request #217 from bookedsolidtech/feature/test-add-test-su…
himerus Mar 26, 2026
60ad017
Merge branch 'origin/dev' into feature/docs-update-readme-social-card-to
himerus Mar 26, 2026
fa8b4c6
Merge pull request #209 from bookedsolidtech/feature/docs-update-read…
himerus Mar 26, 2026
0508d52
test: add test suites for cdn, composition, framework, story, tokens,…
himerus Mar 26, 2026
37eca94
Merge pull request #218 from bookedsolidtech/feature/test-add-test-su…
himerus Mar 26, 2026
2bbbfb7
refactor: replace env var if-blocks with lookup tables in config.ts
himerus Mar 26, 2026
496dede
Merge pull request #219 from bookedsolidtech/feature/fix-refactor-con…
himerus Mar 26, 2026
1436936
feat(vscode): add Configure for Cursor/Windsurf command
himerus Mar 26, 2026
8208b22
Merge pull request #220 from bookedsolidtech/feature/feat-add-cursorw…
himerus Mar 26, 2026
201ebaa
fix: add bounds checking for CLI array args before access
himerus Mar 26, 2026
600373c
Merge pull request #221 from bookedsolidtech/feature/fix-add-bounds-c…
himerus Mar 26, 2026
e62ea1f
test: add test suites for 8 untested analyzer modules
himerus Mar 26, 2026
19c25c4
fix: correct test assertions to match actual analyzer APIs
himerus Mar 26, 2026
db217f2
Merge pull request #222 from bookedsolidtech/feature/test-add-test-su…
himerus Mar 26, 2026
14fe37c
ci: add GitHub Actions workflow to publish VS Code extension to marke…
himerus Mar 26, 2026
cbbefd6
fix: update pnpm-lock.yaml specifier for @modelcontextprotocol/sdk to…
himerus Mar 26, 2026
5c612a4
fix: remove committed node_modules and build symlinks from git tracking
himerus Mar 26, 2026
cda6d75
style: apply prettier formatting to unformatted files
himerus Mar 26, 2026
0af6bca
fix: update dependency overrides to resolve high-severity audit vulne…
himerus Mar 26, 2026
6f1acb0
style: apply prettier formatting to new tool test files
himerus Mar 26, 2026
4560b3d
style: apply prettier formatting to additional unformatted files
himerus Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/workflows/publish-vscode.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Publish VS Code Extension

on:
push:
tags:
- 'vscode-v*'

jobs:
publish-vscode:
name: Publish to Marketplaces
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Install gitleaks
run: |
GITLEAKS_VERSION="8.18.4"
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz -C /usr/local/bin gitleaks

- name: Scan for secrets
run: gitleaks detect --config .gitleaks.toml --log-opts="HEAD~1..HEAD" --verbose --redact

- uses: pnpm/action-setup@v4
with:
version: '9'

- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build helixir
run: pnpm run build

- name: Bundle extension
run: pnpm --filter helixir-vscode run vscode:prepublish

- name: Publish to VS Code Marketplace
run: npx @vscode/vsce publish --no-dependencies
env:
VSCE_PAT: ${{ secrets.VSCE_PAT }}

- name: Publish to Open VSX
run: npx ovsx publish --no-dependencies -p ${{ secrets.OVSX_PAT }}
Comment thread
himerus marked this conversation as resolved.
85 changes: 76 additions & 9 deletions README.md

Large diffs are not rendered by default.

Binary file added assets/social-card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/social-card.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"test:coverage": "vitest run --coverage",
"test:watch": "vitest",
"type-check": "tsc --noEmit",
"typecheck": "pnpm run type-check",
"lint": "eslint src packages/core/src",
"lint:fix": "eslint src packages/core/src --fix",
"format": "prettier --write .",
Expand Down Expand Up @@ -107,7 +108,8 @@
"@hono/node-server": ">=1.19.10",
"express-rate-limit": ">=8.2.2",
"undici": ">=7.24.0",
"flatted": ">=3.4.0"
"flatted": ">=3.4.2",
"picomatch": ">=4.0.4"
}
},
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.26.0",
"@modelcontextprotocol/sdk": "^1.27.1",
"zod": "^3.22.0"
},
"peerDependencies": {
Expand Down
70 changes: 25 additions & 45 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ const defaults: McpWcConfig = {
};

function readConfigFile(projectRoot: string): Partial<McpWcConfig> {
// Primary config file name
const primaryPath = resolve(projectRoot, 'helixir.mcp.json');
if (existsSync(primaryPath)) {
try {
Expand All @@ -44,21 +43,6 @@ function readConfigFile(projectRoot: string): Partial<McpWcConfig> {
}
}

// Backward-compatible fallback to legacy config file name
const legacyPath = resolve(projectRoot, 'mcpwc.config.json');
if (existsSync(legacyPath)) {
process.stderr.write(
`[helixir] Warning: mcpwc.config.json is deprecated. Rename to helixir.mcp.json.\n`,
);
try {
const raw = readFileSync(legacyPath, 'utf-8');
return JSON.parse(raw) as Partial<McpWcConfig>;
} catch {
process.stderr.write(`[helixir] Warning: mcpwc.config.json is malformed. Using defaults.\n`);
return {};
}
}

return {};
}

Expand Down Expand Up @@ -92,36 +76,32 @@ export function loadConfig(): Readonly<McpWcConfig> {
}

// Apply env vars (highest priority)
if (process.env['MCP_WC_CEM_PATH'] !== undefined) {
config.cemPath = process.env['MCP_WC_CEM_PATH'];
}
if (process.env['MCP_WC_PROJECT_ROOT'] !== undefined) {
config.projectRoot = process.env['MCP_WC_PROJECT_ROOT'];
}
if (process.env['MCP_WC_COMPONENT_PREFIX'] !== undefined) {
config.componentPrefix = process.env['MCP_WC_COMPONENT_PREFIX'];
}
if (process.env['MCP_WC_HEALTH_HISTORY_DIR'] !== undefined) {
config.healthHistoryDir = process.env['MCP_WC_HEALTH_HISTORY_DIR'];
}
if (process.env['MCP_WC_TSCONFIG_PATH'] !== undefined) {
config.tsconfigPath = process.env['MCP_WC_TSCONFIG_PATH'];
}
if (process.env['MCP_WC_TOKENS_PATH'] !== undefined) {
const val = process.env['MCP_WC_TOKENS_PATH'];
config.tokensPath = val === 'null' ? null : val;
}
if (process.env['MCP_WC_CDN_BASE'] !== undefined) {
const val = process.env['MCP_WC_CDN_BASE'];
config.cdnBase = val === 'null' ? null : val;
}
if (process.env['MCP_WC_CDN_AUTOLOADER'] !== undefined) {
const val = process.env['MCP_WC_CDN_AUTOLOADER'];
config.cdnAutoloader = val === 'null' ? null : val;
// String keys map directly; nullable keys treat the literal string 'null' as null.
const ENV_MAP_STRING: Readonly<Record<string, keyof McpWcConfigMutable>> = {
MCP_WC_CEM_PATH: 'cemPath',
MCP_WC_PROJECT_ROOT: 'projectRoot',
MCP_WC_COMPONENT_PREFIX: 'componentPrefix',
MCP_WC_HEALTH_HISTORY_DIR: 'healthHistoryDir',
MCP_WC_TSCONFIG_PATH: 'tsconfigPath',
};
const ENV_MAP_NULLABLE: Readonly<Record<string, keyof McpWcConfigMutable>> = {
MCP_WC_TOKENS_PATH: 'tokensPath',
MCP_WC_CDN_BASE: 'cdnBase',
MCP_WC_CDN_AUTOLOADER: 'cdnAutoloader',
MCP_WC_CDN_STYLESHEET: 'cdnStylesheet',
};

for (const [envKey, configKey] of Object.entries(ENV_MAP_STRING)) {
const val = process.env[envKey];
if (val !== undefined) {
(config as Record<string, unknown>)[configKey] = val;
}
}
if (process.env['MCP_WC_CDN_STYLESHEET'] !== undefined) {
const val = process.env['MCP_WC_CDN_STYLESHEET'];
config.cdnStylesheet = val === 'null' ? null : val;
for (const [envKey, configKey] of Object.entries(ENV_MAP_NULLABLE)) {
const val = process.env[envKey];
if (val !== undefined) {
(config as Record<string, unknown>)[configKey] = val === 'null' ? null : val;
}
}

// --watch CLI flag overrides config file value
Expand Down
5 changes: 4 additions & 1 deletion packages/core/src/handlers/cem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,10 @@ export function findComponentsByToken(
cem: Cem,
): FindComponentsByTokenResult {
if (!token.startsWith('--')) {
throw new Error(`CSS custom property name must start with "--": "${token}"`);
throw new MCPError(
`CSS custom property name must start with "--": "${token}"`,
ErrorCategory.VALIDATION,
);
}

const components: TokenComponentMatch[] = [];
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/handlers/dependencies.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Cem } from './cem.js';
import { MCPError, ErrorCategory } from '../shared/error-handling.js';

export interface ComponentDependencyResult {
tagName: string;
Expand Down Expand Up @@ -105,7 +106,7 @@ export function getComponentDependencies(
}

if (!found) {
throw new Error(`Component "${tagName}" not found in CEM.`);
throw new MCPError(`Component "${tagName}" not found in CEM.`, ErrorCategory.NOT_FOUND);
}

const depMap = buildDependencyMap(cem);
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/handlers/extend.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Cem, CemDeclaration } from './cem.js';
import { MCPError, ErrorCategory } from '../shared/error-handling.js';

// --- Helpers ---

Expand Down Expand Up @@ -66,7 +67,7 @@ export function extendComponent(
): ExtendComponentResult {
const parentDecl = findDeclaration(cem, parentTagName);
if (!parentDecl) {
throw new Error(`Component "${parentTagName}" not found in CEM.`);
throw new MCPError(`Component "${parentTagName}" not found in CEM.`, ErrorCategory.NOT_FOUND);
}

const parentClassName = tagNameToClassName(parentTagName);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/handlers/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ function lightPlaceholder(tokenName: string, category: string): string {
return '200ms';

default:
return '/* TODO: set value */';
return `var(${tokenName})`;
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"homepage": "https://github.com/bookedsolidtech/helixir/tree/main/packages/mcp#readme",
"peerDependencies": {
"helixir": ">=0.5.0",
"@modelcontextprotocol/sdk": "^1.26.0",
"@modelcontextprotocol/sdk": "^1.27.1",
"zod": "^3.22.0"
},
"devDependencies": {
Expand Down
30 changes: 30 additions & 0 deletions packages/vscode/.vscodeignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Source files — not needed in the packaged extension
src/
tsconfig.json
esbuild.config.mjs

# Development dependencies and lock files
node_modules/
.pnpm-store/
pnpm-lock.yaml
package-lock.json

# Test artefacts
coverage/
*.test.ts
*.spec.ts

# Build intermediates (keep dist/)
*.map

# Editor and OS artefacts
.vscode/
.DS_Store
*.log

# Root-level workspace files that should not be bundled
../../node_modules/
../../src/
../../build/
../../packages/
../../.github/
87 changes: 87 additions & 0 deletions packages/vscode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Helixir — VS Code Extension

**AI-powered web component intelligence for VS Code.**

Helixir gives AI assistants full situational awareness of any web component library by wiring the [helixir MCP server](https://github.com/bookedsolidtech/helixir) directly into VS Code's MCP layer.

## 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
Comment thread
himerus marked this conversation as resolved.
- **Zero hallucinations** — every AI component suggestion is grounded in your actual `custom-elements.json`
- **Framework-agnostic** — works with Lit, Stencil, FAST, Spectrum, Shoelace, or any library that produces a Custom Elements Manifest

## Requirements

- VS Code **≥ 1.99.0**
- A component library with a `custom-elements.json` (Custom Elements Manifest)
- Node.js **≥ 20** on `PATH`

## Getting Started

1. Install the extension from the VS Code Marketplace
2. Open your component library folder in VS Code
3. The Helixir MCP server will register automatically with AI assistants that support MCP (e.g., GitHub Copilot, Claude)

### Optional: Configure the Config Path

If your `mcpwc.config.json` is not at the workspace root, set the path via VS Code settings:

```json
// .vscode/settings.json
{
"helixir.configPath": "packages/web-components/mcpwc.config.json"
}
Comment on lines +26 to +34
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

Use helixir.mcp.json here instead of the legacy filename.

src/mcp/index.ts already tells users to configure helixir.mcp.json, but this README still points them at mcpwc.config.json. That sends new users to the wrong file name before they even start.

📝 Proposed fix
-If your `mcpwc.config.json` is not at the workspace root, set the path via VS Code settings:
+If your `helixir.mcp.json` is not at the workspace root, set the path via VS Code settings:

 {
-  "helixir.configPath": "packages/web-components/mcpwc.config.json"
+  "helixir.configPath": "packages/web-components/helixir.mcp.json"
 }

-| `helixir.configPath` | `string` | `""`    | Path to `mcpwc.config.json`. Empty = workspace root. |
+| `helixir.configPath` | `string` | `""`    | Path to `helixir.mcp.json`. Empty = workspace root. |

-Additional configuration (token path, component prefix, health history dir) belongs in `mcpwc.config.json`.
+Additional configuration (token path, component prefix, health history dir) belongs in `helixir.mcp.json`.

Also applies to: 47-49, 66-66

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

In `@packages/vscode/README.md` around lines 26 - 34, The README's example uses
the legacy filename "mcpwc.config.json" instead of the current
"helixir.mcp.json"; update all occurrences in the file (including the example
under "Optional: Configure the Config Path" and the other referenced lines) to
use "helixir.mcp.json" and ensure the setting key remains "helixir.configPath"
so users are pointed to the correct config file referenced by src/mcp/index.ts.

```

The path can be relative to the workspace root or absolute.

## Commands

| Command | Description |
| --------------------------- | ------------------------------------------------------ |
| `Helixir: Run Health Check` | Guides you to run a health check via your AI assistant |

## Extension Settings

| Setting | Type | Default | Description |
| -------------------- | -------- | ------- | ---------------------------------------------------- |
| `helixir.configPath` | `string` | `""` | Path to `mcpwc.config.json`. Empty = workspace root. |

## How It Works

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.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Configuration Reference

The helixir server is configured via environment variables passed by the extension:

| Variable | Description |
| --------------------- | ------------------------------------------- |
| `MCP_WC_PROJECT_ROOT` | Set to your workspace folder automatically |
| `MCP_WC_CONFIG_PATH` | Set when `helixir.configPath` is configured |

Additional configuration (token path, component prefix, health history dir) belongs in `mcpwc.config.json`. See the [helixir documentation](https://github.com/bookedsolidtech/helixir) for the full config reference.

## Troubleshooting

**MCP server not appearing in AI assistant tools**

- Verify VS Code ≥ 1.99.0 is installed
- Confirm your workspace contains a `custom-elements.json`
- Check the Output panel → Helixir for error messages

**"No workspace folder" error from Run Health Check**

- Open a folder (not just a file) in VS Code — the extension uses the workspace folder as the project root

**Server starts but returns no components**

- Ensure `custom-elements.json` exists at the workspace root or configure `helixir.configPath`
- Regenerate the manifest: `npm run analyze:cem` (or your CEM generation script)

## License

MIT — see [LICENSE](../../LICENSE)
Loading
Loading