Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
59 changes: 46 additions & 13 deletions .claude/skills/brand/scripts/sync-brand-to-tokens.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const { execFileSync } = require('child_process');

// Paths
const BRAND_GUIDELINES = 'docs/brand-guidelines.md';
Expand Down Expand Up @@ -246,21 +246,54 @@ function main() {
fs.writeFileSync(tokensPath, JSON.stringify(tokens, null, 2));
console.log(`✅ Updated: ${DESIGN_TOKENS_JSON}`);

// Regenerate CSS
const generateScript = path.resolve(process.cwd(), GENERATE_TOKENS_SCRIPT);
if (fs.existsSync(generateScript)) {
try {
execSync(`node ${generateScript} --config ${DESIGN_TOKENS_JSON} -o ${DESIGN_TOKENS_CSS}`, {
cwd: process.cwd(),
stdio: 'inherit'
});
console.log(`✅ Regenerated: ${DESIGN_TOKENS_CSS}`);
} catch (e) {
console.error('⚠️ Failed to regenerate CSS:', e.message);
}
if (runTokenCssRegeneration({
generateScript,
configPath: DESIGN_TOKENS_JSON,
outputPath: DESIGN_TOKENS_CSS,
cwd: process.cwd(),
})) {
console.log(`✅ Regenerated: ${DESIGN_TOKENS_CSS}`);
}

console.log('\n✨ Brand sync complete!');
}

main();
/**
* Regenerate design-tokens.css via generate-tokens.cjs (no shell interpolation).
* @param {object} opts
* @param {string} opts.generateScript - Absolute path to generate-tokens.cjs
* @param {string} opts.configPath - Config path relative to cwd
* @param {string} opts.outputPath - Output path relative to cwd
* @param {string} opts.cwd - Working directory
* @param {typeof execFileSync} [opts.execFile] - Injectable for tests
* @param {typeof execFileSync} [execFileImpl] - Second-arg injectable for tests
* @returns {boolean} True if regeneration ran
*/
function runTokenCssRegeneration(opts, execFileImpl) {
const execFile = opts.execFile || execFileImpl || execFileSync;
if (!fs.existsSync(opts.generateScript)) {
return false;
}
try {
execFile(
process.execPath,
[opts.generateScript, '--config', opts.configPath, '-o', opts.outputPath],
{ cwd: opts.cwd, stdio: 'inherit' }
);
return true;
} catch (e) {
console.error('⚠️ Failed to regenerate CSS:', e.message);
return false;
}
}

if (require.main === module) {
main();
}

module.exports = {
runTokenCssRegeneration,
extractColorsFromMarkdown,
updateDesignTokens,
};
31 changes: 26 additions & 5 deletions .claude/skills/design/scripts/icon/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@
from datetime import datetime


def sanitize_svg(svg: str) -> str:
"""Strip script tags, event handlers, and javascript: URIs from model output."""
svg = re.sub(r"<script\b[^>]*>.*?</script>", "", svg, flags=re.I | re.S)
svg = re.sub(r'\s+on\w+\s*=\s*["\'][^"\']*["\']', "", svg, flags=re.I)
svg = re.sub(r"\s+on\w+\s*=\s*[^\s>]+", "", svg, flags=re.I)
svg = re.sub(r"javascript:", "", svg, flags=re.I)
return svg


def load_env():
"""Load .env files in priority order"""
env_paths = [
Expand All @@ -46,10 +55,12 @@ def load_env():
try:
from google import genai
from google.genai import types

GENAI_AVAILABLE = True
except ImportError:
print("Error: google-genai package not installed.")
print("Install with: pip install google-genai")
sys.exit(1)
genai = None
types = None
GENAI_AVAILABLE = False


# ============ CONFIGURATION ============
Expand Down Expand Up @@ -190,6 +201,11 @@ def generate_icon(prompt, style=None, category=None, name=None,
color=None, size=24, output_path=None, viewbox=24):
"""Generate a single SVG icon using Gemini 3.1 Pro Preview"""

if not GENAI_AVAILABLE:
print("Error: google-genai package not installed.")
print("Install with: pip install google-genai")
return None

if not GEMINI_API_KEY:
print("Error: GEMINI_API_KEY not set")
print("Set it with: export GEMINI_API_KEY='your-key'")
Expand Down Expand Up @@ -255,7 +271,7 @@ def generate_icon(prompt, style=None, category=None, name=None,
print(response_text[:500])
return None

svg_code = svgs[0]
svg_code = sanitize_svg(svgs[0])

# Apply color if specified
svg_code = apply_color(svg_code, color)
Expand Down Expand Up @@ -287,6 +303,11 @@ def generate_batch(prompt, count, output_dir, style=None, color=None,
viewbox=24, name=None):
"""Generate multiple icon variations"""

if not GENAI_AVAILABLE:
print("Error: google-genai package not installed.")
print("Install with: pip install google-genai")
return []

if not GEMINI_API_KEY:
print("Error: GEMINI_API_KEY not set")
return []
Expand Down Expand Up @@ -347,7 +368,7 @@ def generate_batch(prompt, count, output_dir, style=None, color=None,
style_suffix = f"_{style}" if style else ""

for i, svg_code in enumerate(svgs[:count]):
svg_code = apply_color(svg_code, color)
svg_code = apply_color(sanitize_svg(svg_code), color)
filename = f"{slug}{style_suffix}_{i+1:02d}.svg"
filepath = os.path.join(output_dir, filename)

Expand Down
11 changes: 11 additions & 0 deletions .claude/skills/ui-styling/scripts/shadcn_add.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@

import argparse
import json
import re
import subprocess
import sys
from pathlib import Path
from typing import List, Optional

COMPONENT_NAME_PATTERN = re.compile(r"^[a-z0-9-]+$")


class ShadcnInstaller:
"""Handle shadcn/ui component installation."""
Expand Down Expand Up @@ -80,6 +83,14 @@ def add_components(
if not components:
return False, "No components specified"

invalid = [c for c in components if not COMPONENT_NAME_PATTERN.match(c)]
if invalid:
return (
False,
f"Invalid component name(s): {', '.join(invalid)}. "
"Use lowercase letters, numbers, and hyphens only.",
)

if not self.check_shadcn_config():
return (
False,
Expand Down
8 changes: 8 additions & 0 deletions .claude/skills/ui-styling/scripts/tests/test_shadcn_add.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ def test_add_components_no_config(self, tmp_path):
assert success is False
assert "not initialized" in message

def test_add_components_rejects_invalid_names(self, temp_project):
"""Test shell-metacharacter names are rejected before subprocess."""
installer = ShadcnInstaller(project_root=temp_project)
success, message = installer.add_components(["button;rm -rf /"])

assert success is False
assert "Invalid component name" in message

def test_add_components_already_installed(self, temp_project):
"""Test adding components that are already installed."""
ui_dir = temp_project / "components" / "ui"
Expand Down
107 changes: 107 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
## Summary

<!-- 2–4 sentences: what this PR does and why. A maintainer should understand the goal without opening the diff. -->

-

## Type of change

<!-- Mark one primary type. -->

- [ ] Feature / enhancement
- [ ] Bug fix
- [ ] Tests only (no production behavior change)
- [ ] Documentation
- [ ] Security hardening
- [ ] Data / CSV update
- [ ] CLI (`uipro`) change
- [ ] Other: <!-- describe -->

## Scope

<!-- Keep PRs focused. If this touches multiple areas, explain why they belong together. -->

**In scope:**

-

**Out of scope (follow-up PRs):**

-

## Related work

<!-- Links help reviewers and future you. -->

| Link | ID / reference |
|------|----------------|
| Issue | <!-- #123 or "none" --> |
| Security hardening plan | <!-- e.g. Phase 1, T-010–T-016 or "N/A" --> |
| Prior PR | <!-- e.g. depends on #42 or "none" --> |

## How to review

<!-- Suggested order: which files matter most and what to verify. -->

1.
2.

## Test plan

<!-- Check what you ran. CI must pass; list anything extra you did manually. -->

- [ ] `pip install -e ".[test]" && make test` (or `make test-python` + `make test-cli`)
- [ ] CI **Test** workflow green
- [ ] Manual smoke (if applicable): <!-- command or "N/A" -->

### CLI / assets sync

<!-- Required when changing source of truth under src/ui-ux-pro-max/ -->

- [ ] Not applicable
- [ ] Synced `cli/assets/` from `src/ui-ux-pro-max/` (scripts / data / templates)

```bash
# Example — run only what you changed
cp -r src/ui-ux-pro-max/scripts/* cli/assets/scripts/
cp -r src/ui-ux-pro-max/data/* cli/assets/data/
cp -r src/ui-ux-pro-max/templates/* cli/assets/templates/
```

### Golden / snapshot tests

- [ ] Not applicable
- [ ] No golden files changed
- [ ] Golden files updated **intentionally** — reason: <!-- e.g. ranking tweak, new CSV column -->

## User-facing impact

<!-- Will end users or skill consumers notice a change? -->

- [ ] No behavior change (refactor, tests, docs only)
- [ ] Behavior change — described below:

## Screenshots / sample output

<!-- Optional: CLI output, search results, before/after. -->

<details>
<summary>Expand if helpful</summary>

```text
(paste output)
```

</details>

## Checklist

- [ ] Branch is based on latest `main` (rebased or merged recently)
- [ ] PR targets `main` on `nextlevelbuilder/ui-ux-pro-max-skill` (not a direct push to upstream `main`)
- [ ] Commit messages describe **why**, not only **what**
- [ ] No secrets, API keys, or `.env` files committed
- [ ] CSV / data changes reviewed for accidental prompt-injection phrasing (see [CONTRIBUTING.md](../CONTRIBUTING.md))

---

<!-- For security hardening contributors: see docs/security-hardening-plan.md for phase boundaries (one phase per PR). -->
34 changes: 0 additions & 34 deletions .github/workflows/python-package-conda.yml

This file was deleted.

32 changes: 32 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Test

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install Python test dependencies
run: pip install -e ".[test]"

- name: Python tests
run: pytest tests/python -v

- name: Set up Bun
uses: oven-sh/setup-bun@v2

- name: CLI tests
run: |
cd cli && bun install
bun test tests/cli
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
Thumbs.db

# Python
*.egg-info/
.pytest_cache/
.coverage
htmlcov/
__pycache__/
*.py[cod]
*.pyo
Expand Down
Loading