diff --git a/.github/workflows/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md similarity index 100% rename from .github/workflows/ISSUE_TEMPLATE/bug-report.md rename to .github/ISSUE_TEMPLATE/bug-report.md diff --git a/.github/workflows/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md similarity index 100% rename from .github/workflows/ISSUE_TEMPLATE/feature-request.md rename to .github/ISSUE_TEMPLATE/feature-request.md diff --git a/.github/workflows/ISSUE_TEMPLATE/config.yml b/.github/workflows/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 3ded0c8..0000000 --- a/.github/workflows/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: OpenCode Game Studios Discussion - url: https://github.com/striderZA/OpenCodeGameStudios/discussions - about: Please ask questions and discuss ideas in GitHub Discussions. - - name: Contributing Guide - url: https://github.com/striderZA/OpenCodeGameStudios/blob/master/CONTRIBUTING.md - about: Review the contributing guide before submitting. diff --git a/.github/workflows/agent-validation.yml b/.github/workflows/agent-validation.yml index 499f77e..cb947dc 100644 --- a/.github/workflows/agent-validation.yml +++ b/.github/workflows/agent-validation.yml @@ -12,6 +12,8 @@ jobs: name: Agent Framework Validation steps: - uses: actions/checkout@v4 + with: + submodules: recursive - uses: actions/setup-node@v4 with: node-version: 20 @@ -29,6 +31,8 @@ jobs: name: Workflow Integrity steps: - uses: actions/checkout@v4 + with: + submodules: recursive - uses: actions/setup-node@v4 with: node-version: 20 diff --git a/.gitignore b/.gitignore index 0686198..90c608e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ desktop.ini *.swo *~ tests/agents/validation-report.md + +# Submodule dependencies +tools/aseprite-mcp/.venv/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ac2f5f6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tools/aseprite-mcp"] + path = tools/aseprite-mcp + url = https://github.com/striderZA/aseprite-mcp.git diff --git a/.opencode/commands/art-generate.md b/.opencode/commands/art-generate.md new file mode 100644 index 0000000..e617782 --- /dev/null +++ b/.opencode/commands/art-generate.md @@ -0,0 +1,8 @@ +--- +name: art-generate +description: "Generate placeholder art from asset specs using the Aseprite MCP" +skill: art-generate +category: design +--- + +Invokes `/art-generate` skill. diff --git a/.opencode/skills/art-generate/SKILL.md b/.opencode/skills/art-generate/SKILL.md new file mode 100644 index 0000000..8315a6a --- /dev/null +++ b/.opencode/skills/art-generate/SKILL.md @@ -0,0 +1,321 @@ +--- +name: art-generate +description: "Generates placeholder .aseprite files from asset specs using the Aseprite MCP. Reads asset specs and art bible, creates sprites with correct dimensions/palette/layers, exports PNGs. Run after /asset-spec has produced specs and /art-bible exists." +argument-hint: "[system: | level: | character: | path/to/spec.md]" +user-invocable: true +allowed-tools: Read, Glob, Grep, Write, Edit, Task, question +--- + +If no argument is provided, read `design/assets/asset-manifest.md`: +- If it exists: find the first target with any asset at `Status: Needed` and use `question`: + - Prompt: "The next unspecced target is **[target]**. Generate placeholder art for it?" + - Options: `[A] Yes — generate for [target]` / `[B] Pick a different target` / `[C] Stop here` +- If no manifest: fail with: + > "No asset manifest found. Run `/asset-spec [target]` first — I need asset specs to generate art from." + +--- + +## Phase 0: Parse Target + +Resolve the target: +1. If argument matches `system:` → spec at `design/assets/specs/[name]-assets.md` +2. If argument matches `level:` → spec at `design/assets/specs/[name]-assets.md` +3. If argument matches `character:` → spec at `design/assets/specs/[name]-assets.md` +4. If argument is a path → read that file directly +5. If argument is a path to a .md file → treat as spec file + +Fail if the spec file does not exist: +> "No asset spec found at `[path]`. Run `/asset-spec [target]` first." + +--- + +## Phase 1: Gather Context + +Read all source material before generating anything. + +### Required reads: +- **Asset spec**: Read the resolved spec file. Extract all `ASSET-NNN` entries. For each, extract: Category, Dimensions, Format, Naming, Visual Description. +- **Art bible**: Read `design/art/art-bible.md` — fail if missing: + > "No art bible found. Run `/art-bible` first — I need the palette and shape language to generate art." + Extract: Color System (Section 2), Shape Language (Section 3), Asset Standards (Section 8 — dimension tiers). +- **Asset manifest**: Read `design/assets/asset-manifest.md` — note which assets already have placeholders. Filter to assets with `Status: Needed`. + +### Present context summary: +> **art-generate: [Target Type] — [Target Name]** +> - Spec: [path] — [N] assets identified +> - Art bible: found — [N] palette colors, shape language: [style] +> - Assets to generate: [N] (filtered from [N] total — [N] already have placeholders) +> - Output: assets/source/[target]/ and assets/sprites/[target]/ + +--- + +## Phase 2: Per-Asset Generation + +For each asset with `Status: Needed`, execute the following pipeline. **Process one asset at a time — do NOT batch MCP calls.** + +### Step 2a: Create Canvas + +Construct the filename as `assets/source/[target]/[naming]` (replace `.png` or similar with `.aseprite`). + +Use the `create_canvas` MCP tool: +``` +create_canvas(width, height, filename) +``` + +Wait for success. If it fails, stop this asset and report. + +### Step 2b: Apply Palette + +Load the art bible palette. If the art bible specifies a named palette resource (e.g., "DB16", "RPG"), use: + +``` +load_palette_from_resource(filename, resource_name) +``` + +Otherwise extract hex colors from the art bible's Color System section and apply: + +``` +set_palette(filename, [hex_colors_array]) +``` + +### Step 2c: Create Layer Structure + +Create layers matching the asset category template: + +**Sprite / 2D Art:** +``` +add_layer(filename, "body") +set_layer(filename, "body") +set_layer_label_color(filename, "body", color_hex) +add_layer(filename, "details") +add_layer(filename, "outline") +``` + +**UI Icon:** +``` +add_layer(filename, "icon") +``` + +**VFX:** +``` +add_layer(filename, "core") +set_layer_blend_mode(filename, "core", "normal") +add_layer(filename, "glow") +set_layer_blend_mode(filename, "glow", "add") +add_layer(filename, "sparks") +``` + +**Environment:** +``` +add_layer(filename, "base") +add_layer(filename, "shading") +add_layer(filename, "details") +``` + +**Character Sprite:** +``` +add_layer(filename, "body") +add_layer(filename, "head") +add_layer(filename, "arms") +add_layer(filename, "legs") +add_layer(filename, "outline") +``` + +### Step 2d: Draw Placeholder Content + +Draw shapes on each layer following the art bible's shape language. Use the semantic color from the art bible for the asset's role. + +General approach per layer: +1. `set_layer(filename, layer_name)` — activate the layer +2. `set_frame(filename, 1)` — draw on frame 1 +3. Draw shapes: + - **Body/base**: Fill a rectangle/circle/polygon covering ~80% of the canvas at the body center position + - **Details**: Smaller shapes offset from center, using secondary/accent colors from the palette + - **Outline**: Thin rectangle around the body bounds using a dark color + +Use these drawing tools as appropriate: +- `draw_rectangle_at(filename, layer, frame, x, y, w, h, color, fill=true)` for blocky shapes +- `draw_circle_at(filename, layer, frame, cx, cy, r, color, fill=true)` for round shapes +- `draw_polygon(filename, layer, frame, points, color, fill=true)` for angular shapes (character limbs) +- `fill_area_at(filename, layer, frame, cx, cy, color)` for filled regions +- `apply_gradient_rect(filename, layer, frame, x, y, w, h, start, end, horizontal)` for gradient fills + +### Step 2e: Handle Animation + +If the spec indicates multiple frames (e.g., "4-frame sprite sheet"): + +``` +add_frames(filename, count=3, duration_ms=200) # frame 1 already exists +set_frame_duration(filename, 1, 200) +``` + +For each additional frame, duplicate frame 1 and add minor position offsets for limbs: +``` +copy_frame(filename, source_frame=1, target_frame=N) +``` + +Then tag the animation: +``` +set_tag(filename, name="default", from_frame=1, to_frame=N, direction="forward") +``` + +If not animated, skip this step. + +### Step 2f: Export + +``` +export_sprite(filename, output_path="assets/sprites/[target]/[naming]") +``` + +### Step 2g: Verify + +Use the quality tools to check the output: + +``` +validate_scene(filename, required_layers=[list from step 2c]) +``` + +Expected: JSON confirming all layers and frames exist. + +Also verify the exported file exists: +``` +Test-Path "assets/sprites/[target]/[naming]" +``` + +Expected: `True` + +### Error Handling + +If any MCP tool call fails: +``` +Question: "Tool [tool_name] failed for ASSET-[NNN] ([asset name]): [error]. Options:" +A) Retry this asset +B) Skip this asset, continue with next +C) Stop generation entirely +``` + +--- + +## Phase 3: Update Manifest + +After all assets are generated (or after partial completion), update `design/assets/asset-manifest.md`. + +For each generated asset, change its status from `Needed` to `Placeholder Created`. Add a `Source` column if it doesn't exist: + +``` +| ASSET-001 | hero-sprite | Sprite | Placeholder Created | design/assets/specs/hero-assets.md | assets/source/hero/ASSET-001-hero-sprite.aseprite | +``` + +If the manifest doesn't have a `Source` column yet, rewrite the header to include it. + +--- + +## Phase 4: Summary Report + +Present a completion summary: + +> **art-generate complete for [target]:** +> - [N]/[M] assets generated successfully +> - [N] failed (skipped) +> - Output: assets/source/[target]/ (source .aseprite files) +> - Output: assets/sprites/[target]/ (exported PNGs) +> - Manifest updated: design/assets/asset-manifest.md + +--- + +## Phase 5: Next Steps + +Use `question`: +- Prompt: "Placeholder art generated for **[target]**. What's next?" +- Options: + - `[A] Generate another target — /art-generate [next-target]` + - `[B] Run /asset-audit — validate generated assets against specs` + - `[C] Review generated art manually` + - `[D] Stop here` + +--- + +## Per-Category Drawing Templates + +### Sprite / 2D Asset (Default) + +``` +Canvas: spec dimensions (e.g. 256x256) +Layers: body, details, outline + +Frame 1: + layer=body: fill_rect(32, 32, 192, 192, body_color) + layer=details: draw_rect(48, 48, 160, 160, accent_color, fill=false) + layer=outline: draw_rect(30, 30, 196, 196, outline_color, fill=false) +``` + +### UI Icon + +``` +Canvas: 32x32 or 64x64 +Layers: icon + +Frame 1: + layer=icon: fill_rect(4, 4, 24, 24, palette[0]) +``` + +### VFX + +``` +Canvas: matching target sprite dimensions +Layers: core, glow, sparks + +Frame 1: + layer=core: circle(center, radius*0.3, core_color, fill=true) + layer=glow: circle(center, radius, glow_color, fill=true, set opacity=128) +``` + +### Environment Prop + +``` +Canvas: tile dimensions +Layers: base, shading, details + +Frame 1: + layer=base: fill_rect(0, 0, w, h, base_color) + layer=shading: gradient_rect(0, 0, w, h, shade_top, shade_bottom) +``` + +### Character Sprite + +``` +Canvas: sprite sheet width, frame height +Layers: body, head, arms, legs, outline + +Frame 1 (idle pose): + layer=body: fill_rect(24, 40, 16, 24, body_color) + layer=head: fill_rect(28, 24, 8, 12, skin_color) + layer=arms: fill_rect(16, 44, 8, 8, skin_color) + draw_rect(40, 44, 8, 8, skin_color) + layer=legs: fill_rect(24, 64, 6, 12, leg_color) + fill_rect(34, 64, 6, 12, leg_color) + layer=outline: draw_rect(22, 22, 20, 54, outline) + +Frame 2 (walk frame 1): copy_frame(1, 2) + layer=legs: offset legs by +2 on x to show stride +``` + +--- + +## Error Recovery + +| Error | Recovery | +|-------|----------| +| Spec file not found | Fail with clear message pointing to /asset-spec | +| Art bible not found | Fail — generation without palette produces wrong colors | +| MCP tool timeout | Report tool + args that failed, offer retry/skip/stop | +| Palette resource unknown | Fall back to `set_palette` with hex colors from art bible | +| Export fails | Check file path permissions, suggest manual export | +| validate_scene fails | Report which layers/frames are missing, offer to fix via ensure_layers_present | + +## Collaborative Protocol + +- Never generate without user confirming the target +- Report each asset's success/failure immediately after processing it +- Write the manifest update only after explicit approval +- If the art bible's palette has changed since the spec was written, ask which to use diff --git a/.opencode/skills/help/SKILL.md b/.opencode/skills/help/SKILL.md index 4d38b1a..5a1441c 100644 --- a/.opencode/skills/help/SKILL.md +++ b/.opencode/skills/help/SKILL.md @@ -5,7 +5,7 @@ argument-hint: "[optional: what you just finished, e.g. 'finished design-review' user-invocable: true allowed-tools: Read, Glob, Grep context: | - !echo "=== Live Project State ===" && echo "Stage: $(cat production/stage.txt 2>/dev/null | tr -d '[:space:]' || echo 'not set')" && echo "Latest sprint: $(ls -t production/sprints/*.md 2>/dev/null | head -1 || echo 'none')" && echo "Session state: $(head -5 production/session-state/active.md 2>/dev/null || echo 'none')" + Static help skill — reads production artifacts directly to determine project state and next steps. model: opencode-go/deepseek-v4-flash --- diff --git a/assets/source/.gitkeep b/assets/source/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/sprites/.gitkeep b/assets/sprites/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/feature-requests/qmd-doc-search.md b/feature-requests/qmd-doc-search.md new file mode 100644 index 0000000..1d83b15 --- /dev/null +++ b/feature-requests/qmd-doc-search.md @@ -0,0 +1,34 @@ +--- +name: qmd markdown search integration +about: Integrate qmd as a local CLI search engine for docs/knowledge base +title: 'feat: integrate qmd for markdown document search' +labels: enhancement +assignees: '' +--- + +**Is your feature request related to a problem?** +As the project's documentation base grows (engine reference docs, ADRs, GDDs, skill files, sprint logs), agents spend increasing context and time searching through plain markdown. The project needs a fast, local, agent-friendly search layer that doesn't require shipping content to external APIs. + +**Describe the solution you'd like** + +Integrate [qmd](https://github.com/tobi/qmd) (Query Markup Documents) — a mini CLI search engine purpose-built for markdown knowledge bases: + +1. **Installation** — Add `@tobilu/qmd` as a dev dependency (npm or bun global install). +2. **Indexing** — Run `qmd index` across key doc directories (`docs/`, `.opencode/agents/`, `.opencode/skills/`, `design/`, `production/`) via a script or pre-commit hook. +3. **Skill / command** — Create an `.opencode/skills/qmd-search/SKILL.md` skill that teaches agents to use: + - `qmd search "..."` for fast BM25 keyword search + - `qmd vsearch "..."` for semantic vector search + - `qmd query "..."` for hybrid (FTS + vector + re-ranking) best-quality search +4. **Context hook integration** — Wire into the `ccgs-hooks.ts` plugin so relevant doc snippets are auto-retrieved as session context when an agent loads. +5. **CI freshness check** — Add a GitHub Action step that validates the qmd index is up-to-date with the doc tree. + +**Describe alternatives you've considered** + +- **ripgrep / grep** — Fast but no ranking, no semantic search, no relevance scoring. +- **WebFetch for docs** — Requires network, doesn't work for local/private docs. +- **Context7 MCP** — Good for external libraries, not for project-internal docs. +- **Rolling our own FTS** — qmd already bundles BM25 + sqlite-vec embeddings + query expansion + re-ranking in a single CLI. No need to reinvent. + +**Additional context** + +qmd is authored by tobi (Tobi L.) — well-maintained, MIT-licensed, 24k+ stars. It stores its index in SQLite (FTS5 + sqlite-vec), is fully local, and has no external API dependencies. The `.claude-plugin/` directory in the qmd repo suggests it was designed with AI agent integration in mind. diff --git a/feature-requests/unity-mcp-integration.md b/feature-requests/unity-mcp-integration.md new file mode 100644 index 0000000..14c6c84 --- /dev/null +++ b/feature-requests/unity-mcp-integration.md @@ -0,0 +1,27 @@ +--- +name: Unity MCP integration +about: Add Unity Engine support alongside existing Godot setup +title: 'feat: Unity MCP integration' +labels: enhancement +assignees: '' +--- + +**Is your feature request related to a problem?** +The project is currently locked to Godot 4. Unity developers cannot contribute or prototype within this repo's agent framework. The `.opencode/agents/` directory has dedicated Unity specialist agents (`unity-specialist`, `unity-dots-specialist`, etc.) but no tooling, MCP server, or engine reference docs to back them up. + +**Describe the solution you'd like** + +1. **Unity MCP server** — Install and configure a Unity MCP server (e.g., `unity-mcp`) so agents can inspect scenes, assets, and project state directly from conversation. +2. **Engine reference docs** — Populate `docs/engine-reference/unity/` with version-pinned API snapshots for the target Unity version (determine via `hub` or project file). +3. **Agent wiring** — Configure the existing Unity specialist agents in `.opencode/agents/` to use the MCP server and reference docs. +4. **Template documentation** — Add Unity-specific sections to `setup-engine` skill and onboarding flow (`/start`, `/setup-engine unity `). + +**Describe alternatives you've considered** + +- Dual-repo setup (Godot + Unity in separate repos) — loses cross-studio coordination and shared agent config. +- Unreal-only + Godot — doesn't cover the Unity use case. +- Manual Unity workflows without MCP — agents can't inspect project state, severely limiting automated dev-story and code-review. + +**Additional context** + +The OCGS template already ships Unity specialist agents at `.opencode/agents/unity-specialist.md`, `unity-dots-specialist.md`, `unity-shader-specialist.md`, `unity-addressables-specialist.md`, and `unity-ui-specialist.md`. This FR activates them. diff --git a/framework/docs/superpowers/plans/2026-05-06-aseprite-mcp-integration.md b/framework/docs/superpowers/plans/2026-05-06-aseprite-mcp-integration.md new file mode 100644 index 0000000..9ac79f4 --- /dev/null +++ b/framework/docs/superpowers/plans/2026-05-06-aseprite-mcp-integration.md @@ -0,0 +1,571 @@ +# Aseprite MCP Integration Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Integrate aseprite-mcp into the OCGS workflow — git submodule, project MCP config, asset directories, and the `art-generate` skill for programmatic placeholder art from specs. + +**Architecture:** aseprite-mcp lives at `tools/aseprite-mcp/` as a git submodule. The project's `opencode.json` declares it as a project-scoped MCP server (enabled). A new OCGS skill `art-generate` reads asset specs + art bible, creates .aseprite files via the MCP tools, and exports PNGs. + +**Tech Stack:** Python 3.13+, FastMCP (mcp[cli]), uv, Aseprite Lua scripting, OCGS skill framework. + +--- + +### Prior Setup + +Check that the Aseprite binary exists at the expected path: + +```powershell +Test-Path "E:\Jaco\Projects\Godot\aseprite\build\bin\aseprite.exe" +``` + +If `False`, the `ASEPRITE_PATH` in opencode.json needs adjustment. + +--- + +### Task 1: Add Git Submodule + Install Dependencies + +**Files:** +- Create: `tools/aseprite-mcp/` (submodule) + +- [ ] **Step 1: Add submodule** + +```powershell +git submodule add https://github.com/striderZA/aseprite-mcp.git tools/aseprite-mcp +``` + +Expected output: `Cloning into 'E:/Jaco/Projects/Godot/OpenCodeGameDesign/tools/aseprite-mcp'...` + +- [ ] **Step 2: Install Python dependencies** + +```powershell +uv sync +``` + +Workdir: `tools/aseprite-mcp` + +Expected: Creates `.venv` and installs httpx, mcp[cli], python-dotenv. + +- [ ] **Step 3: Verify uv can run the server** + +```powershell +uv run --no-sync -m aseprite_mcp --help +``` + +Workdir: `tools/aseprite-mcp` + +Expected: Prints FastMCP usage (no error). Note: `--no-sync` since we just ran `uv sync`. + +- [ ] **Step 4: Create .gitignore entry (if needed)** + +Check if `.gitignore` already covers `tools/` submodules. Add if missing: + +``` +# Submodule dependencies +tools/aseprite-mcp/.venv/ +``` + +- [ ] **Step 5: Commit** + +```powershell +git add tools/aseprite-mcp .gitignore +git commit -m "feat: add aseprite-mcp submodule at tools/aseprite-mcp" +``` + +--- + +### Task 2: Add Project-Level MCP Config + +**Files:** +- Modify: `opencode.json` (add `mcp` block) + +- [ ] **Step 1: Read existing opencode.json to confirm current shape** + +```powershell +Get-Content opencode.json +``` + +- [ ] **Step 2: Add the mcp block** + +Edit `opencode.json` to add the `mcp` key at the top level, before `permission`: + +```json +{ + "$schema": "https://opencode.ai/config.json", + "plugin": ["./.opencode/plugins/ccgs-hooks.ts"], + "command": { + "hybrid-prototype": { + "template": "Run the hybrid-prototype skill: load .opencode/skills/hybrid-prototype/SKILL.md and follow the fast-lane prototype workflow. Concept: $ARGUMENTS", + "description": "Fast-lane prototype for discovery phase — build a playable prototype in 2-3 days with minimal process overhead.", + "agent": "prototyper" + } + }, + "mcp": { + "aseprite": { + "type": "local", + "command": ["uv", "--directory", "tools/aseprite-mcp", "run", "--no-sync", "-m", "aseprite_mcp"], + "enabled": true, + "environment": { + "ASEPRITE_PATH": "E:\\Jaco\\Projects\\Godot\\aseprite\\build\\bin\\aseprite.exe" + } + }, + "godot": { + "type": "local", + "command": ["npx", "@coding-solo/godot-mcp"], + "enabled": false, + "env": { + "GODOT_PATH": "E:\\Jaco\\Projects\\Godot\\engine\\godot.exe", + "DEBUG": "true" + } + } + }, + "permission": { + ... + } +} +``` + +- [ ] **Step 3: Validate JSON** + +```powershell +Get-Content opencode.json | python -m json.tool > $null +``` + +Expected: No output (valid JSON). + +- [ ] **Step 4: Commit** + +```powershell +git add opencode.json +git commit -m "feat: add project-scoped MCP config for aseprite and godot" +``` + +--- + +### Task 3: Create Asset Directory Structure + +**Files:** +- Create: `assets/source/.gitkeep` +- Create: `assets/sprites/.gitkeep` + +- [ ] **Step 1: Create directories and .gitkeep files** + +```powershell +New-Item -ItemType Directory -Path "assets/source" -Force | Out-Null +New-Item -ItemType Directory -Path "assets/sprites" -Force | Out-Null +New-Item -ItemType File -Path "assets/source/.gitkeep" -Force | Out-Null +New-Item -ItemType File -Path "assets/sprites/.gitkeep" -Force | Out-Null +``` + +- [ ] **Step 2: Commit** + +```powershell +git add assets/ +git commit -m "feat: add asset output directories for art-generate skill" +``` + +--- + +### Task 4: Create art-generate Skill + +**Files:** +- Create: `.opencode/skills/art-generate/SKILL.md` + +- [ ] **Step 1: Write the skill file** + +Write `.opencode/skills/art-generate/SKILL.md` with the following content: + +```markdown +--- +name: art-generate +description: "Generates placeholder .aseprite files from asset specs using the Aseprite MCP. Reads asset specs and art bible, creates sprites with correct dimensions/palette/layers, exports PNGs. Run after /asset-spec has produced specs and /art-bible exists." +argument-hint: "[system: | level: | character: | path/to/spec.md]" +user-invocable: true +allowed-tools: Read, Glob, Grep, Write, Edit, Task, question +--- + +If no argument is provided, read `design/assets/asset-manifest.md`: +- If it exists: find the first target with any asset at `Status: Needed` and use `question`: + - Prompt: "The next unspecced target is **[target]**. Generate placeholder art for it?" + - Options: `[A] Yes — generate for [target]` / `[B] Pick a different target` / `[C] Stop here` +- If no manifest: fail with: + > "No asset manifest found. Run `/asset-spec [target]` first — I need asset specs to generate art from." + +--- + +## Phase 0: Parse Target + +Resolve the target: +1. If argument matches `system:` → spec at `design/assets/specs/[name]-assets.md` +2. If argument matches `level:` → spec at `design/assets/specs/[name]-assets.md` +3. If argument matches `character:` → spec at `design/assets/specs/[name]-assets.md` +4. If argument is a path → read that file directly +5. If argument is a path to a .md file → treat as spec file + +Fail if the spec file does not exist: +> "No asset spec found at `[path]`. Run `/asset-spec [target]` first." + +--- + +## Phase 1: Gather Context + +Read all source material before generating anything. + +### Required reads: +- **Asset spec**: Read the resolved spec file. Extract all `ASSET-NNN` entries. For each, extract: Category, Dimensions, Format, Naming, Visual Description. +- **Art bible**: Read `design/art/art-bible.md` — fail if missing: + > "No art bible found. Run `/art-bible` first — I need the palette and shape language to generate art." + Extract: Color System (Section 2), Shape Language (Section 3), Asset Standards (Section 8 — dimension tiers). +- **Asset manifest**: Read `design/assets/asset-manifest.md` — note which assets already have placeholders. Filter to assets with `Status: Needed`. + +### Present context summary: +> **art-generate: [Target Type] — [Target Name]** +> - Spec: [path] — [N] assets identified +> - Art bible: found — [N] palette colors, shape language: [style] +> - Assets to generate: [N] (filtered from [N] total — [N] already have placeholders) +> - Output: assets/source/[target]/ and assets/sprites/[target]/ + +--- + +## Phase 2: Per-Asset Generation + +For each asset with `Status: Needed`, execute the following pipeline. **Process one asset at a time — do NOT batch MCP calls.** + +### Step 2a: Create Canvas + +Construct the filename as `assets/source/[target]/[naming]` (replace `.png` or similar with `.aseprite`). + +``` +create_canvas(width, height, filename) +``` + +Wait for success. If it fails, stop this asset and report. + +### Step 2b: Apply Palette + +Load the art bible palette. If the art bible specifies a named palette resource (e.g., "DB16", "RPG"), use: + +``` +load_palette_from_resource(filename, resource_name) +``` + +Otherwise extract hex colors from the art bible's Color System section and apply: + +``` +set_palette(filename, [hex_colors_array]) +``` + +### Step 2c: Create Layer Structure + +Create layers matching the asset category template: + +**Sprite / 2D Art:** +``` +add_layer(filename, "body") +set_layer(filename, "body") +set_layer_label_color(filename, "body", color_hex) +add_layer(filename, "details") +add_layer(filename, "outline") +``` + +**UI Icon:** +``` +add_layer(filename, "icon") +``` + +**VFX:** +``` +add_layer(filename, "core") +set_layer_blend_mode(filename, "core", "normal") +add_layer(filename, "glow") +set_layer_blend_mode(filename, "glow", "add") +add_layer(filename, "sparks") +``` + +**Environment:** +``` +add_layer(filename, "base") +add_layer(filename, "shading") +add_layer(filename, "details") +``` + +**Character Sprite:** +``` +add_layer(filename, "body") +add_layer(filename, "head") +add_layer(filename, "arms") +add_layer(filename, "legs") +add_layer(filename, "outline") +``` + +### Step 2d: Draw Placeholder Content + +Draw shapes on each layer following the art bible's shape language. Use the semantic color from the art bible for the asset's role. + +General approach per layer: +1. `set_layer(filename, layer_name)` — activate the layer +2. `set_frame(filename, 1)` — draw on frame 1 +3. Draw shapes: + - **Body/base**: Fill a rectangle/circle/polygon covering ~80% of the canvas at the body center position + - **Details**: Smaller shapes offset from center, using secondary/accent colors from the palette + - **Outline**: Thin rectangle around the body bounds using a dark color + +Use these drawing tools as appropriate: +- `draw_rectangle_at(filename, layer, frame, x, y, w, h, color, fill=true)` for blocky shapes +- `draw_circle_at(filename, layer, frame, cx, cy, r, color, fill=true)` for round shapes +- `draw_polygon(filename, layer, frame, points, color, fill=true)` for angular shapes (character limbs) +- `fill_area_at(filename, layer, frame, cx, cy, color)` for filled regions +- `apply_gradient_rect(filename, layer, frame, x, y, w, h, start, end, horizontal)` for gradient fills + +### Step 2e: Handle Animation + +If the spec indicates multiple frames (e.g., "4-frame sprite sheet"): + +``` +add_frames(filename, count=3, duration_ms=200) # frame 1 already exists +set_frame_duration(filename, 1, 200) +``` + +For each additional frame, duplicate frame 1 and add minor position offsets for limbs: +``` +copy_frame(filename, source_frame=1, target_frame=N) +``` + +Then tag the animation: +``` +set_tag(filename, name="default", from_frame=1, to_frame=N, direction="forward") +``` + +If not animated, skip this step. + +### Step 2f: Export + +``` +export_sprite(filename, output_path="assets/sprites/[target]/[naming]") +``` + +### Step 2g: Verify + +Use the quality tools to check the output: + +``` +validate_scene(filename, required_layers=[list from step 2c]) +``` + +Expected: JSON confirming all layers and frames exist. + +Also verify the exported file exists: +``` +Test-Path "assets/sprites/[target]/[naming]" +``` + +Expected: `True` + +### Error Handling + +If any MCP tool call fails: +``` +Question: "Tool [tool_name] failed for ASSET-[NNN] ([asset name]): [error]. Options:" +A) Retry this asset +B) Skip this asset, continue with next +C) Stop generation entirely +``` + +--- + +## Phase 3: Update Manifest + +After all assets are generated (or after partial completion), update `design/assets/asset-manifest.md`. + +For each generated asset, change its status from `Needed` to `Placeholder Created`. Add a `Source` column if it doesn't exist: + +``` +| ASSET-001 | hero-sprite | Sprite | Placeholder Created | design/assets/specs/hero-assets.md | assets/source/hero/ASSET-001-hero-sprite.aseprite | +``` + +If the manifest doesn't have a `Source` column yet, rewrite the header to include it. + +--- + +## Phase 4: Summary Report + +Present a completion summary: + +> **art-generate complete for [target]:** +> - [N]/[M] assets generated successfully +> - [N] failed (skipped) +> - Output: assets/source/[target]/ (source .aseprite files) +> - Output: assets/sprites/[target]/ (exported PNGs) +> - Manifest updated: design/assets/asset-manifest.md + +--- + +## Phase 5: Next Steps + +Use `question`: +- Prompt: "Placeholder art generated for **[target]**. What's next?" +- Options: + - `[A] Generate another target — /art-generate [next-target]` + - `[B] Run /asset-audit — validate generated assets against specs` + - `[C] Review generated art manually` + - `[D] Stop here` + +--- + +## Per-Category Drawing Templates + +### Sprite / 2D Asset (Default) + +``` +Canvas: spec dimensions (e.g. 256x256) +Layers: body, details, outline + +Frame 1: + layer=body: fill_rect(32, 32, 192, 192, body_color) + layer=details: draw_rect(48, 48, 160, 160, accent_color, fill=false) + layer=outline: draw_rect(30, 30, 196, 196, outline_color, fill=false) +``` + +### UI Icon + +``` +Canvas: 32x32 or 64x64 +Layers: icon + +Frame 1: + layer=icon: fill_rect(4, 4, 24, 24, palette[0]) +``` + +### VFX + +``` +Canvas: matching target sprite dimensions +Layers: core, glow, sparks + +Frame 1: + layer=core: circle(center, radius*0.3, core_color, fill=true) + layer=glow: circle(center, radius, glow_color, fill=true, set opacity=128) +``` + +### Environment Prop + +``` +Canvas: tile dimensions +Layers: base, shading, details + +Frame 1: + layer=base: fill_rect(0, 0, w, h, base_color) + layer=shading: gradient_rect(0, 0, w, h, shade_top, shade_bottom) +``` + +### Character Sprite + +``` +Canvas: sprite sheet width, frame height +Layers: body, head, arms, legs, outline + +Frame 1 (idle pose): + layer=body: fill_rect(24, 40, 16, 24, body_color) # torso + layer=head: fill_rect(28, 24, 8, 12, skin_color) # head + layer=arms: fill_rect(16, 44, 8, 8, skin_color) # left arm + draw_rect(40, 44, 8, 8, skin_color) # right arm + layer=legs: fill_rect(24, 64, 6, 12, leg_color) # left leg + fill_rect(34, 64, 6, 12, leg_color) # right leg + layer=outline: draw_rect(22, 22, 20, 54, outline) # body outline + +Frame 2 (walk frame 1): copy_frame(1, 2) + layer=legs: offset legs by +2 on x to show stride +``` + +--- + +## Error Recovery + +| Error | Recovery | +|-------|----------| +| Spec file not found | Fail with clear message pointing to /asset-spec | +| Art bible not found | Fail — generation without palette produces wrong colors | +| MCP tool timeout | Report tool + args that failed, offer retry/skip/stop | +| Palette resource unknown | Fall back to `set_palette` with hex colors from art bible | +| Export fails | Check file path permissions, suggest manual export | +| validate_scene fails | Report which layers/frames are missing, offer to fix via ensure_layers_present | + +## Collaborative Protocol + +- Never generate without user confirming the target +- Report each asset's success/failure immediately after processing it +- Write the manifest update only after explicit approval +- If the art bible's palette has changed since the spec was written, ask which to use +``` + +- [ ] **Step 2: Verify the skill file is valid** + +```powershell +Test-Path ".opencode/skills/art-generate/SKILL.md" +``` + +Expected: `True` + +- [ ] **Step 3: Commit** + +```powershell +git add .opencode/skills/art-generate/SKILL.md +git commit -m "feat: add art-generate skill for programmatic placeholder art" +``` + +--- + +### Task 5: Verify Integration End-to-End + +- [ ] **Step 1: Verify MCP server starts** + +Run the aseprite MCP server briefly to confirm it initializes: + +```powershell +$env:ASEPRITE_PATH = "E:\Jaco\Projects\Godot\aseprite\build\bin\aseprite.exe" +uv run --directory tools/aseprite-mcp --no-sync -m aseprite_mcp +``` + +After confirming it starts (`{"jsonrpc":"2.0","method":"...` output), kill with `Ctrl+C`. + +- [ ] **Step 2: Verify opencode.json is valid JSON** + +```powershell +Get-Content opencode.json | python -m json.tool > $null; echo "Valid" +``` + +Expected: `Valid` + +- [ ] **Step 3: Verify skill is findable** + +```powershell +Get-ChildItem -Recurse -Filter "SKILL.md" -Path ".opencode/skills/art-generate" +``` + +Expected: Finds the file. + +- [ ] **Step 4: Verify tool availability (optional)** + +If opencode exposes MCP tool listing, run: + +```powershell +uv run --directory tools/aseprite-mcp --no-sync -m aseprite_mcp 2>&1 +# Check that known tools like create_canvas, add_layer appear +``` + +- [ ] **Step 5: Final commit (if any verification fixes were needed)** + +```powershell +git add -A +git commit -m "fix: verification adjustments for aseprite-mcp integration" +``` + +--- + +### Rollback Plan + +If any step fails catastrophically: + +- **Submodule issue**: `git submodule deinit tools/aseprite-mcp && git rm tools/aseprite-mcp` +- **MCP config issue**: `git checkout opencode.json` (revert to original) +- **Skill issue**: `git rm .opencode/skills/art-generate/SKILL.md` +- **Full rollback**: `git reset --hard HEAD~4` (revert last 4 commits) diff --git a/framework/docs/superpowers/specs/2026-05-06-aseprite-mcp-integration-design.md b/framework/docs/superpowers/specs/2026-05-06-aseprite-mcp-integration-design.md new file mode 100644 index 0000000..3a10a97 --- /dev/null +++ b/framework/docs/superpowers/specs/2026-05-06-aseprite-mcp-integration-design.md @@ -0,0 +1,199 @@ +# Aseprite MCP Integration Design + +> **Status:** Draft +> **Date:** 2026-05-06 +> **Context:** Integrate the [aseprite-mcp](https://github.com/striderZA/aseprite-mcp) server into the OCGS workflow for programmatic art generation from asset specs. + +## 1. Motivation + +The OCGS workflow currently has three art-focused skills: + +| Skill | Function | +|-------|----------| +| `art-bible` | Defines visual identity, palette, shape language, asset standards | +| `asset-spec` | Generates per-asset visual specs with dimensions, format, naming | +| `asset-audit` | Validates delivered assets against specs | + +There's a gap: **no skill creates the actual art files.** An agent can describe what art to make and check if it exists, but can't make it. The Aseprite MCP closes this gap by allowing programmatic sprite creation, drawing, animation, and export from within an agent session. + +## 2. Architecture + +### 2.1 Git Submodule + +``` +tools/aseprite-mcp/ → https://github.com/striderZA/aseprite-mcp.git +``` + +Pinned to a specific commit/tag for reproducible builds. `ASEPRITE_PATH` is set via environment variable in the MCP config (not hardcoded in the repo). + +### 2.2 Project-Level MCP Config + +Added to `opencode.json` alongside the existing Godot MCP entry (which remains disabled by default): + +```json +"mcp": { + "aseprite": { + "type": "local", + "command": ["uv", "--directory", "tools/aseprite-mcp", "run", "--no-sync", "-m", "aseprite_mcp"], + "enabled": true, + "environment": { + "ASEPRITE_PATH": "E:\\Jaco\\Projects\\Godot\\aseprite\\build\\bin\\aseprite.exe" + } + }, + "godot": { + "type": "local", + "command": ["npx", "@coding-solo/godot-mcp"], + "enabled": false, + "env": { + "GODOT_PATH": "E:\\Jaco\\Projects\\Godot\\engine\\godot.exe", + "DEBUG": "true" + } + } +} +``` + +The Aseprite MCP's 60+ tools become available to any agent in the project when this config is live. + +### 2.3 Skill: `art-generate` + +New skill at `.opencode/skills/art-generate/SKILL.md`. Creates .aseprite files from asset specs. + +## 3. Skill Design: `art-generate` + +### 3.1 Invocation + +``` +/art-generate system: +/art-generate level: +/art-generate character: +/art-generate path/to/spec-file.md +``` + +No argument → reads `design/assets/asset-manifest.md` and finds the first target with `Needed` assets. + +### 3.2 Phase 0: Parse Target & Gather Context + +**Reads:** +- The asset spec file (e.g., `design/assets/specs/[target]-assets.md`) — extracts all ASSET-NNN entries with their fields +- `design/art/art-bible.md` — extracts palette (Section 2), shape language (Section 3), asset standards (Section 8) +- `design/assets/asset-manifest.md` — checks which assets already have placeholders + +**Output:** list of assets to generate, filtered to `Status: Needed`. + +### 3.3 Phase 1: Per-Asset Generation + +For each asset, this pipeline executes via Aseprite MCP tools: + +``` +1. create_canvas(width, height, filename) +2. set_palette(filename, colors) # from art bible palette +3. add_layer(filename, "body") + add_layer(filename, "details") + add_layer(filename, "outline") # layer structure per category template +4. draw or fill shapes on each layer # programmatic, based on shape language +5. set_tag(filename, "idle", 1, N) # if multi-frame +6. export_sprite(filename, output_path) # PNG export +``` + +#### Category Templates + +**Sprite / 2D Asset:** +- Canvas: exact dimensions from spec (e.g., 256x256) +- Layers: body, details, outline +- Body fill: art bible semantic color for the asset's role (e.g., Threat Blue for enemies) +- Detail shapes: secondary color, offset or layered on body +- Outline: dark version of body color on outline layer +- If animated: duplicate frame range, set durations, tag + +**UI Icon:** +- Canvas: 32-64px (per art bible asset standards) +- Layers: icon, background (optional) +- Simplified shape matching asset function +- Uses UI palette from art bible Section 6 + +**VFX:** +- Canvas: dimensions matching target sprite +- Layers: core, glow, sparks +- Core: filled shape with color from VFX section +- Glow: same shape at lower opacity, additive blend mode +- Multi-frame if animated (tween opacity for fade) + +**Environment Tile/Prop:** +- Canvas: tile dimensions from spec +- Layers: base, shading, details +- Palette from environment section of art bible + +**Character Sprite:** +- Canvas: full sprite sheet dimensions (e.g., 1024x256 for 4 frames) +- Layers: body, head, arms, legs, outline +- Base pose on frame 1 +- Duplicate to frame 2-N, offset limbs for walk cycle (rough) +- Tag: idle (1), walk (2-5), etc. + +### 3.4 Phase 2: Output & Manifest Update + +**Output files:** +``` +assets/source/[target]/ASSET-NNN-asset-name.aseprite # source +assets/sprites/[target]/ASSET-NNN-asset-name.png # export +``` + +**Manifest update:** +- Change `Status` from `Needed` to `Placeholder Created` +- Add `Source` column with `.aseprite` path + +### 3.5 Phase 3: Verification + +For each created asset: +- `validate_scene(filename)` — confirms all required layers and frames exist +- File size check against asset standards +- Dimension check against spec + +If validation fails: report the issue, do NOT mark as Placeholder Created. + +### 3.6 Agent Routing + +- **Main session** (this agent): Executes generation pipeline, writes files, updates manifest +- **`art-director`** (subagent, optional): Consulted for color mapping from art bible to asset categories +- **`technical-artist`** (subagent, optional): Consulted for export format / compression settings + +## 4. Directory Changes + +| Path | Change | Reason | +|------|--------|--------| +| `tools/aseprite-mcp/` | New — git submodule | Aseprite MCP server source | +| `opencode.json` | Modified — add MCP block | Project-scoped MCP config | +| `.opencode/skills/art-generate/SKILL.md` | New | Art generation skill | +| `assets/source/` | New — per-target dirs | .aseprite source files | +| `assets/sprites/` | New — per-target dirs | Exported PNGs | + +## 5. Dependency Map + +``` +art-bible (palette, shape language, standards) + ↓ +asset-spec (dimensions, format, naming) + ↓ +art-generate (creates .aseprite + exports PNG) ← NEW + ↓ +asset-audit (validates delivered assets) +``` + +Each skill feeds into the next. `art-generate` sits between spec and audit — it produces the artifacts that audit validates. + +## 6. Risks & Mitigations + +| Risk | Mitigation | +|------|------------| +| Aseprite not installed / wrong path | MCP config has a clear ASEPRITE_PATH error; `opencode.json` validation catches this early | +| Generated art looks bad | Intentional — this is placeholder/prototype art, not production. The value is in correct dimensions, layers, palette, and naming. | +| Aseprite MCP tool failures mid-batch | Phase 3 verification per asset; failed assets are reported without blocking the rest | +| Submodule drift from upstream | Pin to a specific commit; upgrade explicitly via `git submodule update --remote` | +| Python/dependency conflicts | `uv` manages isolated venv; `--no-sync` uses cached deps after initial `uv sync` | + +## 7. Out of Scope + +- **Production-quality art generation** — this creates placeholders matching spec constraints, not artist-quality assets +- **Asset modification** — `art-generate` only creates new assets; editing existing ones is a future concern +- **Batch generation from multiple specs** — one target per invocation +- **AI image generation integration** — no Stable Diffusion / DALL-E calls; purely procedural art via Aseprite drawing tools diff --git a/opencode.json b/opencode.json index d10ae6a..0bb23f9 100644 --- a/opencode.json +++ b/opencode.json @@ -8,6 +8,25 @@ "agent": "prototyper" } }, + "mcp": { + "aseprite": { + "type": "local", + "command": ["uv", "--directory", "tools/aseprite-mcp", "run", "-m", "aseprite_mcp"], + "enabled": true, + "environment": { + "ASEPRITE_PATH": "{env:ASEPRITE_PATH}" + } + }, + "godot": { + "type": "local", + "command": ["npx", "@coding-solo/godot-mcp"], + "enabled": false, + "environment": { + "GODOT_PATH": "{env:GODOT_PATH}", + "DEBUG": "{env:DEBUG}" + } + } + }, "permission": { "bash": { "git status*": "allow", diff --git a/skill-testing-framework/catalog.yaml b/skill-testing-framework/catalog.yaml index cad5a63..e3f6267 100644 --- a/skill-testing-framework/catalog.yaml +++ b/skill-testing-framework/catalog.yaml @@ -786,6 +786,17 @@ skills: priority: low category: utility + - name: art-generate + spec: "" + last_static: "" + last_static_result: "" + last_spec: "" + last_spec_result: "" + last_category: "" + last_category_result: "" + priority: low + category: utility + - name: day-one-patch spec: CCGS Skill Testing Framework/skills/utility/day-one-patch.md last_static: "" diff --git a/tests/workflow/run-all.mjs b/tests/workflow/run-all.mjs index 373dc4e..b898cbb 100644 --- a/tests/workflow/run-all.mjs +++ b/tests/workflow/run-all.mjs @@ -14,19 +14,35 @@ const suites = [ { name: 'invariants', file: join(__dirname, 'invariants.mjs') }, ]; +const SUITE_TIMEOUT = 30000; + async function runSuite(suite) { return new Promise((resolve) => { const child = fork(suite.file, [], { cwd: ROOT, stdio: ['pipe', 'pipe', 'pipe', 'ipc'] }); let output = ''; + let settled = false; + + const timer = setTimeout(() => { + if (settled) return; + settled = true; + child.kill(); + resolve({ name: suite.name, output: output + '\nError: Suite timed out after 30s', code: 1 }); + }, SUITE_TIMEOUT); child.stdout.on('data', (data) => { output += data.toString(); }); child.stderr.on('data', (data) => { output += data.toString(); }); child.on('close', (code) => { + if (settled) return; + settled = true; + clearTimeout(timer); resolve({ name: suite.name, output, code }); }); child.on('error', (err) => { + if (settled) return; + settled = true; + clearTimeout(timer); resolve({ name: suite.name, output: `Error: ${err.message}`, code: 1 }); }); }); diff --git a/tools/aseprite-mcp b/tools/aseprite-mcp new file mode 160000 index 0000000..be47592 --- /dev/null +++ b/tools/aseprite-mcp @@ -0,0 +1 @@ +Subproject commit be475927507344e6994dc43430b593a0223540d1