From da307c6d8a808708d61a23561405aca68a0061f3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Micha=C5=82=20Furga=C5=82a?=
<83299832+00200200@users.noreply.github.com>
Date: Wed, 7 Oct 2026 09:31:54 +0200
Subject: [PATCH] feat(wasm): add WebAssembly/Pyodide compilation engine and
interactive playground (closes #29)
---
docs/index.html | 519 +++++++++++++++++++++++++++++++++++++++++++++
src/cinch/wasm.py | 151 +++++++++++++
tests/test_wasm.py | 121 +++++++++++
3 files changed, 791 insertions(+)
create mode 100644 docs/index.html
create mode 100644 src/cinch/wasm.py
create mode 100644 tests/test_wasm.py
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..50ed6ba
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,519 @@
+
+
+
+
+
+ Cinch Playground — Interactive Cross-Harness Skill Compiler
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/cinch/wasm.py b/src/cinch/wasm.py
new file mode 100644
index 0000000..ff031ee
--- /dev/null
+++ b/src/cinch/wasm.py
@@ -0,0 +1,151 @@
+"""WebAssembly and Pyodide compilation engine for in-browser Cinch playground.
+
+Provides high-speed, zero-dependency translation of Cinch skills into all
+supported AI harness dialects (Claude, Cursor, Codex, Copilot, Gemini, Windsurf,
+Cline, OpenCode, Aider, Zed, Continue, Grok).
+"""
+
+from __future__ import annotations
+
+import json
+import time
+from typing import Any
+
+from cinch.adapters import ADAPTERS, get_adapter
+from cinch.doc import Doc, parse_frontmatter
+from cinch.lsp import parse_diagnostics
+
+
+def doc_from_markdown(
+ content: str,
+ name: str | None = None,
+ kind: str = "skill",
+) -> Doc:
+ """Parse markdown skill content into a canonical Doc object."""
+ meta, body = parse_frontmatter(content)
+ doc_name = meta.get("name") or name or "sample-skill"
+ description = meta.get("description", "")
+
+ if not description:
+ for line in body.splitlines():
+ line_str = line.strip()
+ if line_str and not line_str.startswith("#"):
+ description = line_str
+ break
+ if not description:
+ description = f"{doc_name} {kind}"
+
+ paths_val = meta.get("paths") or meta.get("globs") or meta.get("applyTo") or ()
+ if isinstance(paths_val, str):
+ paths: tuple[str, ...] = (paths_val,)
+ else:
+ paths = tuple(paths_val)
+
+ requires_val = meta.get("requires") or ()
+ if isinstance(requires_val, str):
+ requires: tuple[str, ...] = (requires_val.strip(),) if requires_val.strip() else ()
+ elif isinstance(requires_val, (list, tuple)):
+ requires = tuple(
+ item.strip() for item in requires_val if isinstance(item, str) and item.strip()
+ )
+ else:
+ requires = ()
+
+ clean_body = body.strip()
+ lines = clean_body.splitlines()
+ if lines and lines[0].startswith("# "):
+ h1 = lines[0][2:].strip().lower()
+ if h1 in (doc_name.lower(), doc_name.lower().replace("-", " "), "skill"):
+ clean_body = "\n".join(lines[1:]).strip()
+
+ if not isinstance(description, str):
+ description = str(description)
+ if not isinstance(doc_name, str):
+ doc_name = str(doc_name)
+
+ return Doc(
+ kind=kind,
+ name=doc_name,
+ description=description,
+ body=clean_body,
+ paths=paths,
+ requires=requires,
+ extra_meta=meta,
+ )
+
+
+def compile_skill(content: str, target: str) -> list[dict[str, str]]:
+ """Compile markdown skill to a target harness dialect."""
+ doc = doc_from_markdown(content)
+ adapter = get_adapter(target)
+ files = adapter.render(doc)
+ return [
+ {
+ "relpath": f.relpath,
+ "text": f.text,
+ "mode": f.mode,
+ }
+ for f in files
+ ]
+
+
+def compile_all_dialects(content: str) -> dict[str, list[dict[str, str]]]:
+ """Compile markdown skill into all 12 supported AI harness dialects."""
+ doc = doc_from_markdown(content)
+ results: dict[str, list[dict[str, str]]] = {}
+ for target, adapter in ADAPTERS.items():
+ files = adapter.render(doc)
+ results[target] = [
+ {
+ "relpath": f.relpath,
+ "text": f.text,
+ "mode": f.mode,
+ }
+ for f in files
+ ]
+ return results
+
+
+def validate_skill(content: str) -> list[dict[str, Any]]:
+ """Validate skill frontmatter and return diagnostic warnings/errors."""
+ return parse_diagnostics(content)
+
+
+def compile_json(content: str, target: str | None = None) -> str:
+ """Entry point for WebAssembly / Pyodide / JS interop.
+
+ Accepts raw markdown string, returns JSON string containing:
+ - valid: bool
+ - diagnostics: list of lint issues
+ - dialects: dict of rendered files per dialect (or single target)
+ - elapsed_ms: compilation time in milliseconds
+ """
+ t0 = time.perf_counter()
+ diagnostics = validate_skill(content)
+ has_errors = any(d.get("severity") == 1 for d in diagnostics)
+
+ if target:
+ dialects = {target: compile_skill(content, target)}
+ else:
+ dialects = compile_all_dialects(content)
+
+ elapsed_ms = round((time.perf_counter() - t0) * 1000, 3)
+
+ return json.dumps(
+ {
+ "valid": not has_errors,
+ "diagnostics": diagnostics,
+ "dialects": dialects,
+ "elapsed_ms": elapsed_ms,
+ },
+ indent=2,
+ )
+
+
+__all__ = [
+ "doc_from_markdown",
+ "compile_skill",
+ "compile_all_dialects",
+ "validate_skill",
+ "compile_json",
+]
diff --git a/tests/test_wasm.py b/tests/test_wasm.py
new file mode 100644
index 0000000..f569a23
--- /dev/null
+++ b/tests/test_wasm.py
@@ -0,0 +1,121 @@
+from __future__ import annotations
+
+import json
+import time
+import unittest
+
+from cinch.adapters import ADAPTERS
+from cinch.wasm import (
+ compile_all_dialects,
+ compile_json,
+ compile_skill,
+ doc_from_markdown,
+ validate_skill,
+)
+
+SAMPLE_SKILL = """---
+name: code-reviewer
+description: Automated pull request code reviewer checking security and style.
+paths:
+ - "src/**/*.py"
+requires:
+ - git-tools
+---
+
+# Code Reviewer
+
+## Guidelines
+1. Review all modified lines.
+2. Ensure test coverage.
+"""
+
+
+class WasmCompilationTests(unittest.TestCase):
+ def test_doc_from_markdown(self) -> None:
+ doc = doc_from_markdown(SAMPLE_SKILL)
+ self.assertEqual(doc.name, "code-reviewer")
+ self.assertEqual(
+ doc.description,
+ "Automated pull request code reviewer checking security and style.",
+ )
+ self.assertEqual(doc.paths, ("src/**/*.py",))
+ self.assertEqual(doc.requires, ("git-tools",))
+ # H1 matching doc name was stripped from body
+ self.assertNotIn("# Code Reviewer", doc.body)
+ self.assertIn("## Guidelines", doc.body)
+
+ def test_doc_from_markdown_fallback_description(self) -> None:
+ raw = "---\nname: simple-tool\n---\n\nA helpful utility for tasks.\n"
+ doc = doc_from_markdown(raw)
+ self.assertEqual(doc.name, "simple-tool")
+ self.assertEqual(doc.description, "A helpful utility for tasks.")
+
+ def test_compile_skill_single_dialect(self) -> None:
+ # 1. Claude Code
+ claude_files = compile_skill(SAMPLE_SKILL, "claude")
+ self.assertEqual(len(claude_files), 1)
+ self.assertEqual(claude_files[0]["relpath"], ".claude/skills/code-reviewer/SKILL.md")
+ self.assertIn('name: "code-reviewer"', claude_files[0]["text"])
+
+ # 2. Cursor
+ cursor_files = compile_skill(SAMPLE_SKILL, "cursor")
+ self.assertEqual(len(cursor_files), 1)
+ self.assertEqual(cursor_files[0]["relpath"], ".agents/skills/code-reviewer/SKILL.md")
+ self.assertIn('name: "code-reviewer"', cursor_files[0]["text"])
+ self.assertIn('"src/**/*.py"', cursor_files[0]["text"])
+
+ # 3. Copilot
+ copilot_files = compile_skill(SAMPLE_SKILL, "copilot")
+ self.assertEqual(len(copilot_files), 1)
+ self.assertTrue(copilot_files[0]["relpath"].startswith(".github/"))
+
+ def test_compile_all_dialects(self) -> None:
+ all_res = compile_all_dialects(SAMPLE_SKILL)
+ self.assertEqual(len(all_res), len(ADAPTERS))
+ for key in ADAPTERS:
+ self.assertIn(key, all_res)
+ self.assertGreater(len(all_res[key]), 0)
+ self.assertIn("relpath", all_res[key][0])
+ self.assertIn("text", all_res[key][0])
+
+ def test_compile_speed_under_10ms(self) -> None:
+ # Warmup
+ compile_all_dialects(SAMPLE_SKILL)
+
+ # Benchmark 50 compilations across all 12 dialects
+ iterations = 50
+ start = time.perf_counter()
+ for _ in range(iterations):
+ compile_all_dialects(SAMPLE_SKILL)
+ total_time = time.perf_counter() - start
+ avg_ms = (total_time / iterations) * 1000
+
+ self.assertLess(avg_ms, 10.0, f"Average compilation took {avg_ms:.2f}ms (expected <10ms)")
+
+ def test_compile_json_interop(self) -> None:
+ raw_json = compile_json(SAMPLE_SKILL)
+ data = json.loads(raw_json)
+ self.assertTrue(data["valid"])
+ self.assertIsInstance(data["dialects"], dict)
+ self.assertEqual(len(data["dialects"]), len(ADAPTERS))
+ self.assertIn("claude", data["dialects"])
+ self.assertIn("cursor", data["dialects"])
+ self.assertIsInstance(data["elapsed_ms"], (int, float))
+
+ def test_compile_json_single_target(self) -> None:
+ raw_json = compile_json(SAMPLE_SKILL, target="cursor")
+ data = json.loads(raw_json)
+ self.assertTrue(data["valid"])
+ self.assertEqual(list(data["dialects"].keys()), ["cursor"])
+
+ def test_validate_skill_diagnostics(self) -> None:
+ invalid_skill = "---\nname: Invalid_Name\n\tbad_tab: true\n"
+ diags = validate_skill(invalid_skill)
+ self.assertGreater(len(diags), 0)
+ messages = [d["message"] for d in diags]
+ # Should flag unclosed frontmatter and tabs
+ self.assertTrue(any("Unclosed YAML frontmatter" in m for m in messages))
+
+
+if __name__ == "__main__":
+ unittest.main()