diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8a087cb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 20 + - name: Install Node deps + working-directory: LumenIO/clients/web + run: npm install + - name: Run vitest + working-directory: LumenIO/clients/web + run: npm test + - name: Run playwright + working-directory: LumenIO/clients/web + run: npx playwright install --with-deps && npx playwright test + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install Python deps + working-directory: LumenIO/hub + run: pip install -r requirements.txt + - name: Run pytest + working-directory: LumenIO/hub + run: pytest diff --git a/.github/workflows/matrix-ci.yml b/.github/workflows/matrix-ci.yml new file mode 100644 index 0000000..0fc6023 --- /dev/null +++ b/.github/workflows/matrix-ci.yml @@ -0,0 +1,79 @@ +name: LumenIO Matrix CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + +jobs: + core: + name: Core Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + - run: | + cd LumenIO/packages/lumenio-core + pip install -e . + pytest tests/ + + variants: + name: Variant Tests + runs-on: ${{ matrix.os }} + needs: core + strategy: + matrix: + variant: [pc, mobile, pcproj, mobileproj, standalone, wearables] + os: [ubuntu-latest] + include: + - variant: pc + sensors: webcam,keyboard,mouse + e2e: desktop + - variant: mobile + sensors: camera,gyro,touch,accelerometer + e2e: mobile + - variant: pcproj + sensors: webcam,keyboard,mouse,projector + e2e: desktop-projector + - variant: mobileproj + sensors: camera,gyro,touch,accelerometer,projector + e2e: mobile-projector + - variant: standalone + sensors: camera,lidar,projector,compute + e2e: embedded + - variant: wearables + sensors: imu,camera,haptic,display + e2e: wearable + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install Core + run: | + cd LumenIO/packages/lumenio-core + pip install -e . + - name: Test Variant ${{ matrix.variant }} + run: | + cd LumenIO/packages/lumenio-${{ matrix.variant }} + pip install -e . + pytest tests/unit/ + - name: E2E Tests ${{ matrix.variant }} + run: | + cd LumenIO/packages/lumenio-${{ matrix.variant }} + npm install + npx playwright install + npx playwright test --project=${{ matrix.e2e }} + + integration: + name: Cross-Variant Integration + runs-on: ubuntu-latest + needs: variants + steps: + - uses: actions/checkout@v4 + - run: | + python LumenIO/scripts/validate_contracts.py + python LumenIO/scripts/version_compatibility_check.py diff --git a/LumenIO/VERSIONING.md b/LumenIO/VERSIONING.md new file mode 100644 index 0000000..d2a368d --- /dev/null +++ b/LumenIO/VERSIONING.md @@ -0,0 +1,37 @@ +# LumenIO Versioning & Release Policy + +## Core (LumenIO-Core) +- Semantic Versioning: MAJOR.MINOR.PATCH +- MAJOR: Breaking contract changes +- MINOR: Backward-compatible features +- PATCH: Bug fixes + +## Variants +- Depend on Core with ranges like `^0.1.0` +- Version independently +- Must pass Core contract tests + +## Release Process +1. Core release +2. Update variant core ranges +3. Matrix CI validates all combinations +4. Integration tests ensure compatibility + +## CHANGELOG Format +``` +## [Version] - YYYY-MM-DD +### Core +- Added: ... +- Changed: ... +- Fixed: ... +### Variant Updates +- PC: ... +- Mobile: ... +### Breaking Changes +- ... +``` + +## Compatibility Matrix +| Core | PC | Mobile | PCProj | MobileProj | Standalone | Wearables | +|------|----|--------|--------|------------|------------|-----------| +|0.1.x|✓|✓|✓|✓|✓|✓| diff --git a/LumenIO/bootstrap.xml b/LumenIO/bootstrap.xml new file mode 100644 index 0000000..5abd716 --- /dev/null +++ b/LumenIO/bootstrap.xml @@ -0,0 +1,80 @@ + + + LumenIO + Gesture-first multi-device programming interface ecosystem + https://github.com/testingground/testingground.github.io + + + LumenIO-Core + Shared contracts, schemas, gesture FSM, calibration, agents + 0.1.0 + + contracts + schemas + gestures + calibration + agents + common + + + + + LumenIO-PC + desktop + webcam,keyboard,mouse + ^0.1.0 + + + LumenIO-Mobile + mobile + camera,gyro,touch,accelerometer + ^0.1.0 + + + LumenIO-PCProj + desktop+projector + webcam,projector,keyboard,mouse + ^0.1.0 + + + LumenIO-MobileProj + mobile+projector + camera,projector,gyro,touch,accelerometer + ^0.1.0 + + + LumenIO-Standalone + embedded + camera,lidar,projector,compute + ^0.1.0 + + + LumenIO-Wearables + wearable + imu,camera,haptic,display + ^0.1.0 + + + + Python 3.11+ + FastAPI + aiortc + OpenCV + MediaPipe/TFLite + Blender Python API + WebRTC/WebGPU/WebXR + Node 20 + Vite/Vitest/Playwright + + + LLM/NN Prompt Engineer + Mech/Electrical/Industrial Engineer + + + CODE_FIRST_MINIMAL + semver_core_pinned_variants + + + LumenIO + bootstrap.xml + + diff --git a/LumenIO/clients/web/e2e/app.spec.js b/LumenIO/clients/web/e2e/app.spec.js new file mode 100644 index 0000000..ab1e6ad --- /dev/null +++ b/LumenIO/clients/web/e2e/app.spec.js @@ -0,0 +1,6 @@ +import { test, expect } from '@playwright/test'; + +test('renders greeting', async ({ page }) => { + await page.setContent('
Hello LumenIO
'); + await expect(page.locator('#app')).toHaveText('Hello LumenIO'); +}); diff --git a/LumenIO/clients/web/index.html b/LumenIO/clients/web/index.html new file mode 100644 index 0000000..0e6a123 --- /dev/null +++ b/LumenIO/clients/web/index.html @@ -0,0 +1,11 @@ + + + + + LumenIO + + +
+ + + diff --git a/LumenIO/clients/web/package.json b/LumenIO/clients/web/package.json new file mode 100644 index 0000000..7a54041 --- /dev/null +++ b/LumenIO/clients/web/package.json @@ -0,0 +1,19 @@ +{ + "name": "lumenio-web-client", + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "test": "vitest run", + "test:e2e": "playwright test" + }, + "devDependencies": { + "@playwright/test": "^1.43.0", + "vite": "^5.2.0", + "vitest": "^1.5.0" + }, + "engines": { + "node": ">=20" + } +} diff --git a/LumenIO/clients/web/src/greet.js b/LumenIO/clients/web/src/greet.js new file mode 100644 index 0000000..021dae1 --- /dev/null +++ b/LumenIO/clients/web/src/greet.js @@ -0,0 +1,3 @@ +export function greet(name) { + return `Hello ${name}`; +} diff --git a/LumenIO/clients/web/src/main.js b/LumenIO/clients/web/src/main.js new file mode 100644 index 0000000..b2fa08c --- /dev/null +++ b/LumenIO/clients/web/src/main.js @@ -0,0 +1,3 @@ +import { greet } from './greet.js'; + +document.getElementById('app').textContent = greet('LumenIO'); diff --git a/LumenIO/clients/web/test/greet.test.js b/LumenIO/clients/web/test/greet.test.js new file mode 100644 index 0000000..e864955 --- /dev/null +++ b/LumenIO/clients/web/test/greet.test.js @@ -0,0 +1,8 @@ +import { describe, it, expect } from 'vitest'; +import { greet } from '../src/greet.js'; + +describe('greet', () => { + it('greets by name', () => { + expect(greet('World')).toBe('Hello World'); + }); +}); diff --git a/LumenIO/clients/web/vitest.config.js b/LumenIO/clients/web/vitest.config.js new file mode 100644 index 0000000..ec3bfec --- /dev/null +++ b/LumenIO/clients/web/vitest.config.js @@ -0,0 +1,5 @@ +export default { + test: { + include: ['test/**/*.test.js'] + } +}; diff --git a/LumenIO/hub/__init__.py b/LumenIO/hub/__init__.py new file mode 100644 index 0000000..11bce57 --- /dev/null +++ b/LumenIO/hub/__init__.py @@ -0,0 +1 @@ +"""Hub package.""" diff --git a/LumenIO/hub/main.py b/LumenIO/hub/main.py new file mode 100644 index 0000000..efa6c5d --- /dev/null +++ b/LumenIO/hub/main.py @@ -0,0 +1,7 @@ +from fastapi import FastAPI + +app = FastAPI() + +@app.get('/ping') +async def ping(): + return {'msg': 'pong'} diff --git a/LumenIO/hub/requirements.txt b/LumenIO/hub/requirements.txt new file mode 100644 index 0000000..fdcc7c2 --- /dev/null +++ b/LumenIO/hub/requirements.txt @@ -0,0 +1,4 @@ +fastapi +uvicorn +httpx +jsonschema diff --git a/LumenIO/hub/tests/test_ping.py b/LumenIO/hub/tests/test_ping.py new file mode 100644 index 0000000..39d4aa0 --- /dev/null +++ b/LumenIO/hub/tests/test_ping.py @@ -0,0 +1,10 @@ +import pytest +from httpx import AsyncClient +from main import app + +@pytest.mark.asyncio +async def test_ping(): + async with AsyncClient(app=app, base_url='http://test') as ac: + res = await ac.get('/ping') + assert res.status_code == 200 + assert res.json() == {'msg': 'pong'} diff --git a/LumenIO/hub/tests/test_schema.py b/LumenIO/hub/tests/test_schema.py new file mode 100644 index 0000000..fee6ef5 --- /dev/null +++ b/LumenIO/hub/tests/test_schema.py @@ -0,0 +1,9 @@ +import json +from jsonschema import validate +from pathlib import Path + +def test_gesture_schema(): + schema_path = Path(__file__).resolve().parent.parent.parent / 'schemas' / 'gesture.json' + schema = json.loads(schema_path.read_text()) + sample = {'type': 'wave', 'confidence': 0.9} + validate(instance=sample, schema=schema) diff --git a/LumenIO/packages/lumenio-core/contracts.json b/LumenIO/packages/lumenio-core/contracts.json new file mode 100644 index 0000000..100bc59 --- /dev/null +++ b/LumenIO/packages/lumenio-core/contracts.json @@ -0,0 +1,80 @@ +{ + "contracts": { + "gesture_fsm": { + "interface": "IGestureFSM", + "methods": [ + "register_gesture(pattern: GesturePattern, callback: Callable)", + "process_frame(frame: Frame) -> GestureEvent[]", + "get_state() -> FSMState", + "reset_state() -> None" + ], + "events": [ + "gesture_detected", + "gesture_started", + "gesture_completed", + "gesture_cancelled" + ] + }, + "calibration_api": { + "interface": "ICalibration", + "methods": [ + "calibrate_device(device_config: DeviceConfig) -> CalibrationResult", + "save_calibration(device_id: str, data: CalibrationData) -> bool", + "load_calibration(device_id: str) -> CalibrationData | None", + "validate_calibration(data: CalibrationData) -> ValidationResult" + ] + }, + "agent_tools": { + "interface": "IAgentTool", + "signatures": [ + "execute(action: ActionSpec, context: ExecutionContext) -> ToolResult", + "validate_input(input: Any) -> ValidationResult", + "get_capabilities() -> CapabilitySet", + "cleanup() -> None" + ] + }, + "device_adapter": { + "interface": "IDeviceAdapter", + "methods": [ + "initialize(config: DeviceConfig) -> bool", + "get_sensor_data() -> SensorData", + "send_output(data: OutputData) -> bool", + "get_capabilities() -> DeviceCapabilities" + ] + } + }, + "schemas": { + "gesture_pattern": { + "type": "object", + "properties": { + "id": {"type": "string"}, + "name": {"type": "string"}, + "sequence": {"type": "array", "items": {"$ref": "#/definitions/gesture_keypoint"}}, + "tolerance": {"type": "number", "minimum": 0, "maximum": 1}, + "min_confidence": {"type": "number", "minimum": 0, "maximum": 1} + }, + "required": ["id", "name", "sequence"] + }, + "device_config": { + "type": "object", + "properties": { + "device_id": {"type": "string"}, + "device_type": {"enum": ["desktop", "mobile", "embedded", "wearable"]}, + "sensors": {"type": "array", "items": {"type": "string"}}, + "capabilities": {"$ref": "#/definitions/device_capabilities"}, + "calibration_data": {"$ref": "#/definitions/calibration_data"} + }, + "required": ["device_id", "device_type", "sensors"] + }, + "agent_action": { + "type": "object", + "properties": { + "tool": {"type": "string"}, + "operation": {"type": "string"}, + "parameters": {"type": "object"}, + "context": {"$ref": "#/definitions/execution_context"} + }, + "required": ["tool", "operation"] + } + } +} diff --git a/LumenIO/packages/lumenio-mobile/.gitkeep b/LumenIO/packages/lumenio-mobile/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/LumenIO/packages/lumenio-mobileproj/.gitkeep b/LumenIO/packages/lumenio-mobileproj/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/LumenIO/packages/lumenio-pc/.gitkeep b/LumenIO/packages/lumenio-pc/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/LumenIO/packages/lumenio-pcproj/.gitkeep b/LumenIO/packages/lumenio-pcproj/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/LumenIO/packages/lumenio-standalone/.gitkeep b/LumenIO/packages/lumenio-standalone/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/LumenIO/packages/lumenio-wearables/.gitkeep b/LumenIO/packages/lumenio-wearables/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/LumenIO/schemas/gesture.json b/LumenIO/schemas/gesture.json new file mode 100644 index 0000000..177de45 --- /dev/null +++ b/LumenIO/schemas/gesture.json @@ -0,0 +1,11 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Gesture", + "type": "object", + "properties": { + "type": {"type": "string"}, + "confidence": {"type": "number", "minimum": 0, "maximum": 1} + }, + "required": ["type", "confidence"], + "additionalProperties": false +} diff --git a/LumenIO/scripts/scaffold_variants.json b/LumenIO/scripts/scaffold_variants.json new file mode 100644 index 0000000..aae6f47 --- /dev/null +++ b/LumenIO/scripts/scaffold_variants.json @@ -0,0 +1,59 @@ +{ + "variant_template": { + "structure": { + "package.json": "variant package manifest", + "src/": { + "adapters/": "device-specific adapter implementations", + "config/": "variant-specific configuration", + "main.py": "entry point importing Core + adapters" + }, + "tests/": { + "unit/": "variant-specific unit tests", + "e2e/": "device-specific E2E tests (Playwright)" + }, + "pyproject.toml": "Python dependencies + Core version pin" + }, + "example_package_json": { + "name": "@lumenio/pc", + "version": "0.1.0", + "description": "LumenIO PC Desktop Variant", + "main": "src/main.py", + "dependencies": { + "@lumenio/core": "^0.1.0" + }, + "devDependencies": { + "vitest": "^2.0.0", + "playwright": "^1.40.0" + }, + "scripts": { + "dev": "python src/main.py --dev", + "build": "python -m build", + "test": "vitest", + "test:e2e": "playwright test" + }, + "lumenio": { + "variant": "pc", + "target": "desktop", + "sensors": ["webcam", "keyboard", "mouse"], + "features": { + "projector": false, + "mobile_sensors": false, + "embedded_compute": false + } + } + }, + "adapter_template": { + "class_name": "PCDeviceAdapter", + "imports": ["from lumenio_core.contracts import IDeviceAdapter"], + "methods": ["initialize", "get_sensor_data", "send_output", "get_capabilities"] + } + }, + "variant_configs": { + "pc": {"sensors": ["webcam", "keyboard", "mouse"], "projector": false}, + "mobile": {"sensors": ["camera", "gyro", "touch", "accelerometer"], "projector": false}, + "pcproj": {"sensors": ["webcam", "keyboard", "mouse", "projector"], "projector": true}, + "mobileproj": {"sensors": ["camera", "gyro", "touch", "accelerometer", "projector"], "projector": true}, + "standalone": {"sensors": ["camera", "lidar", "projector", "compute"], "embedded": true}, + "wearables": {"sensors": ["imu", "camera", "haptic", "display"], "wearable": true} + } +}